Skip to content

Commit ebf9515

Browse files
committed
refactor(lint): use tslint api for linting
Closes #867, #3993 BREAKING CHANGE: In order to use the updated `ng lint` command, the following section will have to be added to the project's `angular-cli.json` at the root level of the json object. ```json "lint": [ { "files": "src/**/*.ts", "project": "src/tsconfig.json" }, { "files": "e2e/**/*.ts", "project": "e2e/tsconfig.json" } ], ``` Alternatively, you can run `ng update`.
1 parent 7edac2b commit ebf9515

File tree

14 files changed

+224
-32
lines changed

14 files changed

+224
-32
lines changed

docs/documentation/lint.md

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1+
<!-- Links in /docs/documentation should NOT have `.md` at the end, because they end up in our wiki at release. -->
2+
13
# ng lint
24

35
## Overview
4-
`ng lint` will lint your app code.
6+
`ng lint` will lint you app code using tslint.
7+
8+
## Options
9+
10+
`--fix` will attempt to fix lint errors
511

6-
This will use the `lint` npm script that in generated projects uses `tslint`.
12+
`--force` will always return error code 0 even with lint errors
713

8-
You can modify the these scripts in `package.json` to run whatever tool you prefer.
14+
`--format` (`-t`) the output formatter to use

packages/angular-cli/blueprints/ng2/files/angular-cli.json

