diff --git a/src/project.ts b/src/project.ts index 2ad9e25a..2a530c3e 100644 --- a/src/project.ts +++ b/src/project.ts @@ -9,6 +9,8 @@ import { isRootStartingWithFilePath, getDepIfExists, getPackageJSON, + cached, + PackageInfo, } from './utils/layout-helpers'; import Server from './server'; import { Diagnostic, FileChangeType } from 'vscode-languageserver/node'; @@ -39,6 +41,7 @@ export class Project { initIssues: Error[] = []; files: Map = new Map(); podModulePrefix = ''; + @cached get roots() { const mainRoot = this.root; const otherRoots = this.addonsMeta.map((meta) => meta.root); @@ -105,7 +108,8 @@ export class Project { addWatcher(cb: Watcher) { this.watchers.push(cb); } - get packageJSON() { + @cached + get packageJSON(): PackageInfo { return getPackageJSON(this.root); } get name() { diff --git a/src/server.ts b/src/server.ts index e096640f..5b2bcae9 100644 --- a/src/server.ts +++ b/src/server.ts @@ -54,6 +54,7 @@ import { Usage, findRelatedFiles } from './utils/usages-api'; import { URI } from 'vscode-uri'; import { MatchResultType } from './utils/path-matcher'; import { FileChangeType } from 'vscode-languageserver/node'; +import { debounce } from 'lodash'; export default class Server { initializers: any[] = []; @@ -136,10 +137,16 @@ export default class Server { this.connection.workspace.onDidChangeWorkspaceFolders(this.onDidChangeWorkspaceFolders.bind(this)); } - this.executors['els.setConfig'] = async (_, __, [config]: [{ local: { addons: string[]; ignoredProjects: string[] } }]) => { + this.executors['els.setConfig'] = async (_, __, [config]: [{ local: { addons: string[]; ignoredProjects: string[]; useBuiltinLinting: boolean } }]) => { this.projectRoots.setLocalAddons(config.local.addons); this.projectRoots.setIgnoredProjects(config.local.ignoredProjects); + if (config.local.useBuiltinLinting === false) { + this.templateLinter.disable(); + } else if (config.local.useBuiltinLinting === true) { + this.templateLinter.enable(); + } + if (this.lazyInit) { this.executeInitializers(); } @@ -264,11 +271,14 @@ export default class Server { this.documents.listen(this.connection); + this.onDidChangeContent = this.onDidChangeContent.bind(this); + this._onDidChangeContent = this._onDidChangeContent.bind(this); + // Bind event handlers this.connection.onInitialize(this.onInitialize.bind(this)); this.connection.onInitialized(this.onInitialized.bind(this)); - this.documents.onDidChangeContent(this.onDidChangeContent.bind(this)); - this.documents.onDidOpen(this.onDidChangeContent.bind(this)); + this.documents.onDidChangeContent(this.onDidChangeContent); + this.documents.onDidOpen(this.onDidChangeContent); this.connection.onDidChangeWatchedFiles(this.onDidChangeWatchedFiles.bind(this)); this.connection.onDocumentSymbol(this.onDocumentSymbol.bind(this)); this.connection.onDefinition(this.definitionProvider.handler); @@ -443,23 +453,32 @@ export default class Server { return results; } + lastChangeEvent!: TextDocumentChangeEvent; + private async onDidChangeContent(change: TextDocumentChangeEvent) { + this.lastChangeEvent = change; + debounce(this._onDidChangeContent, 250); + } + + private async _onDidChangeContent() { // this.setStatusText('did-change'); + const change = this.lastChangeEvent; const lintResults = await this.templateLinter.lint(change.document); - const results: Diagnostic[] = []; + + if (change !== this.lastChangeEvent) { + return; + } if (Array.isArray(lintResults)) { - lintResults.forEach((result) => { - results.push(result); - }); + this.connection.sendDiagnostics({ version: change.document.version, uri: change.document.uri, diagnostics: lintResults }); } const addonResults = await this.runAddonLinters(change.document); - addonResults.forEach((result) => { - results.push(result); - }); + if (change !== this.lastChangeEvent) { + return; + } const project = this.projectRoots.projectForUri(change.document.uri); @@ -467,7 +486,7 @@ export default class Server { project.trackChange(change.document.uri, FileChangeType.Changed); } - this.connection.sendDiagnostics({ uri: change.document.uri, diagnostics: results }); + this.connection.sendDiagnostics({ version: change.document.version, uri: change.document.uri, diagnostics: addonResults }); } private onDidChangeWatchedFiles(items: DidChangeWatchedFilesParams) { diff --git a/src/template-linter.ts b/src/template-linter.ts index 0acfa159..f65aafe3 100644 --- a/src/template-linter.ts +++ b/src/template-linter.ts @@ -37,9 +37,18 @@ function setCwd(cwd: string) { export default class TemplateLinter { private _linterCache = new Map(); + private _isEnabled = true; constructor(private server: Server) {} + disable() { + this._isEnabled = false; + } + + enable() { + this._isEnabled = true; + } + private getProjectForDocument(textDocument: TextDocument) { const ext = getExtension(textDocument); @@ -81,6 +90,10 @@ export default class TemplateLinter { } } async lint(textDocument: TextDocument): Promise { + if (this._isEnabled === false) { + return; + } + const cwd = process.cwd(); const project = this.getProjectForDocument(textDocument); diff --git a/src/utils/addon-api.ts b/src/utils/addon-api.ts index 5fef2603..5cb02109 100644 --- a/src/utils/addon-api.ts +++ b/src/utils/addon-api.ts @@ -6,6 +6,7 @@ import { PackageInfo, ADDON_CONFIG_KEY, hasEmberLanguageServerExtension, + addonVersion, } from './layout-helpers'; import { TextDocument } from 'vscode-languageserver-textdocument'; import * as path from 'path'; @@ -196,6 +197,7 @@ export function collectProjectProviders(root: string, addons: string[]): Project addonsMeta.push({ name: info.name, root: packagePath, + version: addonVersion(info), }); } @@ -224,7 +226,7 @@ export function collectProjectProviders(root: string, addons: string[]): Project codeActionProviders: CodeActionResolveFunction[]; initFunctions: InitFunction[]; info: string[]; - addonsMeta: { name: string; root: string }[]; + addonsMeta: AddonMeta[]; } = { definitionProviders: [], referencesProviders: [], @@ -319,7 +321,7 @@ export function collectProjectProviders(root: string, addons: string[]): Project return result; } -export type AddonMeta = { root: string; name: string }; +export type AddonMeta = { root: string; name: string; version: null | 1 | 2 }; export type DependencyMeta = { name: string; version: string }; export interface ProjectProviders { definitionProviders: DefinitionResolveFunction[]; diff --git a/src/utils/layout-helpers.ts b/src/utils/layout-helpers.ts index b50d8321..1159ecbe 100644 --- a/src/utils/layout-helpers.ts +++ b/src/utils/layout-helpers.ts @@ -158,6 +158,26 @@ export function isELSAddonRoot(root: string) { return hasEmberLanguageServerExtension(pack); } +export function cached(_proto: unknown, prop: string, desc: PropertyDescriptor) { + const values = new WeakMap(); + + return { + get() { + if (!values.has(this)) { + values.set(this, {}); + } + + const objects = values.get(this); + + if (!(prop in objects)) { + objects[prop] = desc.get?.call(this); + } + + return objects[prop]; + }, + }; +} + function getRecursiveInRepoAddonRoots(root: string, roots: string[]) { const packageData = getPackageJSON(root); const emberAddonPaths: string[] = (packageData['ember-addon'] && packageData['ember-addon'].paths) || []; @@ -384,7 +404,7 @@ export function isEmberAddon(info: PackageInfo) { return info.keywords && info.keywords.includes('ember-addon'); } -function addonVersion(info: PackageInfo) { +export function addonVersion(info: PackageInfo) { if (!isEmberAddon(info)) { return null; } diff --git a/test/__snapshots__/integration-test.ts.snap b/test/__snapshots__/integration-test.ts.snap index 7a9a0cb6..863629ae 100644 --- a/test/__snapshots__/integration-test.ts.snap +++ b/test/__snapshots__/integration-test.ts.snap @@ -6,18 +6,22 @@ Object { Object { "name": "addon1", "root": "node_modules/addon1", + "version": null, }, Object { "name": "addon2", "root": "node_modules/addon2", + "version": null, }, Object { "name": "addon3", "root": "node_modules/addon3", + "version": null, }, Object { "name": "addon4", "root": "node_modules/addon4", + "version": null, }, ], "registry": Object { @@ -87,6 +91,7 @@ Object { Object { "name": "provider", "root": "node_modules/provider", + "version": null, }, ], "registry": Object { @@ -110,6 +115,7 @@ Object { Object { "name": "provider", "root": "node_modules/provider", + "version": null, }, ], "registry": Object { @@ -146,6 +152,7 @@ Object { Object { "name": "provider", "root": "node_modules/provider", + "version": null, }, ], "registry": Object { @@ -179,6 +186,7 @@ Object { Object { "name": "provider", "root": "node_modules/provider", + "version": null, }, ], "registry": Object { @@ -212,6 +220,7 @@ Object { Object { "name": "provider", "root": "node_modules/provider", + "version": null, }, ], "registry": Object { @@ -317,10 +326,12 @@ Object { Object { "name": "biz", "root": "lib/biz", + "version": 1, }, Object { "name": "foo", "root": "../lib/foo", + "version": 1, }, ], "registry": Object { @@ -743,6 +754,7 @@ Object { Object { "name": "provider", "root": "node_modules/provider", + "version": null, }, ], "registry": Object { @@ -776,6 +788,7 @@ Object { Object { "name": "provider", "root": "node_modules/provider", + "version": null, }, ], "registry": Object { @@ -2378,6 +2391,7 @@ Object { Object { "name": "biz", "root": "lib/biz", + "version": 1, }, ], "registry": Object { @@ -2417,6 +2431,7 @@ Object { Object { "name": "biz", "root": "lib/biz", + "version": 1, }, ], "registry": Object { @@ -2804,10 +2819,12 @@ Object { Object { "name": "biz", "root": "lib/biz", + "version": 1, }, Object { "name": "foo", "root": "../lib/foo", + "version": 1, }, ], "registry": Object { diff --git a/tsconfig.json b/tsconfig.json index 127b4431..99c9d272 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "experimentalDecorators": true, "target": "ES2018", "module": "commonjs", "allowSyntheticDefaultImports": true,