diff --git a/src/language/languageServiceHost.ts b/src/language/languageServiceHost.ts index 3a8b34e779a..fd00e8fd5c2 100644 --- a/src/language/languageServiceHost.ts +++ b/src/language/languageServiceHost.ts @@ -30,7 +30,7 @@ export function wrapProgram(program: ts.Program): ts.LanguageService { getCompilationSettings: () => program.getCompilerOptions(), getCurrentDirectory: () => program.getCurrentDirectory(), getDefaultLibFileName: () => null, - getScriptFileNames: () => program.getSourceFiles().map(sf => sf.fileName), + getScriptFileNames: () => program.getSourceFiles().map((sf) => sf.fileName), getScriptSnapshot: (name: string) => { if (files.hasOwnProperty(name)) { return ts.ScriptSnapshot.fromString(files[name]); diff --git a/src/language/rule/typedRule.ts b/src/language/rule/typedRule.ts index 4c5777b4f21..2e91c092bca 100644 --- a/src/language/rule/typedRule.ts +++ b/src/language/rule/typedRule.ts @@ -26,7 +26,7 @@ export abstract class TypedRule extends AbstractRule { return "applyWithProgram" in rule; } - public apply(sourceFile: ts.SourceFile, languageService: ts.LanguageService): RuleFailure[] { + public apply(): RuleFailure[] { // if no program is given to the linter, throw an error throw new Error(`${this.getOptions().ruleName} requires type checking`); } diff --git a/src/linter.ts b/src/linter.ts index e522288f358..6655415bdd7 100644 --- a/src/linter.ts +++ b/src/linter.ts @@ -31,6 +31,7 @@ import { EnableDisableRulesWalker } from "./enableDisableRules"; import { findFormatter } from "./formatterLoader"; import { ILinterOptions, LintResult } from "./index"; import { IFormatter } from "./language/formatter/formatter"; +import { createLanguageService, wrapProgram } from "./language/languageServiceHost"; import { Fix, IRule, RuleFailure } from "./language/rule/rule"; import { TypedRule } from "./language/rule/typedRule"; import * as utils from "./language/utils"; @@ -50,6 +51,7 @@ class Linter { private failures: RuleFailure[] = []; private fixes: RuleFailure[] = []; + private languageService: ts.LanguageService; /** * Creates a TypeScript program object from a tsconfig.json file path and optional project directory. @@ -93,6 +95,10 @@ class Linter { throw new Error("ILinterOptions does not contain the property `configuration` as of version 4. " + "Did you mean to pass the `IConfigurationFile` object to lint() ? "); } + + if (program) { + this.languageService = wrapProgram(program); + } } public lint(fileName: string, source?: string, configuration: IConfigurationFile = DEFAULT_CONFIG): void { @@ -158,9 +164,9 @@ class Linter { private applyRule(rule: IRule, sourceFile: ts.SourceFile) { let ruleFailures: RuleFailure[] = []; if (this.program && TypedRule.isTypedRule(rule)) { - ruleFailures = rule.applyWithProgram(sourceFile, this.program); + ruleFailures = rule.applyWithProgram(sourceFile, this.languageService); } else { - ruleFailures = rule.apply(sourceFile); + ruleFailures = rule.apply(sourceFile, this.languageService); } let fileFailures: RuleFailure[] = []; for (let ruleFailure of ruleFailures) { @@ -201,6 +207,7 @@ class Linter { } } else { sourceFile = utils.getSourceFile(fileName, source); + this.languageService = createLanguageService(fileName, source); } if (sourceFile === undefined) { diff --git a/src/rules/completedDocsRule.ts b/src/rules/completedDocsRule.ts index a43ad746ac2..13e6c905865 100644 --- a/src/rules/completedDocsRule.ts +++ b/src/rules/completedDocsRule.ts @@ -55,9 +55,9 @@ export class Rule extends Lint.Rules.TypedRule { Rule.ARGUMENT_PROPERTIES, ]; - public applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] { + public applyWithProgram(sourceFile: ts.SourceFile, langSvc: ts.LanguageService): Lint.RuleFailure[] { const options = this.getOptions(); - const completedDocsWalker = new CompletedDocsWalker(sourceFile, options, program); + const completedDocsWalker = new CompletedDocsWalker(sourceFile, options, langSvc.getProgram()); const nodesToCheck = this.getNodesToCheck(options.ruleArguments); completedDocsWalker.setNodesToCheck(nodesToCheck); diff --git a/src/rules/noUnusedVariableRule.ts b/src/rules/noUnusedVariableRule.ts index 29520650736..f943c7c9cac 100644 --- a/src/rules/noUnusedVariableRule.ts +++ b/src/rules/noUnusedVariableRule.ts @@ -145,9 +145,9 @@ class NoUnusedVariablesWalker extends Lint.RuleWalker { let someFixBrokeIt = false; // Performance optimization: type-check the whole file before verifying individual fixes - if (this.possibleFailures.some(f => f.hasFix())) { + if (this.possibleFailures.some((f) => f.hasFix())) { let newText = Lint.Fix.applyAll(this.getSourceFile().getFullText(), - this.possibleFailures.map(f => f.getFix()).filter(f => !!f)); + this.possibleFailures.map((f) => f.getFix()).filter((f) => !!f)); // If we have the program, we can verify that the fix doesn't introduce failures if (Lint.checkEdit(this.languageService, this.getSourceFile(), newText).length > 0) { @@ -156,7 +156,7 @@ class NoUnusedVariablesWalker extends Lint.RuleWalker { } } - this.possibleFailures.forEach(f => { + this.possibleFailures.forEach((f) => { if (!someFixBrokeIt || !f.hasFix()) { this.addFailure(f); } else { diff --git a/src/rules/preferForOfRule.ts b/src/rules/preferForOfRule.ts index b7096391d6e..21649389c75 100644 --- a/src/rules/preferForOfRule.ts +++ b/src/rules/preferForOfRule.ts @@ -35,7 +35,6 @@ export class Rule extends Lint.Rules.AbstractRule { public static FAILURE_STRING = "Expected a 'for-of' loop instead of a 'for' loop with this simple iteration"; public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { - const languageService = Lint.createLanguageService(sourceFile.fileName, sourceFile.getFullText()); return this.applyWithWalker(new PreferForOfWalker(sourceFile, this.getOptions())); } } diff --git a/src/test.ts b/src/test.ts index 214b68c2f78..84fcc53ada5 100644 --- a/src/test.ts +++ b/src/test.ts @@ -48,13 +48,19 @@ export function runTest(testDirectory: string, rulesDirectory?: string | string[ const filesToLint = glob.sync(path.join(testDirectory, `**/*${MARKUP_FILE_EXTENSION}`)); const tslintConfig = Linter.findConfiguration(path.join(testDirectory, "tslint.json"), null).results; const tsConfig = path.join(testDirectory, "tsconfig.json"); - let compilerOptions: ts.CompilerOptions = {}; + let compilerOptions: ts.CompilerOptions = { allowJs: true }; if (fs.existsSync(tsConfig)) { const {config, error} = ts.readConfigFile(tsConfig, ts.sys.readFile); if (error) { throw new Error(JSON.stringify(error)); } - compilerOptions = ts.parseJsonConfigFileContent(config, {readDirectory: ts.sys.readDirectory}, testDirectory).options; + + const parseConfigHost = { + fileExists: fs.existsSync, + readDirectory: ts.sys.readDirectory, + useCaseSensitiveFileNames: true, + }; + compilerOptions = ts.parseJsonConfigFileContent(config, parseConfigHost, testDirectory).options; } const results: TestResult = { directory: testDirectory, results: {} }; diff --git a/src/tslintMulti.ts b/src/tslintMulti.ts deleted file mode 100644 index 2f028dffcc8..00000000000 --- a/src/tslintMulti.ts +++ /dev/null @@ -1,169 +0,0 @@ -/** - * @license - * Copyright 2013 Palantir Technologies, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { existsSync } from "fs"; -import * as ts from "typescript"; - -import { - DEFAULT_CONFIG, - findConfiguration, - findConfigurationPath, - getRelativePath, - getRulesDirectories, - loadConfigurationFromPath, -} from "./configuration"; -import { EnableDisableRulesWalker } from "./enableDisableRules"; -import { findFormatter } from "./formatterLoader"; -import { IFormatter } from "./language/formatter/formatter"; -import { createLanguageService, wrapProgram } from "./language/languageServiceHost"; -import { RuleFailure } from "./language/rule/rule"; -import { TypedRule } from "./language/rule/typedRule"; -import { getSourceFile } from "./language/utils"; -import { IMultiLinterOptions, LintResult } from "./lint"; -import { loadRules } from "./ruleLoader"; -import { arrayify } from "./utils"; - -/** - * Linter that can lint multiple files in consecutive runs. - */ -class MultiLinter { - public static VERSION = "4.0.0-dev"; - - public static findConfiguration = findConfiguration; - public static findConfigurationPath = findConfigurationPath; - public static getRulesDirectories = getRulesDirectories; - public static loadConfigurationFromPath = loadConfigurationFromPath; - - private failures: RuleFailure[] = []; - private languageService: ts.LanguageService; - - /** - * Creates a TypeScript program object from a tsconfig.json file path and optional project directory. - */ - public static createProgram(configFile: string, projectDirectory?: string): ts.Program { - if (projectDirectory === undefined) { - const lastSeparator = configFile.lastIndexOf("/"); - if (lastSeparator < 0) { - projectDirectory = "."; - } else { - projectDirectory = configFile.substring(0, lastSeparator + 1); - } - } - - const { config } = ts.readConfigFile(configFile, ts.sys.readFile); - const parseConfigHost = { - fileExists: existsSync, - readDirectory: ts.sys.readDirectory, - useCaseSensitiveFileNames: true, - }; - const parsed = ts.parseJsonConfigFileContent(config, parseConfigHost, projectDirectory); - const host = ts.createCompilerHost(parsed.options, true); - const program = ts.createProgram(parsed.fileNames, parsed.options, host); - - return program; - } - - /** - * Returns a list of source file names from a TypeScript program. This includes all referenced - * files and excludes declaration (".d.ts") files. - */ - public static getFileNames(program: ts.Program): string[] { - return program.getSourceFiles().map(s => s.fileName).filter(l => l.substr(-5) !== ".d.ts"); - } - - constructor(private options: IMultiLinterOptions, private program?: ts.Program) { - if (program) { - this.languageService = wrapProgram(program); - } - } - - public lint(fileName: string, source?: string, configuration: any = DEFAULT_CONFIG): void { - let sourceFile: ts.SourceFile; - if (this.program) { - sourceFile = this.program.getSourceFile(fileName); - // check if the program has been type checked - if (sourceFile && !("resolvedModules" in sourceFile)) { - throw new Error("Program must be type checked before linting"); - } - } else { - sourceFile = getSourceFile(fileName, source); - this.languageService = createLanguageService(fileName, source); - } - - if (sourceFile === undefined) { - throw new Error(`Invalid source file: ${fileName}. Ensure that the files supplied to lint have a .ts or .tsx extension.`); - } - - // walk the code first to find all the intervals where rules are disabled - const rulesWalker = new EnableDisableRulesWalker(sourceFile, { - disabledIntervals: [], - ruleName: "", - }); - rulesWalker.walk(sourceFile); - const enableDisableRuleMap = rulesWalker.enableDisableRuleMap; - - const rulesDirectories = arrayify(this.options.rulesDirectory) - .concat(arrayify(configuration.rulesDirectory)); - const configurationRules = configuration.rules; - const configuredRules = loadRules(configurationRules, enableDisableRuleMap, rulesDirectories); - const enabledRules = configuredRules.filter((r) => r.isEnabled()); - for (let rule of enabledRules) { - let ruleFailures: RuleFailure[] = []; - if (this.program && rule instanceof TypedRule) { - ruleFailures = rule.applyWithProgram(sourceFile, this.languageService); - } else { - ruleFailures = rule.apply(sourceFile, this.languageService); - } - for (let ruleFailure of ruleFailures) { - if (!this.containsRule(this.failures, ruleFailure)) { - this.failures.push(ruleFailure); - } - } - } - } - - public getResult(): LintResult { - let formatter: IFormatter; - const formattersDirectory = getRelativePath(this.options.formattersDirectory); - - const formatterName = this.options.formatter || "prose"; - const Formatter = findFormatter(formatterName, formattersDirectory); - if (Formatter) { - formatter = new Formatter(); - } else { - throw new Error(`formatter '${formatterName}' not found`); - } - - const output = formatter.format(this.failures); - - return { - failureCount: this.failures.length, - failures: this.failures, - format: formatterName, - output, - }; - } - - private containsRule(rules: RuleFailure[], rule: RuleFailure) { - return rules.some((r) => r.equals(rule)); - } -} - -// tslint:disable-next-line:no-namespace -namespace MultiLinter {} - -export = MultiLinter;