@fimbul/wotan
Advanced tools
Comparing version
{ | ||
"name": "@fimbul/wotan", | ||
"version": "0.21.0-dev.20190318", | ||
"version": "0.21.0-dev.20190322", | ||
"description": "Pluggable TypeScript and JavaScript linter", | ||
@@ -43,4 +43,4 @@ "bin": "bin/main.js", | ||
"dependencies": { | ||
"@fimbul/mimir": "0.21.0-dev.20190318", | ||
"@fimbul/ymir": "0.21.0-dev.20190318", | ||
"@fimbul/mimir": "0.21.0-dev.20190322", | ||
"@fimbul/ymir": "0.21.0-dev.20190322", | ||
"bind-decorator": "^1.0.11", | ||
@@ -47,0 +47,0 @@ "chalk": "^2.3.0", |
import * as ts from 'typescript'; | ||
import { Finding, EffectiveConfiguration, LintAndFixFileResult, Severity, MessageHandler, AbstractProcessor, DeprecationHandler, FindingFilterFactory } from '@fimbul/ymir'; | ||
import { RuleLoader } from './services/rule-loader'; | ||
export interface UpdateFileResult { | ||
file: ts.SourceFile; | ||
program?: ts.Program; | ||
} | ||
export interface LinterOptions { | ||
reportUselessDirectives?: Severity; | ||
} | ||
export declare type UpdateFileCallback = (content: string, range: ts.TextChangeRange) => UpdateFileResult | undefined; | ||
export interface ProgramFactory { | ||
getCompilerOptions(): ts.CompilerOptions; | ||
getProgram(): ts.Program; | ||
} | ||
export declare type UpdateFileCallback = (content: string, range: ts.TextChangeRange) => ts.SourceFile | undefined; | ||
export declare class Linter { | ||
@@ -18,6 +18,6 @@ private ruleLoader; | ||
constructor(ruleLoader: RuleLoader, logger: MessageHandler, deprecationHandler: DeprecationHandler, filterFactory: FindingFilterFactory); | ||
lintFile(file: ts.SourceFile, config: EffectiveConfiguration, program?: ts.Program, options?: LinterOptions): ReadonlyArray<Finding>; | ||
lintAndFix(file: ts.SourceFile, content: string, config: EffectiveConfiguration, updateFile: UpdateFileCallback, iterations?: number, program?: ts.Program, processor?: AbstractProcessor, options?: LinterOptions): LintAndFixFileResult; | ||
lintFile(file: ts.SourceFile, config: EffectiveConfiguration, programOrFactory?: ProgramFactory | ts.Program, options?: LinterOptions): ReadonlyArray<Finding>; | ||
lintAndFix(file: ts.SourceFile, content: string, config: EffectiveConfiguration, updateFile: UpdateFileCallback, iterations?: number, programFactory?: ProgramFactory, processor?: AbstractProcessor, options?: LinterOptions): LintAndFixFileResult; | ||
private prepareRules; | ||
private applyRules; | ||
} |
@@ -13,2 +13,30 @@ "use strict"; | ||
const log = debug('wotan:linter'); | ||
class StaticProgramFactory { | ||
constructor(program) { | ||
this.program = program; | ||
} | ||
getCompilerOptions() { | ||
return this.program.getCompilerOptions(); | ||
} | ||
getProgram() { | ||
return this.program; | ||
} | ||
} | ||
class CachedProgramFactory { | ||
constructor(factory) { | ||
this.factory = factory; | ||
this.program = undefined; | ||
this.options = undefined; | ||
} | ||
getCompilerOptions() { | ||
return this.options || (this.options = this.factory.getCompilerOptions()); | ||
} | ||
getProgram() { | ||
if (this.program === undefined) { | ||
this.program = this.factory.getProgram(); | ||
this.options = this.program.getCompilerOptions(); | ||
} | ||
return this.program; | ||
} | ||
} | ||
let Linter = class Linter { | ||
@@ -21,8 +49,10 @@ constructor(ruleLoader, logger, deprecationHandler, filterFactory) { | ||
} | ||
lintFile(file, config, program, options = {}) { | ||
return this.getFindings(file, config, program, undefined, options); | ||
lintFile(file, config, programOrFactory, options = {}) { | ||
return this.getFindings(file, config, programOrFactory !== undefined && 'getTypeChecker' in programOrFactory | ||
? new StaticProgramFactory(programOrFactory) | ||
: programOrFactory, undefined, options); | ||
} | ||
lintAndFix(file, content, config, updateFile, iterations = 10, program, processor, options = {}) { | ||
lintAndFix(file, content, config, updateFile, iterations = 10, programFactory, processor, options = {}) { | ||
let totalFixes = 0; | ||
let findings = this.getFindings(file, config, program, processor, options); | ||
let findings = this.getFindings(file, config, programFactory, processor, options); | ||
for (let i = 0; i < iterations; ++i) { | ||
@@ -57,6 +87,6 @@ if (findings.length === 0) | ||
} | ||
({ program, file } = updateResult); | ||
file = updateResult; | ||
content = fixed.result; | ||
totalFixes += fixed.fixed; | ||
findings = this.getFindings(file, config, program, processor, options); | ||
findings = this.getFindings(file, config, programFactory, processor, options); | ||
} | ||
@@ -69,14 +99,16 @@ return { | ||
} | ||
getFindings(sourceFile, config, program, processor, options) { | ||
getFindings(sourceFile, config, programFactory, processor, options) { | ||
if (programFactory !== undefined) | ||
programFactory = new CachedProgramFactory(programFactory); | ||
let suppressMissingTypeInfoWarning = false; | ||
log('Linting file %s', sourceFile.fileName); | ||
if (program !== undefined && /\.jsx?/.test(sourceFile.fileName)) { | ||
if (programFactory !== undefined && /\.jsx?/.test(sourceFile.fileName)) { | ||
const directive = tsutils_1.getCheckJsDirective(sourceFile.text); | ||
if (directive === undefined ? !tsutils_1.isCompilerOptionEnabled(program.getCompilerOptions(), 'checkJs') : !directive.enabled) { | ||
if (directive === undefined ? !tsutils_1.isCompilerOptionEnabled(programFactory.getCompilerOptions(), 'checkJs') : !directive.enabled) { | ||
log('Not using type information for this unchecked JS file'); | ||
program = undefined; | ||
programFactory = undefined; | ||
suppressMissingTypeInfoWarning = true; | ||
} | ||
} | ||
const rules = this.prepareRules(config, sourceFile, program, suppressMissingTypeInfoWarning); | ||
const rules = this.prepareRules(config, sourceFile, programFactory, suppressMissingTypeInfoWarning); | ||
let findings; | ||
@@ -95,6 +127,6 @@ if (rules.length === 0) { | ||
} | ||
findings = this.applyRules(sourceFile, program, rules, config.settings, options); | ||
findings = this.applyRules(sourceFile, programFactory, rules, config.settings, options); | ||
return processor === undefined ? findings : processor.postprocess(findings); | ||
} | ||
prepareRules(config, sourceFile, program, noWarn) { | ||
prepareRules(config, sourceFile, programFactory, noWarn) { | ||
const rules = []; | ||
@@ -109,3 +141,3 @@ for (const [ruleName, { options, severity, rulesDirectories, rule }] of config.rules) { | ||
this.deprecationHandler.handle("rule", ruleName, typeof ctor.deprecated === 'string' ? ctor.deprecated : undefined); | ||
if (program === undefined && ctor.requiresTypeInformation) { | ||
if (programFactory === undefined && ctor.requiresTypeInformation) { | ||
if (noWarn) { | ||
@@ -120,3 +152,8 @@ log('Rule %s requires type information', ruleName); | ||
if (ctor.supports !== undefined) { | ||
const supports = ctor.supports(sourceFile, { program, options, settings: config.settings }); | ||
const supports = ctor.supports(sourceFile, { | ||
get program() { return programFactory && programFactory.getProgram(); }, | ||
get compilerOptions() { return programFactory && programFactory.getCompilerOptions(); }, | ||
options, | ||
settings: config.settings, | ||
}); | ||
if (supports !== true) { | ||
@@ -136,3 +173,3 @@ if (!supports) { | ||
} | ||
applyRules(sourceFile, program, rules, settings, options) { | ||
applyRules(sourceFile, programFactory, rules, settings, options) { | ||
const result = []; | ||
@@ -170,3 +207,4 @@ let findingFilter; | ||
getWrappedAst, | ||
program, | ||
get program() { return programFactory && programFactory.getProgram(); }, | ||
get compilerOptions() { return programFactory && programFactory.getCompilerOptions(); }, | ||
sourceFile, | ||
@@ -173,0 +211,0 @@ settings, |
@@ -177,15 +177,8 @@ "use strict"; | ||
} | ||
updateSourceFile(sourceFile, program, newContent, changeRange) { | ||
let error = false; | ||
const oldContent = sourceFile.text; | ||
sourceFile = ts.updateSourceFile(sourceFile, newContent, changeRange); | ||
if (utils_1.hasParseErrors(sourceFile)) { | ||
log("Not using updated content of '%s' because of syntax errors", sourceFile.fileName); | ||
sourceFile = ts.updateSourceFile(sourceFile, oldContent, utils_1.invertChangeRange(changeRange)); | ||
error = true; | ||
} | ||
updateSourceFile(sourceFile) { | ||
this.sourceFileCache.set(sourceFile.fileName, sourceFile); | ||
program = this.createProgram(program.getRootFileNames(), program.getCompilerOptions(), program, program.getProjectReferences()); | ||
return { sourceFile, program, error }; | ||
} | ||
updateProgram(program) { | ||
return this.createProgram(program.getRootFileNames(), program.getCompilerOptions(), program, program.getProjectReferences()); | ||
} | ||
onReleaseOldSourceFile(sourceFile) { | ||
@@ -192,0 +185,0 @@ this.uncacheFile(sourceFile.fileName); |
@@ -48,2 +48,16 @@ "use strict"; | ||
for (let { files, program } of this.getFilesAndProgram(options.project, options.files, options.exclude, processorHost, options.references)) { | ||
let invalidatedProgram = false; | ||
const factory = { | ||
getCompilerOptions() { | ||
return program.getCompilerOptions(); | ||
}, | ||
getProgram() { | ||
if (invalidatedProgram) { | ||
log('updating invalidated program'); | ||
program = processorHost.updateProgram(program); | ||
invalidatedProgram = false; | ||
} | ||
return program; | ||
}, | ||
}; | ||
for (const file of files) { | ||
@@ -63,10 +77,17 @@ if (options.config === undefined) | ||
summary = this.linter.lintAndFix(sourceFile, originalContent, effectiveConfig, (content, range) => { | ||
let error; | ||
({ sourceFile, program, error } = processorHost.updateSourceFile(sourceFile, program, content, range)); | ||
return error ? undefined : { program, file: sourceFile }; | ||
}, fix === true ? undefined : fix, program, mapped === undefined ? undefined : mapped.processor, linterOptions); | ||
invalidatedProgram = true; | ||
const oldContent = sourceFile.text; | ||
sourceFile = ts.updateSourceFile(sourceFile, content, range); | ||
const hasErrors = utils_1.hasParseErrors(sourceFile); | ||
if (hasErrors) { | ||
log("Autofixing caused syntax errors in '%s', rolling back", sourceFile.fileName); | ||
sourceFile = ts.updateSourceFile(sourceFile, oldContent, utils_1.invertChangeRange(range)); | ||
} | ||
processorHost.updateSourceFile(sourceFile); | ||
return hasErrors ? undefined : sourceFile; | ||
}, fix === true ? undefined : fix, factory, mapped === undefined ? undefined : mapped.processor, linterOptions); | ||
} | ||
else { | ||
summary = { | ||
findings: this.linter.getFindings(sourceFile, effectiveConfig, program, mapped === undefined ? undefined : mapped.processor, linterOptions), | ||
findings: this.linter.getFindings(sourceFile, effectiveConfig, factory, mapped === undefined ? undefined : mapped.processor, linterOptions), | ||
fixes: 0, | ||
@@ -133,3 +154,3 @@ content: originalContent, | ||
} | ||
return { file: sourceFile }; | ||
return sourceFile; | ||
}, fix === true ? undefined : fix, undefined, processor, linterOptions); | ||
@@ -136,0 +157,0 @@ } |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
324090
1.02%4015
1.31%+ Added
+ Added
- Removed
- Removed