@fimbul/wotan
Advanced tools
Comparing version 0.24.0-dev.20210208 to 0.24.0-dev.20210214
@@ -14,6 +14,9 @@ export * from '@fimbul/ymir'; | ||
export * from './src/services/default/rule-loader-host'; | ||
export * from './src/services/default/state-persistence'; | ||
export * from './src/services/cached-file-system'; | ||
export * from './src/services/configuration-manager'; | ||
export * from './src/services/dependency-resolver'; | ||
export * from './src/services/formatter-loader'; | ||
export * from './src/services/processor-loader'; | ||
export * from './src/services/program-state'; | ||
export * from './src/services/rule-loader'; | ||
@@ -20,0 +23,0 @@ export { parseGlobalOptions, ParsedGlobalOptions, GLOBAL_OPTIONS_SPEC } from './src/argparse'; |
@@ -18,6 +18,9 @@ "use strict"; | ||
tslib_1.__exportStar(require("./src/services/default/rule-loader-host"), exports); | ||
tslib_1.__exportStar(require("./src/services/default/state-persistence"), exports); | ||
tslib_1.__exportStar(require("./src/services/cached-file-system"), exports); | ||
tslib_1.__exportStar(require("./src/services/configuration-manager"), exports); | ||
tslib_1.__exportStar(require("./src/services/dependency-resolver"), exports); | ||
tslib_1.__exportStar(require("./src/services/formatter-loader"), exports); | ||
tslib_1.__exportStar(require("./src/services/processor-loader"), exports); | ||
tslib_1.__exportStar(require("./src/services/program-state"), exports); | ||
tslib_1.__exportStar(require("./src/services/rule-loader"), exports); | ||
@@ -24,0 +27,0 @@ var argparse_1 = require("./src/argparse"); |
{ | ||
"name": "@fimbul/wotan", | ||
"version": "0.24.0-dev.20210208", | ||
"version": "0.24.0-dev.20210214", | ||
"description": "Pluggable TypeScript and JavaScript linter", | ||
@@ -40,7 +40,8 @@ "bin": "bin/main.js", | ||
"escape-string-regexp": "^4.0.0", | ||
"memfs": "^3.2.0", | ||
"rimraf": "^3.0.0" | ||
}, | ||
"dependencies": { | ||
"@fimbul/mimir": "0.24.0-dev.20210131", | ||
"@fimbul/ymir": "0.22.0", | ||
"@fimbul/mimir": "0.24.0-dev.20210214", | ||
"@fimbul/ymir": "0.24.0-dev.20210214", | ||
"bind-decorator": "^1.0.11", | ||
@@ -63,3 +64,3 @@ "chalk": "^4.0.0", | ||
"tslib": "^2.0.0", | ||
"tsutils": "^3.18.0" | ||
"tsutils": "^3.20.0" | ||
}, | ||
@@ -66,0 +67,0 @@ "peerDependencies": { |
@@ -162,2 +162,3 @@ # Wotan | ||
* `--report-useless-directives [true|false|error|warning|suggestion]` reports `// wotan-disable` and `// wotan-enable` comments that are redundant (i.e. rules are already disabled) or unused (there are no findings for the specified rules). Useless directives are reported as lint findings with the specified severity (`true` is converted to `error`). Those findings cannot be disabled by a disable comment. The findings are fixable which allow autofixing when used with the `--fix` option. | ||
* `--cache` enables caching of lint results for projects. Can only be used with `-p --project` option. Read more about [caching](#caching). | ||
* `[...FILES]` specifies the files to lint. You can specify paths and glob patterns here. | ||
@@ -237,2 +238,32 @@ | ||
### Caching | ||
Caching is done per project. Hence it requires type information. For every `tsconfig.json` it creates a `tsconfig.fimbullintercache` file that contains the state of the previous run. | ||
The content of this file is not intended for use by other tools. All cache information is relative to the project directory. That means the cache can be checked into your VCS and reused by CI or your collegues. | ||
If a cache is available for a given project, Wotan can avoid linting files that are known to be unchanged. This can significantly reduce execution time. | ||
A file is treated as outdated and therefore linted if one of the following is true: | ||
* TypeScript version changed | ||
* compilerOptions changed | ||
* added, removed or outdated files that affect the global scope | ||
* linter configuration for the file changed | ||
* file has no cached lint result | ||
* file content changed | ||
* module resolution of imports or exports has changed | ||
* dependency is added or removed | ||
* ambient module or module augmentation is added, removed | ||
* dependency is outdated | ||
* if compilerOption `assumeChangesOnlyAffectDirectDependencies` is enabled, only checks direct dependencies | ||
* otherwise recursively checks all transitive dependencies | ||
The following cases don't work well with caching: | ||
* rules accessing the file system or environment variables | ||
* rules accessing other files in the project that are not global or dependencies of the current file | ||
* linting the same project with different configurations -> only use caching for one of the configurations | ||
* projects where all files are in the global scope | ||
* updating the linter, rules or their (transitive) dependencies -> you need to manually remove the cache if you expect it to affect the lint result | ||
### Excluded Files | ||
@@ -239,0 +270,0 @@ |
@@ -15,2 +15,3 @@ import { GlobalOptions, Severity } from '@fimbul/ymir'; | ||
references: OptionParser.ParseFunction<boolean>; | ||
cache: OptionParser.ParseFunction<boolean>; | ||
formatter: OptionParser.ParseFunction<string | undefined>; | ||
@@ -17,0 +18,0 @@ fix: OptionParser.ParseFunction<number | boolean>; |
@@ -51,2 +51,3 @@ "use strict"; | ||
references: optparse_1.OptionParser.Transform.withDefault(optparse_1.OptionParser.Factory.parsePrimitive('boolean'), false), | ||
cache: optparse_1.OptionParser.Transform.withDefault(optparse_1.OptionParser.Factory.parsePrimitive('boolean'), false), | ||
formatter: optparse_1.OptionParser.Factory.parsePrimitive('string'), | ||
@@ -103,2 +104,5 @@ fix: optparse_1.OptionParser.Transform.withDefault(optparse_1.OptionParser.Factory.parsePrimitive('boolean', 'number'), false), | ||
break; | ||
case '--cache': | ||
({ index: i, argument: result.cache } = parseOptionalBoolean(args, i)); | ||
break; | ||
case '-e': | ||
@@ -146,2 +150,3 @@ case '--exclude': | ||
} | ||
const usesProject = result.project.length !== 0 || result.files.length === 0; | ||
if (result.extensions !== undefined) { | ||
@@ -151,6 +156,8 @@ if (result.extensions.length === 0) { | ||
} | ||
else if (result.project.length !== 0 || result.files.length === 0) { | ||
else if (usesProject) { | ||
throw new ymir_1.ConfigurationError("Options '--ext' and '--project' cannot be used together."); | ||
} | ||
} | ||
if (result.cache && !usesProject) | ||
throw new ymir_1.ConfigurationError("Option '--cache' can only be used together with '--project'"); | ||
return result; | ||
@@ -157,0 +164,0 @@ } |
@@ -23,2 +23,3 @@ "use strict"; | ||
...config, | ||
cache: config.cache || undefined, | ||
fix: config.fix || undefined, | ||
@@ -25,0 +26,0 @@ reportUselessDirectives: config.reportUselessDirectives || undefined, |
@@ -13,2 +13,4 @@ "use strict"; | ||
const ymir_1 = require("@fimbul/ymir"); | ||
const program_state_1 = require("../services/program-state"); | ||
const dependency_resolver_1 = require("../services/dependency-resolver"); | ||
function createCoreModule(globalOptions) { | ||
@@ -23,2 +25,4 @@ return new inversify_1.ContainerModule((bind) => { | ||
bind(runner_1.Runner).toSelf(); | ||
bind(program_state_1.ProgramStateFactory).toSelf(); | ||
bind(dependency_resolver_1.DependencyResolverFactory).toSelf(); | ||
bind(ymir_1.GlobalOptions).toConstantValue(globalOptions); | ||
@@ -25,0 +29,0 @@ }); |
@@ -18,2 +18,3 @@ "use strict"; | ||
const file_filter_1 = require("../services/default/file-filter"); | ||
const state_persistence_1 = require("../services/default/state-persistence"); | ||
function createDefaultModule() { | ||
@@ -47,2 +48,4 @@ return new inversify_1.ContainerModule((bind, _unbind, isBound) => { | ||
bind(ymir_1.FileFilterFactory).to(file_filter_1.DefaultFileFilterFactory); | ||
if (!isBound(ymir_1.StatePersistence)) | ||
bind(ymir_1.StatePersistence).to(state_persistence_1.DefaultStatePersistence); | ||
}); | ||
@@ -49,0 +52,0 @@ } |
@@ -27,5 +27,7 @@ import * as ts from 'typescript'; | ||
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; | ||
lintAndFix(file: ts.SourceFile, content: string, config: EffectiveConfiguration, updateFile: UpdateFileCallback, iterations?: number, programFactory?: ProgramFactory, processor?: AbstractProcessor, options?: LinterOptions, | ||
/** Initial set of findings from a cache. If provided, the initial linting is skipped and these findings are used for fixing. */ | ||
findings?: readonly Finding[]): LintAndFixFileResult; | ||
private prepareRules; | ||
private applyRules; | ||
} |
@@ -55,9 +55,10 @@ "use strict"; | ||
} | ||
lintAndFix(file, content, config, updateFile, iterations = 10, programFactory, processor, options = {}) { | ||
lintAndFix(file, content, config, updateFile, iterations = 10, programFactory, processor, options = {}, | ||
/** Initial set of findings from a cache. If provided, the initial linting is skipped and these findings are used for fixing. */ | ||
findings = this.getFindings(file, config, programFactory, processor, options)) { | ||
let totalFixes = 0; | ||
let findings = this.getFindings(file, config, programFactory, processor, options); | ||
for (let i = 0; i < iterations; ++i) { | ||
if (findings.length === 0) | ||
break; | ||
const fixes = findings.map((f) => f.fix).filter((f) => f !== undefined); | ||
const fixes = utils_1.mapDefined(findings, (f) => f.fix); | ||
if (fixes.length === 0) { | ||
@@ -64,0 +65,0 @@ log('No fixes'); |
@@ -12,2 +12,3 @@ "use strict"; | ||
const additionalExtensions = ['.json']; | ||
// @internal | ||
class ProjectHost { | ||
@@ -72,5 +73,5 @@ constructor(cwd, config, fs, configManager, processorLoader) { | ||
for (const entry of entries) { | ||
const fileName = `${dir}/${entry.name}`; | ||
switch (entry.kind) { | ||
case 1 /* File */: { | ||
const fileName = `${dir}/${entry.name}`; | ||
if (!utils_1.hasSupportedExtension(fileName, additionalExtensions)) { | ||
@@ -81,10 +82,10 @@ const c = this.config || this.tryFindConfig(fileName); | ||
const ctor = this.processorLoader.loadProcessor(processor); | ||
const newName = fileName + | ||
ctor.getSuffixForFile({ | ||
fileName, | ||
getSettings: () => this.configManager.getSettings(c, fileName), | ||
readFile: () => this.fs.readFile(fileName), | ||
}); | ||
const suffix = ctor.getSuffixForFile({ | ||
fileName, | ||
getSettings: () => this.configManager.getSettings(c, fileName), | ||
readFile: () => this.fs.readFile(fileName), | ||
}); | ||
const newName = fileName + suffix; | ||
if (utils_1.hasSupportedExtension(newName, additionalExtensions)) { | ||
files.push(newName); | ||
files.push(entry.name + suffix); | ||
this.reverseMap.set(newName, fileName); | ||
@@ -95,3 +96,3 @@ break; | ||
} | ||
files.push(fileName); | ||
files.push(entry.name); | ||
this.files.push(fileName); | ||
@@ -101,3 +102,3 @@ break; | ||
case 2 /* Directory */: | ||
directories.push(fileName); | ||
directories.push(entry.name); | ||
} | ||
@@ -186,4 +187,4 @@ } | ||
if (cached !== undefined) | ||
return cached.directories.map((d) => path.posix.basename(d)); | ||
return utils_1.mapDefined(this.fs.readDirectory(dir), (entry) => entry.kind === 2 /* Directory */ ? path.join(dir, entry.name) : undefined); | ||
return cached.directories.slice(); | ||
return utils_1.mapDefined(this.fs.readDirectory(dir), (entry) => entry.kind === 2 /* Directory */ ? entry.name : undefined); | ||
} | ||
@@ -234,5 +235,5 @@ getSourceFile(fileName, languageVersion) { | ||
} | ||
resolveModuleNames(names, file, _, reference) { | ||
resolveModuleNames(names, file, _, reference, options) { | ||
const seen = new Map(); | ||
const resolve = (name) => ts.resolveModuleName(name, file, this.compilerOptions, this, this.moduleResolutionCache, reference).resolvedModule; | ||
const resolve = (name) => ts.resolveModuleName(name, file, options, this, this.moduleResolutionCache, reference).resolvedModule; | ||
return names.map((name) => utils_1.resolveCachedResult(seen, name, resolve)); | ||
@@ -239,0 +240,0 @@ } |
import { Linter } from './linter'; | ||
import { LintResult, DirectoryService, MessageHandler, FileFilterFactory, Severity } from '@fimbul/ymir'; | ||
import * as ts from 'typescript'; | ||
import { ProcessorLoader } from './services/processor-loader'; | ||
import { CachedFileSystem } from './services/cached-file-system'; | ||
import { ConfigurationManager } from './services/configuration-manager'; | ||
import { ProgramStateFactory } from './services/program-state'; | ||
export interface LintOptions { | ||
@@ -16,2 +16,3 @@ config: string | undefined; | ||
reportUselessDirectives: Severity | boolean | undefined; | ||
cache: boolean; | ||
} | ||
@@ -26,3 +27,4 @@ export declare class Runner { | ||
private filterFactory; | ||
constructor(fs: CachedFileSystem, configManager: ConfigurationManager, linter: Linter, processorLoader: ProcessorLoader, directories: DirectoryService, logger: MessageHandler, filterFactory: FileFilterFactory); | ||
private programStateFactory; | ||
constructor(fs: CachedFileSystem, configManager: ConfigurationManager, linter: Linter, processorLoader: ProcessorLoader, directories: DirectoryService, logger: MessageHandler, filterFactory: FileFilterFactory, programStateFactory: ProgramStateFactory); | ||
lintCollection(options: LintOptions): LintResult; | ||
@@ -35,8 +37,1 @@ private lintProject; | ||
} | ||
declare module 'typescript' { | ||
function matchFiles(path: string, extensions: ReadonlyArray<string>, excludes: ReadonlyArray<string> | undefined, includes: ReadonlyArray<string>, useCaseSensitiveFileNames: boolean, currentDirectory: string, depth: number | undefined, getFileSystemEntries: (path: string) => ts.FileSystemEntries, realpath: (path: string) => string): string[]; | ||
interface FileSystemEntries { | ||
readonly files: ReadonlyArray<string>; | ||
readonly directories: ReadonlyArray<string>; | ||
} | ||
} |
@@ -19,5 +19,7 @@ "use strict"; | ||
const normalize_glob_1 = require("normalize-glob"); | ||
const program_state_1 = require("./services/program-state"); | ||
const config_hash_1 = require("./config-hash"); | ||
const log = debug('wotan:runner'); | ||
let Runner = class Runner { | ||
constructor(fs, configManager, linter, processorLoader, directories, logger, filterFactory) { | ||
constructor(fs, configManager, linter, processorLoader, directories, logger, filterFactory, programStateFactory) { | ||
this.fs = fs; | ||
@@ -30,2 +32,3 @@ this.configManager = configManager; | ||
this.filterFactory = filterFactory; | ||
this.programStateFactory = programStateFactory; | ||
} | ||
@@ -50,3 +53,4 @@ lintCollection(options) { | ||
const processorHost = new project_host_1.ProjectHost(this.directories.getCurrentDirectory(), config, this.fs, this.configManager, this.processorLoader); | ||
for (let { files, program } of this.getFilesAndProgram(options.project, options.files, options.exclude, processorHost, options.references)) { | ||
for (let { files, program, configFilePath: tsconfigPath } of this.getFilesAndProgram(options.project, options.files, options.exclude, processorHost, options.references)) { | ||
const programState = options.cache ? this.programStateFactory.create(program, processorHost, tsconfigPath) : undefined; | ||
let invalidatedProgram = false; | ||
@@ -78,3 +82,6 @@ const factory = { | ||
const fix = shouldFix(sourceFile, options, originalName); | ||
const configHash = programState === undefined ? undefined : config_hash_1.createConfigHash(effectiveConfig, linterOptions); | ||
const resultFromCache = programState === null || programState === void 0 ? void 0 : programState.getUpToDateResult(sourceFile.fileName, configHash); | ||
if (fix) { | ||
let updatedFile = false; | ||
summary = this.linter.lintAndFix(sourceFile, originalContent, effectiveConfig, (content, range) => { | ||
@@ -89,10 +96,17 @@ invalidatedProgram = true; | ||
} | ||
else { | ||
updatedFile = true; | ||
} | ||
// either way we need to store the new SourceFile as the old one is now corrupted | ||
processorHost.updateSourceFile(sourceFile); | ||
return hasErrors ? undefined : sourceFile; | ||
}, fix === true ? undefined : fix, factory, mapped === undefined ? undefined : mapped.processor, linterOptions); | ||
}, fix === true ? undefined : fix, factory, mapped === null || mapped === void 0 ? void 0 : mapped.processor, linterOptions, | ||
// pass cached results so we can apply fixes from cache | ||
resultFromCache); | ||
if (updatedFile) | ||
programState === null || programState === void 0 ? void 0 : programState.update(factory.getProgram(), sourceFile.fileName); | ||
} | ||
else { | ||
summary = { | ||
findings: this.linter.getFindings(sourceFile, effectiveConfig, factory, mapped === undefined ? undefined : mapped.processor, linterOptions), | ||
findings: resultFromCache !== null && resultFromCache !== void 0 ? resultFromCache : this.linter.getFindings(sourceFile, effectiveConfig, factory, mapped === null || mapped === void 0 ? void 0 : mapped.processor, linterOptions), | ||
fixes: 0, | ||
@@ -102,4 +116,7 @@ content: originalContent, | ||
} | ||
if (programState !== undefined && resultFromCache !== summary.findings) | ||
programState.setFileResult(file, configHash, summary.findings); | ||
yield [originalName, summary]; | ||
} | ||
programState === null || programState === void 0 ? void 0 : programState.save(); | ||
} | ||
@@ -204,3 +221,3 @@ } | ||
let filesOfPreviousProject; | ||
for (const program of this.createPrograms(projects, host, projectsSeen, references, isFileIncluded)) { | ||
for (const { program, configFilePath } of this.createPrograms(projects, host, projectsSeen, references, isFileIncluded)) { | ||
const ownFiles = []; | ||
@@ -227,3 +244,3 @@ const files = []; | ||
if (files.length !== 0) | ||
yield { files, program }; | ||
yield { files, program, configFilePath }; | ||
} | ||
@@ -274,3 +291,3 @@ ensurePatternsMatch(nonMagicGlobs, ex, allMatchedFiles, projectsSeen); | ||
const program = host.createProgram(commandLine.fileNames, commandLine.options, undefined, commandLine.projectReferences); | ||
yield program; | ||
yield { program, configFilePath }; | ||
if (references) | ||
@@ -305,3 +322,4 @@ resolvedReferences = program.getResolvedProjectReferences(); | ||
ymir_1.MessageHandler, | ||
ymir_1.FileFilterFactory]) | ||
ymir_1.FileFilterFactory, | ||
program_state_1.ProgramStateFactory]) | ||
], Runner); | ||
@@ -308,0 +326,0 @@ exports.Runner = Runner; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.getOutputFileNamesOfProjectReference = exports.iterateProjectReferences = exports.invertChangeRange = exports.hasParseErrors = exports.addUnique = exports.flatMap = exports.mapDefined = exports.hasSupportedExtension = exports.calculateChangeRange = exports.assertNever = exports.format = exports.unixifyPath = exports.resolveCachedResult = exports.arrayify = exports.emptyArray = exports.OFFSET_TO_NODE_MODULES = void 0; | ||
exports.djb2 = exports.getOutputFileNamesOfProjectReference = exports.iterateProjectReferences = exports.invertChangeRange = exports.hasParseErrors = exports.addUnique = exports.flatMap = exports.mapDefined = exports.hasSupportedExtension = exports.calculateChangeRange = exports.assertNever = exports.format = exports.unixifyPath = exports.resolveCachedResult = exports.arrayify = exports.emptyArray = exports.OFFSET_TO_NODE_MODULES = void 0; | ||
const json5 = require("json5"); | ||
@@ -201,2 +201,9 @@ const yaml = require("js-yaml"); | ||
} | ||
function djb2(str) { | ||
let hash = 5381; | ||
for (let i = 0; i < str.length; ++i) | ||
hash = ((hash << 5) + hash) + str.charCodeAt(i); | ||
return hash; | ||
} | ||
exports.djb2 = djb2; | ||
//# sourceMappingURL=utils.js.map |
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
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
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
412745
117
5176
288
10
+ Added@fimbul/mimir@0.24.0-dev.20210214(transitive)
+ Added@fimbul/ymir@0.24.0-dev.20210214(transitive)
- Removed@fimbul/mimir@0.24.0-dev.20210131(transitive)
- Removed@fimbul/ymir@0.22.0(transitive)
Updatedtsutils@^3.20.0