@fimbul/wotan
Advanced tools
Comparing version 0.24.0-dev.20210214 to 0.24.0-dev.20210216
@@ -5,2 +5,3 @@ export * from '@fimbul/ymir'; | ||
export * from './src/services/default/configuration-provider'; | ||
export * from './src/services/default/content-hasher'; | ||
export * from './src/services/default/deprecation-handler'; | ||
@@ -7,0 +8,0 @@ export * from './src/services/default/directory-service'; |
@@ -9,2 +9,3 @@ "use strict"; | ||
tslib_1.__exportStar(require("./src/services/default/configuration-provider"), exports); | ||
tslib_1.__exportStar(require("./src/services/default/content-hasher"), exports); | ||
tslib_1.__exportStar(require("./src/services/default/deprecation-handler"), exports); | ||
@@ -11,0 +12,0 @@ tslib_1.__exportStar(require("./src/services/default/directory-service"), exports); |
@@ -14,2 +14,3 @@ import * as ts from 'typescript'; | ||
private findingsForFile; | ||
private oldState; | ||
getExternalFiles?: () => string[]; | ||
@@ -22,3 +23,4 @@ constructor(config: Record<string, unknown>, project: import('typescript/lib/tsserverlibrary').server.Project, serverHost: import('typescript/lib/tsserverlibrary').server.ServerHost, languageService: ts.LanguageService, require: (id: string) => {}, log: (message: string) => void); | ||
getSupportedCodeFixes(fixes: string[]): string[]; | ||
cleanupSemanticCache(): void; | ||
dispose(): void; | ||
} |
@@ -19,2 +19,4 @@ "use strict"; | ||
const minimatch_1 = require("minimatch"); | ||
const program_state_1 = require("../src/services/program-state"); | ||
const config_hash_1 = require("../src/config-hash"); | ||
exports.version = '1'; | ||
@@ -34,2 +36,3 @@ class LanguageServiceInterceptor { | ||
this.findingsForFile = new WeakMap(); | ||
this.oldState = undefined; | ||
} | ||
@@ -41,27 +44,28 @@ updateConfig(config) { | ||
const program = this.languageService.getProgram(); | ||
if (program === undefined) | ||
return diagnostics; | ||
const file = program.getSourceFile(fileName); | ||
if (file === undefined) { | ||
this.log(`file ${fileName} is not included in the Program`); | ||
return diagnostics; | ||
} | ||
else { | ||
this.log(`started linting ${fileName}`); | ||
const findings = this.getFindings(file, program); | ||
this.findingsForFile.set(file, findings); | ||
diagnostics = diagnostics.concat(findings.map((finding) => ({ | ||
file, | ||
category: finding.severity === 'error' | ||
? this.config.displayErrorsAsWarnings | ||
? ts.DiagnosticCategory.Warning | ||
: ts.DiagnosticCategory.Error | ||
: finding.severity === 'warning' | ||
? ts.DiagnosticCategory.Warning | ||
: ts.DiagnosticCategory.Suggestion, | ||
code: finding.ruleName, | ||
source: 'wotan', | ||
messageText: finding.message, | ||
start: finding.start.position, | ||
length: finding.end.position - finding.start.position, | ||
}))); | ||
this.log(`finished linting ${fileName} with ${findings.length} findings`); | ||
} | ||
this.log(`started linting ${fileName}`); | ||
const findings = this.getFindings(file, program); | ||
this.findingsForFile.set(file, findings); | ||
diagnostics = diagnostics.concat(findings.map((finding) => ({ | ||
file, | ||
category: finding.severity === 'error' | ||
? this.config.displayErrorsAsWarnings | ||
? ts.DiagnosticCategory.Warning | ||
: ts.DiagnosticCategory.Error | ||
: finding.severity === 'warning' | ||
? ts.DiagnosticCategory.Warning | ||
: ts.DiagnosticCategory.Suggestion, | ||
code: finding.ruleName, | ||
source: 'wotan', | ||
messageText: finding.message, | ||
start: finding.start.position, | ||
length: finding.end.position - finding.start.position, | ||
}))); | ||
this.log(`finished linting ${fileName} with ${findings.length} findings`); | ||
return diagnostics; | ||
@@ -93,2 +97,9 @@ } | ||
container.load(this.loadPluginModule(module, globalConfigDir, globalOptions)); | ||
container.bind(ymir_1.StatePersistence).toConstantValue({ | ||
loadState: () => this.oldState, | ||
saveState: (_, state) => this.oldState = state, | ||
}); | ||
container.bind(ymir_1.ContentId).toConstantValue({ | ||
forFile: (fileName) => this.project.getScriptVersion(fileName), | ||
}); | ||
container.bind(ymir_1.FileSystem).toConstantValue(new ProjectFileSystem(this.project)); | ||
@@ -138,4 +149,3 @@ container.bind(ymir_1.DirectoryService).toConstantValue({ | ||
return []; | ||
const linter = container.get(linter_1.Linter); | ||
return linter.lintFile(file, effectiveConfig, program, { | ||
const linterOptions = { | ||
reportUselessDirectives: globalConfig.reportUselessDirectives | ||
@@ -146,3 +156,15 @@ ? globalConfig.reportUselessDirectives === true | ||
: undefined, | ||
}); | ||
}; | ||
const programState = container.get(program_state_1.ProgramStateFactory).create(program, this.project, this.project.projectName); | ||
const configHash = config_hash_1.createConfigHash(effectiveConfig, linterOptions); | ||
const cached = programState.getUpToDateResult(file.fileName, configHash); | ||
if (cached !== undefined) { | ||
this.log('Using cached results'); | ||
return cached; | ||
} | ||
const linter = container.get(linter_1.Linter); | ||
const result = linter.lintFile(file, effectiveConfig, program, linterOptions); | ||
programState.setFileResult(file.fileName, configHash, result); | ||
programState.save(); | ||
return result; | ||
} | ||
@@ -164,4 +186,7 @@ loadPluginModule(moduleName, basedir, options) { | ||
} | ||
cleanupSemanticCache() { | ||
this.findingsForFile = new WeakMap(); | ||
this.oldState = undefined; | ||
} | ||
dispose() { | ||
// TODO clean up after ourselves | ||
return this.languageService.dispose(); | ||
@@ -168,0 +193,0 @@ } |
{ | ||
"name": "@fimbul/wotan", | ||
"version": "0.24.0-dev.20210214", | ||
"version": "0.24.0-dev.20210216", | ||
"description": "Pluggable TypeScript and JavaScript linter", | ||
@@ -44,4 +44,4 @@ "bin": "bin/main.js", | ||
"dependencies": { | ||
"@fimbul/mimir": "0.24.0-dev.20210214", | ||
"@fimbul/ymir": "0.24.0-dev.20210214", | ||
"@fimbul/mimir": "0.24.0-dev.20210216", | ||
"@fimbul/ymir": "0.24.0-dev.20210216", | ||
"bind-decorator": "^1.0.11", | ||
@@ -48,0 +48,0 @@ "chalk": "^4.0.0", |
@@ -19,2 +19,3 @@ "use strict"; | ||
const state_persistence_1 = require("../services/default/state-persistence"); | ||
const content_hasher_1 = require("../services/default/content-hasher"); | ||
function createDefaultModule() { | ||
@@ -50,2 +51,4 @@ return new inversify_1.ContainerModule((bind, _unbind, isBound) => { | ||
bind(ymir_1.StatePersistence).to(state_persistence_1.DefaultStatePersistence); | ||
if (!isBound(ymir_1.ContentId)) | ||
bind(ymir_1.ContentId).to(content_hasher_1.ContentHasher); | ||
}); | ||
@@ -52,0 +55,0 @@ } |
@@ -92,4 +92,6 @@ "use strict"; | ||
var _a; | ||
const result = new Map(); | ||
if (this.program.getSourceFile(file) === undefined) | ||
return result; | ||
(_a = this.state) !== null && _a !== void 0 ? _a : (this.state = this.buildState()); | ||
const result = new Map(); | ||
{ | ||
@@ -96,0 +98,0 @@ const augmentations = this.state.moduleAugmentations.get(file); |
import * as ts from 'typescript'; | ||
import { DependencyResolver, DependencyResolverFactory, DependencyResolverHost } from './dependency-resolver'; | ||
import { Finding, StatePersistence } from '@fimbul/ymir'; | ||
import { ContentId, Finding, StatePersistence } from '@fimbul/ymir'; | ||
export interface ProgramState { | ||
@@ -13,3 +13,4 @@ update(program: ts.Program, updatedFile: string): void; | ||
private statePersistence; | ||
constructor(resolverFactory: DependencyResolverFactory, statePersistence: StatePersistence); | ||
private contentId; | ||
constructor(resolverFactory: DependencyResolverFactory, statePersistence: StatePersistence, contentId: ContentId); | ||
create(program: ts.Program, host: ProgramStateHost & DependencyResolverHost, tsconfigPath: string): ProgramStateImpl; | ||
@@ -24,10 +25,10 @@ } | ||
private statePersistence; | ||
private contentId; | ||
private project; | ||
private projectDirectory; | ||
private caseSensitive; | ||
private getCanonicalFileName; | ||
private canonicalProjectDirectory; | ||
private optionsHash; | ||
private assumeChangesOnlyAffectDirectDependencies; | ||
private fileHashes; | ||
private contentIds; | ||
private fileResults; | ||
@@ -38,8 +39,9 @@ private relativePathNames; | ||
private dependenciesUpToDate; | ||
constructor(host: ProgramStateHost, program: ts.Program, resolver: DependencyResolver, statePersistence: StatePersistence, project: string); | ||
private contentIdHost; | ||
constructor(host: ProgramStateHost, program: ts.Program, resolver: DependencyResolver, statePersistence: StatePersistence, contentId: ContentId, project: string); | ||
/** get old state if global files didn't change */ | ||
private tryReuseOldState; | ||
update(program: ts.Program, updatedFile: string): void; | ||
private getFileHash; | ||
private computeFileHash; | ||
private getContentId; | ||
private computeContentId; | ||
private getRelativePath; | ||
@@ -53,3 +55,3 @@ private makeRelativePath; | ||
private aggregate; | ||
private sortByHash; | ||
private sortById; | ||
private lookupFileIndex; | ||
@@ -56,0 +58,0 @@ private remapFileNames; |
@@ -16,8 +16,9 @@ "use strict"; | ||
let ProgramStateFactory = class ProgramStateFactory { | ||
constructor(resolverFactory, statePersistence) { | ||
constructor(resolverFactory, statePersistence, contentId) { | ||
this.resolverFactory = resolverFactory; | ||
this.statePersistence = statePersistence; | ||
this.contentId = contentId; | ||
} | ||
create(program, host, tsconfigPath) { | ||
return new ProgramStateImpl(host, program, this.resolverFactory.create(host, program), this.statePersistence, tsconfigPath); | ||
return new ProgramStateImpl(host, program, this.resolverFactory.create(host, program), this.statePersistence, this.contentId, tsconfigPath); | ||
} | ||
@@ -27,3 +28,5 @@ }; | ||
inversify_1.injectable(), | ||
tslib_1.__metadata("design:paramtypes", [dependency_resolver_1.DependencyResolverFactory, ymir_1.StatePersistence]) | ||
tslib_1.__metadata("design:paramtypes", [dependency_resolver_1.DependencyResolverFactory, | ||
ymir_1.StatePersistence, | ||
ymir_1.ContentId]) | ||
], ProgramStateFactory); | ||
@@ -34,3 +37,3 @@ exports.ProgramStateFactory = ProgramStateFactory; | ||
class ProgramStateImpl { | ||
constructor(host, program, resolver, statePersistence, project) { | ||
constructor(host, program, resolver, statePersistence, contentId, project) { | ||
this.host = host; | ||
@@ -40,13 +43,17 @@ this.program = program; | ||
this.statePersistence = statePersistence; | ||
this.contentId = contentId; | ||
this.project = project; | ||
this.projectDirectory = utils_1.unixifyPath(path.dirname(this.project)); | ||
this.caseSensitive = this.host.useCaseSensitiveFileNames(); | ||
this.getCanonicalFileName = this.caseSensitive ? (f) => f : (f) => f.toLowerCase(); | ||
this.canonicalProjectDirectory = this.getCanonicalFileName(this.projectDirectory); | ||
this.canonicalProjectDirectory = this.caseSensitive ? this.projectDirectory : this.projectDirectory.toLowerCase(); | ||
this.optionsHash = computeCompilerOptionsHash(this.program.getCompilerOptions(), this.projectDirectory); | ||
this.assumeChangesOnlyAffectDirectDependencies = tsutils_1.isCompilerOptionEnabled(this.program.getCompilerOptions(), 'assumeChangesOnlyAffectDirectDependencies'); | ||
this.fileHashes = new Map(); | ||
this.contentIds = new Map(); | ||
this.fileResults = new Map(); | ||
this.relativePathNames = new Map(); | ||
this.recheckOldState = true; | ||
// TODO this can be removed once ProjectHost correctly reflects applied fixed in readFile | ||
this.contentIdHost = { | ||
readFile: (f) => { var _a; return (_a = this.program.getSourceFile(f)) === null || _a === void 0 ? void 0 : _a.text; }, | ||
}; | ||
const oldState = this.statePersistence.loadState(project); | ||
@@ -70,8 +77,8 @@ if ((oldState === null || oldState === void 0 ? void 0 : oldState.v) !== STATE_VERSION || oldState.ts !== ts.version || oldState.options !== this.optionsHash) { | ||
return this[oldStateSymbol] = undefined; | ||
const globalFilesWithHash = this.sortByHash(filesAffectingGlobalScope); | ||
for (let i = 0; i < globalFilesWithHash.length; ++i) { | ||
const globalFilesWithId = this.sortById(filesAffectingGlobalScope); | ||
for (let i = 0; i < globalFilesWithId.length; ++i) { | ||
const index = oldState.global[i]; | ||
if (globalFilesWithHash[i].hash !== oldState.files[index].hash || | ||
if (globalFilesWithId[i].id !== oldState.files[index].id || | ||
!this.assumeChangesOnlyAffectDirectDependencies && | ||
!this.fileDependenciesUpToDate(globalFilesWithHash[i].fileName, index, oldState)) | ||
!this.fileDependenciesUpToDate(globalFilesWithId[i].fileName, index, oldState)) | ||
return this[oldStateSymbol] = undefined; | ||
@@ -85,12 +92,11 @@ } | ||
this.resolver.update(program, updatedFile); | ||
this.fileHashes.delete(updatedFile); | ||
this.contentIds.delete(updatedFile); | ||
this.recheckOldState = true; | ||
this.dependenciesUpToDate.fill(0 /* Unknown */); | ||
} | ||
getFileHash(file) { | ||
// TODO move hashing to a separate service | ||
return utils_1.resolveCachedResult(this.fileHashes, file, this.computeFileHash); | ||
getContentId(file) { | ||
return utils_1.resolveCachedResult(this.contentIds, file, this.computeContentId); | ||
} | ||
computeFileHash(file) { | ||
return '' + utils_1.djb2(this.program.getSourceFile(file).text); | ||
computeContentId(file) { | ||
return this.contentId.forFile(file, this.contentIdHost); | ||
} | ||
@@ -101,3 +107,3 @@ getRelativePath(fileName) { | ||
makeRelativePath(fileName) { | ||
return utils_1.unixifyPath(path.relative(this.canonicalProjectDirectory, this.getCanonicalFileName(fileName))); | ||
return utils_1.unixifyPath(path.relative(this.canonicalProjectDirectory, this.caseSensitive ? fileName : fileName.toLowerCase())); | ||
} | ||
@@ -114,3 +120,3 @@ getUpToDateResult(fileName, configHash) { | ||
old.config !== configHash || | ||
old.hash !== this.getFileHash(fileName) || | ||
old.id !== this.getContentId(fileName) || | ||
!this.fileDependenciesUpToDate(fileName, index, oldState)) | ||
@@ -139,3 +145,3 @@ return; | ||
const index = this.lookupFileIndex(fileName, oldState); | ||
if (index === undefined || oldState.files[index].hash !== this.getFileHash(fileName)) | ||
if (index === undefined || oldState.files[index].id !== this.getContentId(fileName)) | ||
return false; | ||
@@ -203,12 +209,12 @@ switch (this.dependenciesUpToDate[index]) { | ||
return markAsOutdated(parents, index, cycles, this.dependenciesUpToDate); | ||
const newDepsWithHash = this.sortByHash(newDeps); | ||
for (let i = 0; i < newDepsWithHash.length; ++i) { | ||
const newDepsWithId = this.sortById(newDeps); | ||
for (let i = 0; i < newDepsWithId.length; ++i) { | ||
const oldDepState = oldState.files[oldDeps[i]]; | ||
if (newDepsWithHash[i].hash !== oldDepState.hash) | ||
if (newDepsWithId[i].id !== oldDepState.id) | ||
return markAsOutdated(parents, index, cycles, this.dependenciesUpToDate); | ||
if (!this.assumeChangesOnlyAffectDirectDependencies && fileName !== newDepsWithHash[i].fileName) { | ||
if (!this.assumeChangesOnlyAffectDirectDependencies && fileName !== newDepsWithId[i].fileName) { | ||
const indexInQueue = parents.indexOf(oldDeps[i]); | ||
if (indexInQueue === -1) { | ||
// no circular dependency | ||
fileNameQueue.push(newDepsWithHash[i].fileName); | ||
fileNameQueue.push(newDepsWithId[i].fileName); | ||
indexQueue.push(oldDeps[i]); | ||
@@ -277,5 +283,16 @@ ++childCount; | ||
aggregate() { | ||
const additionalFiles = new Set(); | ||
const oldState = this.tryReuseOldState(); | ||
const sourceFiles = this.program.getSourceFiles(); | ||
const lookup = {}; | ||
const mapToIndex = ({ fileName }) => lookup[this.relativePathNames.get(fileName)]; | ||
const mapToIndex = ({ fileName }) => { | ||
const relativeName = this.getRelativePath(fileName); | ||
let index = lookup[relativeName]; | ||
if (index === undefined) { | ||
index = sourceFiles.length + additionalFiles.size; | ||
additionalFiles.add(fileName); | ||
lookup[relativeName] = index; | ||
} | ||
return index; | ||
}; | ||
const mapDependencies = (dependencies) => { | ||
@@ -288,7 +305,6 @@ if (dependencies.size === 0) | ||
? null | ||
: this.sortByHash(Array.from(new Set(f))).map(mapToIndex); | ||
: this.sortById(Array.from(new Set(f))).map(mapToIndex); | ||
return result; | ||
}; | ||
const files = []; | ||
const sourceFiles = this.program.getSourceFiles(); | ||
for (let i = 0; i < sourceFiles.length; ++i) | ||
@@ -312,6 +328,8 @@ lookup[this.getRelativePath(sourceFiles[i].fileName)] = i; | ||
...results, | ||
hash: this.getFileHash(file.fileName), | ||
id: this.getContentId(file.fileName), | ||
dependencies: mapDependencies(this.resolver.getDependencies(file.fileName)), | ||
}); | ||
} | ||
for (const additional of additionalFiles) | ||
files.push({ id: this.getContentId(additional) }); | ||
return { | ||
@@ -323,10 +341,10 @@ files, | ||
cs: this.caseSensitive, | ||
global: this.sortByHash(this.resolver.getFilesAffectingGlobalScope()).map(mapToIndex), | ||
global: this.sortById(this.resolver.getFilesAffectingGlobalScope()).map(mapToIndex), | ||
options: this.optionsHash, | ||
}; | ||
} | ||
sortByHash(fileNames) { | ||
sortById(fileNames) { | ||
return fileNames | ||
.map((f) => ({ fileName: f, hash: this.getFileHash(f) })) | ||
.sort(compareHashKey); | ||
.map((f) => ({ fileName: f, id: this.getContentId(f) })) | ||
.sort(compareId); | ||
} | ||
@@ -354,3 +372,3 @@ lookupFileIndex(fileName, oldState) { | ||
tslib_1.__metadata("design:returntype", void 0) | ||
], ProgramStateImpl.prototype, "computeFileHash", null); | ||
], ProgramStateImpl.prototype, "computeContentId", null); | ||
tslib_1.__decorate([ | ||
@@ -419,4 +437,4 @@ bind_decorator_1.default, | ||
} | ||
function compareHashKey(a, b) { | ||
return +(a.hash >= b.hash) - +(a.hash <= b.hash); | ||
function compareId(a, b) { | ||
return +(a.id >= b.id) - +(a.id <= b.id); | ||
} | ||
@@ -423,0 +441,0 @@ const compilerOptionKinds = { |
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
418268
120
5250
+ Added@fimbul/mimir@0.24.0-dev.20210216(transitive)
+ Added@fimbul/ymir@0.24.0-dev.20210216(transitive)
- Removed@fimbul/mimir@0.24.0-dev.20210214(transitive)
- Removed@fimbul/ymir@0.24.0-dev.20210214(transitive)