+10
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@
3333
"config": "./protractor.conf.js"
3434
}
3535
},
36+
"lint": [
37+
{
38+
"files": "<%= sourceDir %>/**/*.ts",
39+
"project": "<%= sourceDir %>/tsconfig.json"
40+
},
41+
{
42+
"files": "e2e/**/*.ts",
43+
"project": "e2e/tsconfig.json"
44+
}
45+
],
3646
"test": {
3747
"karma": {
3848
"config": "./karma.conf.js"

packages/angular-cli/blueprints/ng2/files/package.json

-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
"scripts": {
77
"ng": "ng",
88
"start": "ng serve",
9-
"lint": "tslint \"<%= sourceDir %>/**/*.ts\" --project src/tsconfig.json --type-check && tslint \"e2e/**/*.ts\" --project e2e/tsconfig.json --type-check",
109
"test": "ng test",
1110
"pree2e": "webdriver-manager update --standalone false --gecko false",
1211
"e2e": "protractor"

packages/angular-cli/commands/lint.ts

+15-2
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,29 @@
11
const Command = require('../ember-cli/lib/models/command');
22

3+
export interface LintCommandOptions {
4+
fix?: boolean;
5+
format?: string;
6+
force?: boolean;
7+
}
8+
39
export default Command.extend({
410
name: 'lint',
11+
aliases: ['l'],
512
description: 'Lints code in existing project',
613
works: 'insideProject',
7-
run: function () {
14+
availableOptions: [
15+
{ name: 'fix', type: Boolean, default: false },
16+
{ name: 'force', type: Boolean, default: false },
17+
{ name: 'format', alias: 't', type: String, default: 'prose' }
18+
],
19+
run: function (commandOptions: LintCommandOptions) {
820
const LintTask = require('../tasks/lint').default;
21+
922
const lintTask = new LintTask({
1023
ui: this.ui,
1124
project: this.project
1225
});
1326

14-
return lintTask.run();
27+
return lintTask.run(commandOptions);
1528
}
1629
});

packages/angular-cli/lib/config/schema.json

+27-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,9 @@
124124
}
125125
},
126126
"additionalProperties": true,
127-
"required": ["input"]
127+
"required": [
128+
"input"
129+
]
128130
}
129131
]
130132
},
@@ -173,6 +175,30 @@
173175
},
174176
"additionalProperties": false
175177
},
178+
"lint": {
179+
"description": "Properties to be passed to TSLint.",
180+
"type": "array",
181+
"items": {
182+
"type": "object",
183+
"properties": {
184+
"files": {
185+
"type": "string"
186+
},
187+
"project": {
188+
"type": "string"
189+
},
190+
"tslintConfig": {
191+
"type": "string",
192+
"default": "tslint.json"
193+
}
194+
},
195+
"required": [
196+
"files",
197+
"project"
198+
],
199+
"additionalProperties": false
200+
}
201+
},
176202
"test": {
177203
"type": "object",
178204
"properties": {

packages/angular-cli/tasks/lint.ts

+51-12
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,61 @@
11
const Task = require('../ember-cli/lib/models/task');
22
import * as chalk from 'chalk';
3-
import {exec} from 'child_process';
3+
import * as path from 'path';
4+
import { requireDependency } from '../utilities/require-project-module';
5+
import { CliConfig } from '../models/config';
6+
import { LintCommandOptions } from '../commands/lint';
7+
import { oneLine } from 'common-tags';
48

59
export default Task.extend({
6-
run: function () {
10+
run: function (commandOptions: LintCommandOptions) {
711
const ui = this.ui;
12+
const projectRoot = this.project.root;
813

9-
return new Promise(function(resolve, reject) {
10-
exec('npm run lint', (err, stdout) => {
11-
ui.writeLine(stdout);
12-
if (err) {
13-
ui.writeLine(chalk.red('Lint errors found in the listed files.'));
14-
reject();
15-
} else {
16-
ui.writeLine(chalk.green('All files pass linting.'));
17-
resolve();
18-
}
14+
return new Promise(function (resolve, reject) {
15+
const tslint = requireDependency(projectRoot, 'tslint');
16+
const Linter = tslint.Linter;
17+
const Configuration = tslint.Configuration;
18+
19+
const lintConfigs = CliConfig.fromProject().config.lint || [];
20+
21+
if (lintConfigs.length === 0) {
22+
ui.writeLine(chalk.yellow(oneLine`
23+
No lint config(s) found.
24+
If this is not intended, run "ng update".
25+
`));
26+
return resolve(0);
27+
}
28+
29+
let errors = 0;
30+
31+
lintConfigs.forEach((config) => {
32+
const program = Linter.createProgram(config.project);
33+
const files: string[] = Linter.getFileNames(program);
34+
35+
const linter = new Linter({
36+
fix: commandOptions.fix,
37+
formatter: commandOptions.format
38+
}, program);
39+
40+
files.forEach((file) => {
41+
const fileContents = program.getSourceFile(file).getFullText();
42+
const configLoad = Configuration.findConfiguration(config.tslintConfig, file);
43+
linter.lint(file, fileContents, configLoad.results);
44+
});
45+
46+
const result = linter.getResult();
47+
errors += result.failureCount;
48+
49+
ui.writeLine(result.output.trim().concat('\n'));
1950
});
51+
52+
if (errors > 0) {
53+
ui.writeLine(chalk.red('Lint errors found in the listed files.'));
54+
return commandOptions.force ? resolve(0) : resolve(2);
55+
}
56+
57+
ui.writeLine(chalk.green('All files pass linting.'));
58+
return resolve(0);
2059
});
2160
}
2261
});

packages/angular-cli/tasks/test.ts

+1-7
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
const Task = require('../ember-cli/lib/models/task');
22
import { TestOptions } from '../commands/test';
33
import * as path from 'path';
4-
5-
// require dependencies within the target project
6-
function requireDependency(root: string, moduleName: string) {
7-
const packageJson = require(path.join(root, 'node_modules', moduleName, 'package.json'));
8-
const main = path.normalize(packageJson.main);
9-
return require(path.join(root, 'node_modules', moduleName, main));
10-
}
4+
import { requireDependency } from '../utilities/require-project-module';
115

126
export default Task.extend({
137
run: function (options: TestOptions) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import * as path from 'path';
2+
3+
// require dependencies within the target project
4+
export function requireDependency(root: string, moduleName: string) {
5+
const packageJson = require(path.join(root, 'node_modules', moduleName, 'package.json'));
6+
const main = path.normalize(packageJson.main);
7+
return require(path.join(root, 'node_modules', moduleName, main));
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { ng } from '../../utils/process';
2+
import { oneLine } from 'common-tags';
3+
4+
export default function () {
5+
return Promise.resolve()
6+
.then(() => ng('set', 'lint', '[]'))
7+
.then(() => ng('lint'))
8+
.then((output) => {
9+
if (!output.match(/No lint config\(s\) found\./)) {
10+
throw new Error(oneLine`
11+
Expected to match "No lint configs found."
12+
in ${output}.
13+
`);
14+
}
15+
16+
return output;
17+
})
18+
.then((output) => {
19+
if (!output.match(/If this is not intended, run "ng update"\./)) {
20+
throw new Error(oneLine`
21+
Expected to match "If this is not intended, run "ng update"."
22+
in ${output}.
23+
`);
24+
}
25+
26+
return output;
27+
});
28+
}

tests/e2e/tests/lint/lint-with-fix.ts

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { ng } from '../../utils/process';
2+
import { readFile, writeFile } from '../../utils/fs';
3+
4+
export default function () {
5+
const fileName = 'src/app/foo.ts';
6+
7+
return Promise.resolve()
8+
.then(() => writeFile(fileName, 'const foo = "";\n'))
9+
.then(() => ng('lint', '--fix', '--force'))
10+
.then(() => readFile(fileName))
11+
.then(content => {
12+
if (!content.match(/const foo = '';/)) {
13+
throw new Error(`Expected to match "const foo = '';" in ${content}.`);
14+
}
15+
});
16+
}
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { ng } from '../../utils/process';
2+
import { writeFile } from '../../utils/fs';
3+
import { oneLine } from 'common-tags';
4+
5+
export default function () {
6+
const fileName = 'src/app/foo.ts';
7+
8+
return Promise.resolve()
9+
.then(() => writeFile(fileName, 'const foo = "";\n'))
10+
.then(() => ng('lint', '--force'))
11+
.then((output) => {
12+
if (!output.match(/" should be '/)) {
13+
throw new Error(`Expected to match "" should be '" in ${output}.`);
14+
}
15+
16+
return output;
17+
})
18+
.then((output) => {
19+
if (!output.match(/Lint errors found in the listed files\./)) {
20+
throw new Error(oneLine`
21+
Expected to match "Lint errors found in the listed files."
22+
in ${output}.
23+
`);
24+
}
25+
});
26+
}
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { ng } from '../../utils/process';
2+
import { writeFile } from '../../utils/fs';
3+
import { oneLine } from 'common-tags';
4+
5+
export default function () {
6+
const fileName = 'src/app/foo.ts';
7+
8+
return Promise.resolve()
9+
.then(() => writeFile(fileName, 'const foo = "";\n'))
10+
.then(() => ng('lint', '--format=stylish', '--force'))
11+
.then((output) => {
12+
if (!output.match(/1:13 quotemark " should be '/)) {
13+
throw new Error(oneLine`
14+
Expected to match "1:13 quotemark " should be '"
15+
in ${output}.
16+
`);
17+
}
18+
});
19+
}

tests/e2e/tests/lint/lint.ts

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { ng } from '../../utils/process';
2+
import { oneLine } from 'common-tags';
3+
4+
export default function () {
5+
return ng('lint')
6+
.then((output) => {
7+
if (!output.match(/All files pass linting\./)) {
8+
throw new Error(oneLine`
9+
Expected to match "All files pass linting."
10+
in ${output}.
11+
`);
12+
}
13+
});
14+
}

tests/e2e/tests/test/lint.ts

-6
This file was deleted.

0 commit comments

Comments
 (0)