Skip to content
This repository was archived by the owner on Mar 25, 2021. It is now read-only.

Commit eca8ac7

Browse files
caugnerjkillian
authored andcommitted
Call formatter once for all file results (#1472)
* Change tslint-cli#processFile to tslint-cli#processFiles * Extract MultiLinter from Linter * Always write output stream * MultiLinter#lint: Make source parameter optional
1 parent 8db2e4f commit eca8ac7

File tree

6 files changed

+221
-128
lines changed

6 files changed

+221
-128
lines changed

src/lint.ts

+6
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,9 @@ export interface ILinterOptions extends ILinterOptionsRaw {
5858
formatter: string | Function;
5959
rulesDirectory: string | string[];
6060
}
61+
62+
export interface IMultiLinterOptions {
63+
formatter?: string | Function;
64+
formattersDirectory?: string;
65+
rulesDirectory?: string | string[];
66+
}

src/tsconfig.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"test.ts",
4444
"tslint-cli.ts",
4545
"tslint.ts",
46+
"tslintMulti.ts",
4647
"utils.ts",
4748
"configs/latest.ts",
4849
"configs/recommended.ts",
@@ -98,8 +99,8 @@
9899
"rules/noConditionalAssignmentRule.ts",
99100
"rules/noConsecutiveBlankLinesRule.ts",
100101
"rules/noConsoleRule.ts",
101-
"rules/noConstructorVarsRule.ts",
102102
"rules/noConstructRule.ts",
103+
"rules/noConstructorVarsRule.ts",
103104
"rules/noDebuggerRule.ts",
104105
"rules/noDefaultExportRule.ts",
105106
"rules/noDuplicateKeyRule.ts",

src/tslint-cli.ts

+40-36
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
findConfiguration,
2828
} from "./configuration";
2929
import {consoleTestResultHandler, runTest} from "./test";
30-
import * as Linter from "./tslint";
30+
import * as Linter from "./tslintMulti";
3131

3232
let processed = optimist
3333
.usage("Usage: $0 [options] file ...")
@@ -217,45 +217,48 @@ if (argv.c && !fs.existsSync(argv.c)) {
217217
}
218218
const possibleConfigAbsolutePath = argv.c != null ? path.resolve(argv.c) : null;
219219

220-
const processFile = (file: string, program?: ts.Program) => {
221-
if (!fs.existsSync(file)) {
222-
console.error(`Unable to open file: ${file}`);
223-
process.exit(1);
224-
}
220+
const processFiles = (files: string[], program?: ts.Program) => {
225221

226-
const buffer = new Buffer(256);
227-
buffer.fill(0);
228-
const fd = fs.openSync(file, "r");
229-
try {
230-
fs.readSync(fd, buffer, 0, 256, null);
231-
if (buffer.readInt8(0) === 0x47 && buffer.readInt8(188) === 0x47) {
232-
// MPEG transport streams use the '.ts' file extension. They use 0x47 as the frame
233-
// separator, repeating every 188 bytes. It is unlikely to find that pattern in
234-
// TypeScript source, so tslint ignores files with the specific pattern.
235-
console.warn(`${file}: ignoring MPEG transport stream`);
236-
return;
222+
const linter = new Linter({
223+
formatter: argv.t,
224+
formattersDirectory: argv.s || "",
225+
rulesDirectory: argv.r || "",
226+
}, program);
227+
228+
for (const file of files) {
229+
if (!fs.existsSync(file)) {
230+
console.error(`Unable to open file: ${file}`);
231+
process.exit(1);
237232
}
238-
} finally {
239-
fs.closeSync(fd);
240-
}
241233

242-
const contents = fs.readFileSync(file, "utf8");
243-
const configuration = findConfiguration(possibleConfigAbsolutePath, file);
234+
const buffer = new Buffer(256);
235+
buffer.fill(0);
236+
const fd = fs.openSync(file, "r");
237+
try {
238+
fs.readSync(fd, buffer, 0, 256, null);
239+
if (buffer.readInt8(0) === 0x47 && buffer.readInt8(188) === 0x47) {
240+
// MPEG transport streams use the '.ts' file extension. They use 0x47 as the frame
241+
// separator, repeating every 188 bytes. It is unlikely to find that pattern in
242+
// TypeScript source, so tslint ignores files with the specific pattern.
243+
console.warn(`${file}: ignoring MPEG transport stream`);
244+
return;
245+
}
246+
} finally {
247+
fs.closeSync(fd);
248+
}
244249

245-
const linter = new Linter(file, contents, {
246-
configuration,
247-
formatter: argv.t,
248-
formattersDirectory: argv.s,
249-
rulesDirectory: argv.r,
250-
}, program);
250+
const contents = fs.readFileSync(file, "utf8");
251+
const configuration = findConfiguration(possibleConfigAbsolutePath, file);
252+
linter.lint(file, contents, configuration);
253+
}
251254

252-
const lintResult = linter.lint();
255+
const lintResult = linter.getResult();
253256

254-
if (lintResult.failureCount > 0) {
255-
outputStream.write(lintResult.output, () => {
257+
outputStream.write(lintResult.output, () => {
258+
if (lintResult.failureCount > 0) {
256259
process.exit(argv.force ? 0 : 2);
257-
});
258-
}
260+
}
261+
});
259262
};
260263

261264
// if both files and tsconfig are present, use files
@@ -293,6 +296,7 @@ if (argv.project != null) {
293296
}
294297
}
295298

296-
for (const file of files) {
297-
glob.sync(file, { ignore: argv.e }).forEach((file) => processFile(file, program));
298-
}
299+
files = files
300+
.map((file: string) => glob.sync(file, { ignore: argv.e }))
301+
.reduce((a: string[], b: string[]) => a.concat(b));
302+
processFiles(files, program);

src/tslint.ts

+12-90
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,18 @@ import {
2121
DEFAULT_CONFIG,
2222
findConfiguration,
2323
findConfigurationPath,
24-
getRelativePath,
2524
getRulesDirectories,
2625
loadConfigurationFromPath,
2726
} from "./configuration";
28-
import { EnableDisableRulesWalker } from "./enableDisableRules";
29-
import { findFormatter } from "./formatterLoader";
30-
import { IFormatter } from "./language/formatter/formatter";
31-
import { RuleFailure } from "./language/rule/rule";
32-
import { TypedRule } from "./language/rule/typedRule";
33-
import { getSourceFile } from "./language/utils";
3427
import { ILinterOptions, ILinterOptionsRaw, LintResult } from "./lint";
35-
import { loadRules } from "./ruleLoader";
28+
import * as MultiLinter from "./tslintMulti";
3629
import { arrayify } from "./utils";
3730

31+
/**
32+
* Linter that can lint exactly one file.
33+
*/
3834
class Linter {
39-
public static VERSION = "3.15.1";
35+
public static VERSION = MultiLinter.VERSION;
4036

4137
public static findConfiguration = findConfiguration;
4238
public static findConfigurationPath = findConfigurationPath;
@@ -49,99 +45,25 @@ class Linter {
4945
* Creates a TypeScript program object from a tsconfig.json file path and optional project directory.
5046
*/
5147
public static createProgram(configFile: string, projectDirectory?: string): ts.Program {
52-
if (projectDirectory === undefined) {
53-
const lastSeparator = configFile.lastIndexOf("/");
54-
if (lastSeparator < 0) {
55-
projectDirectory = ".";
56-
} else {
57-
projectDirectory = configFile.substring(0, lastSeparator + 1);
58-
}
59-
}
60-
61-
const {config} = ts.readConfigFile(configFile, ts.sys.readFile);
62-
const parsed = ts.parseJsonConfigFileContent(config, {readDirectory: ts.sys.readDirectory}, projectDirectory);
63-
const host = ts.createCompilerHost(parsed.options, true);
64-
const program = ts.createProgram(parsed.fileNames, parsed.options, host);
65-
66-
return program;
48+
return MultiLinter.createProgram(configFile, projectDirectory);
6749
}
6850

6951
/**
7052
* Returns a list of source file names from a TypeScript program. This includes all referenced
7153
* files and excludes declaration (".d.ts") files.
7254
*/
7355
public static getFileNames(program: ts.Program): string[] {
74-
return program.getSourceFiles().map(s => s.fileName).filter(l => l.substr(-5) !== ".d.ts");
56+
return MultiLinter.getFileNames(program);
7557
}
7658

7759
constructor(private fileName: string, private source: string, options: ILinterOptionsRaw, private program?: ts.Program) {
78-
this.options = this.computeFullOptions(options);
60+
this.options = this.computeFullOptions(options);
7961
}
8062

8163
public lint(): LintResult {
82-
const failures: RuleFailure[] = [];
83-
let sourceFile: ts.SourceFile;
84-
if (this.program) {
85-
sourceFile = this.program.getSourceFile(this.fileName);
86-
// check if the program has been type checked
87-
if (!("resolvedModules" in sourceFile)) {
88-
throw new Error("Program must be type checked before linting");
89-
}
90-
} else {
91-
sourceFile = getSourceFile(this.fileName, this.source);
92-
}
93-
94-
if (sourceFile === undefined) {
95-
throw new Error(`Invalid source file: ${this.fileName}. Ensure that the files supplied to lint have a .ts or .tsx extension.`);
96-
}
97-
98-
// walk the code first to find all the intervals where rules are disabled
99-
const rulesWalker = new EnableDisableRulesWalker(sourceFile, {
100-
disabledIntervals: [],
101-
ruleName: "",
102-
});
103-
rulesWalker.walk(sourceFile);
104-
const enableDisableRuleMap = rulesWalker.enableDisableRuleMap;
105-
106-
const rulesDirectories = this.options.rulesDirectory;
107-
const configuration = this.options.configuration.rules;
108-
const configuredRules = loadRules(configuration, enableDisableRuleMap, rulesDirectories);
109-
const enabledRules = configuredRules.filter((r) => r.isEnabled());
110-
for (let rule of enabledRules) {
111-
let ruleFailures: RuleFailure[] = [];
112-
if (this.program && rule instanceof TypedRule) {
113-
ruleFailures = rule.applyWithProgram(sourceFile, this.program);
114-
} else {
115-
ruleFailures = rule.apply(sourceFile);
116-
}
117-
for (let ruleFailure of ruleFailures) {
118-
if (!this.containsRule(failures, ruleFailure)) {
119-
failures.push(ruleFailure);
120-
}
121-
}
122-
}
123-
124-
let formatter: IFormatter;
125-
const formattersDirectory = getRelativePath(this.options.formattersDirectory);
126-
127-
const Formatter = findFormatter(this.options.formatter, formattersDirectory);
128-
if (Formatter) {
129-
formatter = new Formatter();
130-
} else {
131-
throw new Error(`formatter '${this.options.formatter}' not found`);
132-
}
133-
134-
const output = formatter.format(failures);
135-
return {
136-
failureCount: failures.length,
137-
failures,
138-
format: this.options.formatter,
139-
output,
140-
};
141-
}
142-
143-
private containsRule(rules: RuleFailure[], rule: RuleFailure) {
144-
return rules.some((r) => r.equals(rule));
64+
const multiLinter: MultiLinter = new MultiLinter(this.options, this.program);
65+
multiLinter.lint(this.fileName, this.source, this.options.configuration);
66+
return multiLinter.getResult();
14567
}
14668

14769
private computeFullOptions(options: ILinterOptionsRaw = {}): ILinterOptions {
@@ -157,7 +79,7 @@ class Linter {
15779
formattersDirectory,
15880
rulesDirectory: arrayify(rulesDirectory).concat(arrayify(configuration.rulesDirectory)),
15981
};
160-
}
82+
}
16183
}
16284

16385
// tslint:disable-next-line:no-namespace

0 commit comments

Comments
 (0)