diff --git a/ember-language-server.code-workspace b/ember-language-server.code-workspace new file mode 100644 index 00000000..1e5e53c6 --- /dev/null +++ b/ember-language-server.code-workspace @@ -0,0 +1,11 @@ +{ + "folders": [ + { + "path": "." + }, + { + "path": "../vscode-ember" + } + ], + "settings": {} +} \ No newline at end of file diff --git a/src/builtin-addons/core/script-completion-provider.ts b/src/builtin-addons/core/script-completion-provider.ts index 657be868..bfbefc16 100644 --- a/src/builtin-addons/core/script-completion-provider.ts +++ b/src/builtin-addons/core/script-completion-provider.ts @@ -84,7 +84,7 @@ export default class ScriptCompletionProvider { } if (!this.meta.projectAddonsInfoInitialized) { - await mGetProjectAddonsInfo(root); + await mGetProjectAddonsInfo(root, this.project.dependencyMap); this.enableRegistryCache('projectAddonsInfoInitialized'); this.project.invalidateRegistry(); } @@ -107,7 +107,7 @@ export default class ScriptCompletionProvider { } if (!this.meta.projectAddonsInfoInitialized) { - await mGetProjectAddonsInfo(root); + await mGetProjectAddonsInfo(root, this.project.dependencyMap); this.enableRegistryCache('projectAddonsInfoInitialized'); this.project.invalidateRegistry(); } @@ -130,7 +130,7 @@ export default class ScriptCompletionProvider { } if (!this.meta.projectAddonsInfoInitialized) { - await mGetProjectAddonsInfo(root); + await mGetProjectAddonsInfo(root, this.project.dependencyMap); this.enableRegistryCache('projectAddonsInfoInitialized'); this.project.invalidateRegistry(); } @@ -185,7 +185,7 @@ export default class ScriptCompletionProvider { } if (!this.meta.projectAddonsInfoInitialized) { - await mGetProjectAddonsInfo(root); + await mGetProjectAddonsInfo(root, this.project.dependencyMap); this.enableRegistryCache('projectAddonsInfoInitialized'); this.project.invalidateRegistry(); } diff --git a/src/builtin-addons/core/script-definition-provider.ts b/src/builtin-addons/core/script-definition-provider.ts index 295b901a..e518dafb 100644 --- a/src/builtin-addons/core/script-definition-provider.ts +++ b/src/builtin-addons/core/script-definition-provider.ts @@ -29,6 +29,7 @@ type ItemType = 'Model' | 'Transform' | 'Service'; // eslint-disable-line type LayoutCollectorFn = (root: string, itemName: string, podModulePrefix?: string) => string[]; type AsyncLayoutCollectorFn = (root: string, itemName: string, podModulePrefix?: string) => Promise; +type ProjectAwareCollectionFn = (project: Project, itemName: string, podModulePrefix?: string) => Promise; function joinPaths(...args: string[]) { return ['.ts', '.js'].map((extName: string) => { @@ -40,7 +41,8 @@ function joinPaths(...args: string[]) { } class PathResolvers { - [key: string]: LayoutCollectorFn | AsyncLayoutCollectorFn; + [key: string]: LayoutCollectorFn | AsyncLayoutCollectorFn | ProjectAwareCollectionFn; + muModelPaths(root: string, modelName: string) { return joinPaths(root, 'src', 'data', 'models', modelName, 'model'); } @@ -68,11 +70,11 @@ class PathResolvers { podServicePaths(root: string, modelName: string, podPrefix: string) { return joinPaths(root, 'app', podPrefix, modelName, 'service'); } - async addonServicePaths(root: string, serviceName: string): Promise { - return await getAddonPathsForType(root, 'services', serviceName); + async addonServicePaths(project: Project, serviceName: string): Promise { + return await getAddonPathsForType(project, 'services', serviceName); } - async addonImportPaths(root: string, pathName: string) { - return await getAddonImport(root, pathName); + async addonImportPaths(project: Project, pathName: string) { + return await getAddonImport(project, pathName); } classicImportPaths(root: string, pathName: string) { const pathParts = pathName.split('/'); @@ -123,7 +125,7 @@ export default class CoreScriptDefinitionProvider { guessedPaths.push(pathLocation); }); - const addonImports = await this.resolvers.addonImportPaths(root, importPath); + const addonImports = await this.resolvers.addonImportPaths(this.project, importPath); addonImports.forEach((pathLocation: string) => { guessedPaths.push(pathLocation); @@ -148,7 +150,7 @@ export default class CoreScriptDefinitionProvider { } if (fnName === 'Service') { - const paths = await this.resolvers.addonServicePaths(root, typeName); + const paths = await this.resolvers.addonServicePaths(this.project, typeName); paths.forEach((item: string) => { guessedPaths.push(item); diff --git a/src/builtin-addons/core/template-completion-provider.ts b/src/builtin-addons/core/template-completion-provider.ts index 9231078a..f92133a2 100644 --- a/src/builtin-addons/core/template-completion-provider.ts +++ b/src/builtin-addons/core/template-completion-provider.ts @@ -178,7 +178,7 @@ export default class TemplateCompletionProvider { await mListComponents(project); this.enableRegistryCache('componentsRegistryInitialized'); - await mGetProjectAddonsInfo(project.root); + await mGetProjectAddonsInfo(project.root, project.dependencyMap); this.enableRegistryCache('projectAddonsInfoInitialized'); this.project.invalidateRegistry(); @@ -195,7 +195,7 @@ export default class TemplateCompletionProvider { const items: CompletionItem[] = []; if (!this.meta.projectAddonsInfoInitialized) { - await mGetProjectAddonsInfo(root); + await mGetProjectAddonsInfo(root, this.project.dependencyMap); this.enableRegistryCache('projectAddonsInfoInitialized'); this.project.invalidateRegistry(); } @@ -256,7 +256,7 @@ export default class TemplateCompletionProvider { } async getMustachePathCandidates(root: string) { if (!this.meta.projectAddonsInfoInitialized) { - await mGetProjectAddonsInfo(root); + await mGetProjectAddonsInfo(root, this.project.dependencyMap); this.enableRegistryCache('projectAddonsInfoInitialized'); this.project.invalidateRegistry(); } @@ -305,7 +305,7 @@ export default class TemplateCompletionProvider { } async getBlockPathCandidates(root: string): Promise { if (!this.meta.projectAddonsInfoInitialized) { - await mGetProjectAddonsInfo(root); + await mGetProjectAddonsInfo(root, this.project.dependencyMap); this.enableRegistryCache('projectAddonsInfoInitialized'); this.project.invalidateRegistry(); } @@ -340,7 +340,7 @@ export default class TemplateCompletionProvider { } if (!this.meta.projectAddonsInfoInitialized) { - await mGetProjectAddonsInfo(this.project.root); + await mGetProjectAddonsInfo(this.project.root, this.project.dependencyMap); this.enableRegistryCache('projectAddonsInfoInitialized'); this.project.invalidateRegistry(); } @@ -447,7 +447,7 @@ export default class TemplateCompletionProvider { } if (!this.meta.projectAddonsInfoInitialized) { - await mGetProjectAddonsInfo(root); + await mGetProjectAddonsInfo(root, this.project.dependencyMap); this.enableRegistryCache('projectAddonsInfoInitialized'); this.project.invalidateRegistry(); } diff --git a/src/project-roots.ts b/src/project-roots.ts index b7a8f113..e3f361b1 100644 --- a/src/project-roots.ts +++ b/src/project-roots.ts @@ -87,6 +87,10 @@ export default class ProjectRoots { ignore: ['**/.git/**', '**/bower_components/**', '**/dist/**', '**/node_modules/**', '**/tmp/**'], }); + logInfo(`ELS: Found ${roots.length} roots for ${workspaceRoot}`); + + const start = Date.now(); + for (const rootPath of roots) { const filePath = path.join(workspaceRoot, rootPath); const fullPath = path.dirname(filePath); @@ -103,6 +107,8 @@ export default class ProjectRoots { await this.onProjectAdd(fullPath); } } + + logInfo(`ELS: iterating roots took ${Date.now() - start}ms`); } async initialize(workspaceRoot: string) { @@ -122,6 +128,8 @@ export default class ProjectRoots { if (this.projects.has(projectPath)) { const project = this.projects.get(projectPath) as Project; + logInfo(`Project already existed at ${projectPath}`); + return { initIssues: project.initIssues, providers: project.providers, @@ -154,12 +162,16 @@ export default class ProjectRoots { }; } + logInfo(`Initializing new project at ${projectPath} with ${this.localAddons.length} ELS addons.`); + const project = new Project(projectPath, this.localAddons, info); + const start = Date.now(); + await project.initialize(this.server); this.projects.set(projectPath, project); - logInfo(`Ember CLI project added at ${projectPath}`); + logInfo(`Ember CLI project added at ${projectPath}. (took ${Date.now() - start}ms)`); await project.init(this.server); return { diff --git a/src/project.ts b/src/project.ts index 04221b71..5e6014d6 100644 --- a/src/project.ts +++ b/src/project.ts @@ -35,6 +35,17 @@ export interface Executors { } export class Project extends BaseProject { + // Don't traverse dependencies we've already seen. + // correct package graph can sort of throw us in to cycles if we don't keep track of this. + + dependencyMap: Map< + string, + { + package: PackageInfo; + root: string; + } + > = new Map(); + providers!: ProjectProviders; builtinProviders!: ProjectProviders; addonsMeta: AddonMeta[] = []; @@ -148,7 +159,7 @@ export class Project extends BaseProject { this.providers = emptyProjectProviders(); this.flags.enableEagerRegistryInitialization = false; } else if (server.options.type === 'node') { - this.providers = await collectProjectProviders(this.root, this.addons); + this.providers = await collectProjectProviders(this.root, this.addons, this.dependencyMap); } else { throw new Error(`Unknown server type: "${server.options.type}"`); } diff --git a/src/server.ts b/src/server.ts index 2ed5e531..02abe190 100644 --- a/src/server.ts +++ b/src/server.ts @@ -168,12 +168,14 @@ export default class Server { hoverProvider!: HoverProvider; codeActionProvider!: CodeActionProvider; async executeInitializers() { - logInfo('UELS: executeInitializers'); + logInfo('ELS: executeInitializers'); + logInfo(`ELS: ${this.initializers.length} initializers`); for (const initializer of this.initializers) { await initializer(); } + logInfo(`ELS: clearing initializers because they've been initialized`); this.initializers = []; } private onInitialized() { @@ -216,7 +218,7 @@ export default class Server { }; } - await mGetProjectAddonsInfo(project.root); + await mGetProjectAddonsInfo(project.root, project.dependencyMap); project.invalidateRegistry(); return { @@ -524,6 +526,8 @@ export default class Server { this.initializers.push(async () => { await this.projectRoots.initialize(rootPath as string); + logInfo(`Found ${workspaceFolders?.length ?? 0} workspace folders for ${rootPath}`); + if (workspaceFolders && Array.isArray(workspaceFolders)) { for (const folder of workspaceFolders) { const folderPath = URI.parse(folder.uri).fsPath; diff --git a/src/utils/addon-api.ts b/src/utils/addon-api.ts index d3d4007d..6f6eb543 100644 --- a/src/utils/addon-api.ts +++ b/src/utils/addon-api.ts @@ -11,7 +11,7 @@ import { } from './layout-helpers'; import { TextDocument } from 'vscode-languageserver-textdocument'; import * as path from 'path'; -import { log, logInfo, logError, safeStringify } from './logger'; +import { log, logInfo, logError, safeStringify, instrumentTime } from './logger'; import Server from '../server'; import ASTPath from './../glimmer-utils'; import DAGMap from 'dag-map'; @@ -187,8 +187,17 @@ function requireUncached(module: string) { return result; } -export async function collectProjectProviders(root: string, addons: string[]): Promise { - const [projectAddonsRoots, projectInRepoAddonsRoots] = await Promise.all([getProjectAddonsRoots(root), getProjectInRepoAddonsRoots(root)]); +export async function collectProjectProviders(root: string, addons: string[], dependencyMap: Project['dependencyMap']): Promise { + const time = instrumentTime(`collectProjectProviders(${root})`); + + time.log(`Starting`); + const [projectAddonsRoots, projectInRepoAddonsRoots] = await Promise.all([ + getProjectAddonsRoots(root, dependencyMap), + getProjectInRepoAddonsRoots(root, dependencyMap), + ]); + + time.log(`found roots`); + const roots = addons .concat([root]) .concat(projectAddonsRoots, projectInRepoAddonsRoots) @@ -226,6 +235,8 @@ export async function collectProjectProviders(root: string, addons: string[]): P } } + time.log(`found ELS addons`); + const result: { definitionProviders: DefinitionResolveFunction[]; referencesProviders: ReferenceResolveFunction[]; @@ -332,6 +343,8 @@ export async function collectProjectProviders(root: string, addons: string[]): P } }); + time.log(`finished crawling dagMap`); + return result; } diff --git a/src/utils/definition-helpers.ts b/src/utils/definition-helpers.ts index 22f52ac0..b5187b57 100644 --- a/src/utils/definition-helpers.ts +++ b/src/utils/definition-helpers.ts @@ -6,6 +6,7 @@ import { URI } from 'vscode-uri'; import { podModulePrefixForRoot, hasAddonFolderInPath, getProjectAddonsRoots, getProjectInRepoAddonsRoots, asyncFilter } from './layout-helpers'; import { fsProvider } from '../fs-provider'; +import { Project } from '..'; const mProjectAddonsRoots = memoize(getProjectAddonsRoots, { length: 3, @@ -166,7 +167,8 @@ export function getPathsForComponentTemplates(root: string, maybeComponentName: return paths; } -export async function getAddonImport(root: string, importPath: string) { +export async function getAddonImport(project: Project, importPath: string) { + const { root, dependencyMap } = project; const importParts = importPath.split('/'); let addonName = importParts.shift(); @@ -179,7 +181,7 @@ export async function getAddonImport(root: string, importPath: string) { } const items: string[] = []; - const [addonRoots, inRepoRoots] = await Promise.all([mProjectAddonsRoots(root), mProjectInRepoAddonsRoots(root)]); + const [addonRoots, inRepoRoots] = await Promise.all([mProjectAddonsRoots(root, dependencyMap), mProjectInRepoAddonsRoots(root, dependencyMap)]); const roots = items.concat(addonRoots, inRepoRoots); let existingPaths: string[] = []; @@ -228,9 +230,10 @@ export async function getAddonImport(root: string, importPath: string) { return existingPaths; } -export async function getAddonPathsForType(root: string, collection: 'services' | 'models' | 'modifiers' | 'helpers' | 'routes', name: string) { +export async function getAddonPathsForType(project: Project, collection: 'services' | 'models' | 'modifiers' | 'helpers' | 'routes', name: string) { + const { root, dependencyMap } = project; const items: string[] = []; - const [addonRoots, inRepoRoots] = await Promise.all([mProjectAddonsRoots(root), mProjectInRepoAddonsRoots(root)]); + const [addonRoots, inRepoRoots] = await Promise.all([mProjectAddonsRoots(root, dependencyMap), mProjectInRepoAddonsRoots(root, dependencyMap)]); const roots = items.concat(addonRoots, inRepoRoots); let existingPaths: string[] = []; let hasValidPath = false; diff --git a/src/utils/layout-helpers.ts b/src/utils/layout-helpers.ts index 6118733e..7c27acb5 100644 --- a/src/utils/layout-helpers.ts +++ b/src/utils/layout-helpers.ts @@ -6,6 +6,8 @@ import { clean, coerce, valid } from 'semver'; import { BaseProject } from '../base-project'; import { fsProvider } from '../fs-provider'; import walkAsync from './walk-async'; +import { instrumentTime, logDebugInfo } from './logger'; +import { Project } from '../project'; // const GLOBAL_REGISTRY = ['primitive-name'][['relatedFiles']]; @@ -194,8 +196,21 @@ export function cached(_proto: unknown, prop: string, desc: PropertyDescriptor) }; } -async function getRecursiveInRepoAddonRoots(root: string, roots: string[]) { - const packageData = await asyncGetPackageJSON(root); +async function getRecursiveInRepoAddonRoots(root: string, dependencyMap: Project['dependencyMap'], roots: string[]) { + let fastPackage: PackageInfo | null = null; + + for (const dependencyMapItem of dependencyMap) { + if (dependencyMapItem[1].root === root) { + fastPackage = dependencyMapItem[1].package; + break; + } + } + + const packageData = fastPackage || (await asyncGetPackageJSON(root)); + + // names are required for packages + if (!packageData.name) return []; + const emberAddonPaths: string[] = (packageData['ember-addon'] && packageData['ember-addon'].paths) || []; if (roots.length) { @@ -211,7 +226,23 @@ async function getRecursiveInRepoAddonRoots(root: string, roots: string[]) { const validPaths = await asyncFilter(normalizedPaths, isProjectAddonRoot); for (const validRoot of validPaths) { - const packInfo = await asyncGetPackageJSON(validRoot); + let fastPackage: PackageInfo | null = null; + + for (const dependencyMapItem of dependencyMap) { + if (dependencyMapItem[1].root === validRoot) { + fastPackage = dependencyMapItem[1].package; + break; + } + } + + const packInfo = fastPackage || (await asyncGetPackageJSON(validRoot)); + + // names are required for packages + if (!packInfo.name) continue; + + if (!fastPackage) { + dependencyMap.set(packInfo.name, { root: validRoot, package: packInfo }); + } // we don't need to go deeper if package itself not an ember-addon or els-extension if (!isEmberAddon(packInfo) && !hasEmberLanguageServerExtension(packInfo)) { @@ -220,7 +251,7 @@ async function getRecursiveInRepoAddonRoots(root: string, roots: string[]) { if (!recursiveRoots.includes(validRoot)) { recursiveRoots.push(validRoot); - const items = await getRecursiveInRepoAddonRoots(validRoot, recursiveRoots); + const items = await getRecursiveInRepoAddonRoots(validRoot, dependencyMap, recursiveRoots); items.forEach((relatedRoot: string) => { if (!recursiveRoots.includes(relatedRoot)) { @@ -233,8 +264,12 @@ async function getRecursiveInRepoAddonRoots(root: string, roots: string[]) { return recursiveRoots.sort(); } -export async function getProjectInRepoAddonsRoots(root: string): Promise { - const roots: string[] = await getRecursiveInRepoAddonRoots(root, []); +export async function getProjectInRepoAddonsRoots(root: string, dependencyMap: Project['dependencyMap']): Promise { + const time = instrumentTime(`getProjectInRepoAddonsRoots(${root})`); + + const roots: string[] = await getRecursiveInRepoAddonRoots(root, dependencyMap, []); + + time.log(`finished getRecursiveInRepoAddonRoots`); return Array.from(new Set(roots)); } @@ -273,8 +308,30 @@ export async function isGlimmerXProject(root: string) { return hasDep(pack, '@glimmerx/core') || hasDep(pack, 'glimmer-lite-core'); } -export async function getProjectAddonsRoots(root: string, resolvedItems: string[] = [], packageFolderName = 'node_modules') { - const pack = await asyncGetPackageJSON(root); +export async function getProjectAddonsRoots( + root: string, + dependencyMap: Project['dependencyMap'], + resolvedItems: string[] = [], + packageFolderName = 'node_modules' +) { + const time = instrumentTime(`getProjectInRepoAddonsRoots(${root})`); + + let fastPackage: PackageInfo | null = null; + + for (const dependencyMapItem of dependencyMap) { + if (dependencyMapItem[1].root === root) { + fastPackage = dependencyMapItem[1].package; + break; + } + } + + const pack = fastPackage || (await asyncGetPackageJSON(root)); + + if (!pack.name) { + logDebugInfo('no name', root, JSON.stringify(pack), Array.from(dependencyMap), resolvedItems); + + return []; + } if (resolvedItems.length) { if (!isEmberAddon(pack)) { @@ -287,19 +344,41 @@ export async function getProjectAddonsRoots(root: string, resolvedItems: string[ : [...Object.keys(pack.dependencies || {}), ...Object.keys(pack.peerDependencies || {}), ...Object.keys(pack.devDependencies || {})]; // logDebugInfo('items', items); - const rawRoots = await Promise.all( + const rawRoots: [string, string | false][] = await Promise.all( items.map(async (item: string) => { - return await resolvePackageRoot(root, item, packageFolderName); + if (dependencyMap.has(item)) { + return [item, dependencyMap.get(item)!.root ?? false]; + } + + const packageRoot = await resolvePackageRoot(root, item, packageFolderName); + + return [item, packageRoot]; }) ); - const roots = rawRoots.filter((p: string | boolean) => { + const _roots = rawRoots.filter(([, p]: [string, string | boolean]) => { return p !== false; - }) as string[]; + }) as Array<[string, string]>; const recursiveRoots: string[] = resolvedItems.slice(0); - const packages = await Promise.all(roots.map((root) => asyncGetPackageJSON(root))); + const packages = await Promise.all( + _roots.map(async ([packageName, packageRoot]) => { + const mappedValue = dependencyMap.get(packageName); + + if (mappedValue) { + return mappedValue.package; + } + + const packInfo = await asyncGetPackageJSON(packageRoot); + + dependencyMap.set(packageName, { root: packageRoot, package: packInfo }); + + return packInfo; + }) + ); + + const roots = _roots.map(([, p]: [string, string]) => p); for (const rootItem of roots) { const packInfo = packages[roots.indexOf(rootItem)]; @@ -311,7 +390,7 @@ export async function getProjectAddonsRoots(root: string, resolvedItems: string[ if (!recursiveRoots.includes(rootItem)) { recursiveRoots.push(rootItem); - const addonRoots = await getProjectAddonsRoots(rootItem, recursiveRoots, packageFolderName); + const addonRoots = await getProjectAddonsRoots(rootItem, dependencyMap, recursiveRoots, packageFolderName); addonRoots.forEach((item: string) => { if (!recursiveRoots.includes(item)) { @@ -321,6 +400,8 @@ export async function getProjectAddonsRoots(root: string, resolvedItems: string[ } } + time.log(`Finished looping over ${roots.length} roots`); + return recursiveRoots; } @@ -396,8 +477,11 @@ export function hasAddonFolderInPath(name: string) { return name.includes(path.sep + 'addon' + path.sep) || name.includes(path.sep + 'addon-test-support' + path.sep); } -export async function getProjectAddonsInfo(root: string): Promise { - const [projectAddonsRoots, projectInRepoAddonsRoots] = await Promise.all([getProjectAddonsRoots(root), getProjectInRepoAddonsRoots(root)]); +export async function getProjectAddonsInfo(root: string, dependencyMap: Project['dependencyMap']): Promise { + const [projectAddonsRoots, projectInRepoAddonsRoots] = await Promise.all([ + getProjectAddonsRoots(root, dependencyMap), + getProjectInRepoAddonsRoots(root, dependencyMap), + ]); const roots = ([] as string[]).concat(projectAddonsRoots, projectInRepoAddonsRoots).filter((pathItem: unknown) => typeof pathItem === 'string'); for (const packagePath of roots) { diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 26d9bd22..5274a745 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -35,6 +35,24 @@ export function logInfo(str: string) { } } +export function instrumentTime(label: string) { + let last = Date.now(); + + return { + reset: () => { + last = Date.now(); + }, + log: (msg: string) => { + const now = Date.now(); + const diff = now - last; + + last = now; + + logInfo(`[${label}] +${diff}ms :: ${msg}`); + }, + }; +} + export function setConsole(item: RemoteConsole | null) { remoteConsole = item; } diff --git a/src/utils/usages-api.ts b/src/utils/usages-api.ts index 83a930ee..a092ce75 100644 --- a/src/utils/usages-api.ts +++ b/src/utils/usages-api.ts @@ -107,7 +107,7 @@ export function findRelatedFiles(token: string, tokenType: MatchResultType = 'co const tokenQueue: [UsageType, string, string][] = []; -let extractionTimeout: NodeJS.Timeout; +let extractionTimeout: NodeJS.Timeout | number; function scheduleTokensExtraction(kind: UsageType, normalizedName: string, file: string) { tokenQueue.push([kind, normalizedName, file]); diff --git a/test/glimmer-utils-test.ts b/test/glimmer-utils-test.ts index c9a78e87..e3c41743 100644 --- a/test/glimmer-utils-test.ts +++ b/test/glimmer-utils-test.ts @@ -1,5 +1,6 @@ import { Position } from 'vscode-languageserver'; import { ASTv1, preprocess } from '@glimmer/syntax'; +import * as assert from 'node:assert'; import ASTPath, { getLocalScope, maybeComponentNameForPath, sourceForNode, focusedBlockParamName, maybeBlockParamDefinition } from '../src/glimmer-utils'; import { toPosition } from '../src/estree-utils'; @@ -16,7 +17,7 @@ describe('glimmer-utils', function () { `; const astPath = ASTPath.toPosition(preprocess(input), toPosition(Position.create(3, 5))); - expect(astPath.node).toMatchSnapshot(); + expect(astPath?.node).toMatchSnapshot(); }); }); describe('getLocalScope', function () { @@ -30,6 +31,8 @@ describe('glimmer-utils', function () { `; const astPath = ASTPath.toPosition(preprocess(input), toPosition(Position.create(3, 5))); + assert(astPath, `ASTPath not found in test`); + expect(getLocalScope(astPath).map(({ name, index }) => [name, index])).toEqual([ ['item', 0], ['bar', 1], @@ -48,6 +51,8 @@ describe('glimmer-utils', function () { `; const astPath = ASTPath.toPosition(preprocess(input), toPosition(Position.create(3, 5))); + assert(astPath, `ASTPath not found in test`); + expect(maybeComponentNameForPath(astPath)).toEqual('Component'); }); }); @@ -62,6 +67,8 @@ describe('glimmer-utils', function () { `; const astPath = ASTPath.toPosition(preprocess(input), toPosition(Position.create(2, 2))); + assert(astPath, `ASTPath not found in test`); + expect((astPath.node as ASTv1.ElementNode).tag).toEqual('Component'); expect(sourceForNode(astPath.node, input)).toEqual(input.trim()); }); @@ -69,6 +76,8 @@ describe('glimmer-utils', function () { const input = ['', '{{#let items as |item bar|}}', '{{items}}', '{{/let}}', ''].join('\n'); const astPath = ASTPath.toPosition(preprocess(input), toPosition(Position.create(2, 3))); + assert(astPath, `ASTPath not found in test`); + expect((astPath.node as ASTv1.PathExpression).original).toEqual('items'); expect(sourceForNode(astPath.node, input)).toEqual('items'); }); @@ -79,6 +88,8 @@ describe('glimmer-utils', function () { const pos = toPosition(Position.create(1, 3)); const astPath = ASTPath.toPosition(preprocess(input), pos); + assert(astPath, `ASTPath not found in test`); + expect(maybeBlockParamDefinition(astPath, input, pos)).toEqual(undefined); }); it('able to handle single param', function () { @@ -86,6 +97,8 @@ describe('glimmer-utils', function () { const pos = toPosition(Position.create(0, 16)); const astPath = ASTPath.toPosition(preprocess(input), pos); + assert(astPath, `ASTPath not found in test`); + expect(maybeBlockParamDefinition(astPath, input, pos)).toMatchSnapshot(); }); it('able to handle single fiew params', function () { @@ -93,6 +106,8 @@ describe('glimmer-utils', function () { const pos = toPosition(Position.create(0, 22)); const astPath = ASTPath.toPosition(preprocess(input), pos); + assert(astPath, `ASTPath not found in test`); + expect(maybeBlockParamDefinition(astPath, input, pos)).toMatchSnapshot(); }); }); diff --git a/test/integration-test.ts b/test/integration-test.ts index 321778d6..1bb7f76f 100644 --- a/test/integration-test.ts +++ b/test/integration-test.ts @@ -208,6 +208,7 @@ describe('integration', function () { }, }, 'package.json': JSON.stringify({ + name: 'some-project', 'ember-addon': { paths: ['lib/biz'], }, @@ -263,6 +264,7 @@ describe('integration', function () { }, }, 'package.json': JSON.stringify({ + name: 'some-different-project', 'ember-addon': { paths: ['lib/biz'], }, @@ -1006,6 +1008,7 @@ describe('integration', function () { connection, { 'package.json': JSON.stringify({ + name: 'default-name', 'ember-language-server': { entry: './lib/langserver', capabilities: { @@ -1056,6 +1059,7 @@ describe('integration', function () { }, }, 'package.json': JSON.stringify({ + name: 't1', dependencies: { provider: '*', }, @@ -1097,7 +1101,9 @@ describe('integration', function () { }, }, 'package.json': JSON.stringify({ + name: 'fake', dependencies: { + name: 'lake', provider: '*', }, }), @@ -1322,7 +1328,9 @@ describe('integration', function () { }, }, 'package.json': JSON.stringify({ + name: 'pork', dependencies: { + name: 'park', provider: '*', }, }), @@ -1396,7 +1404,9 @@ describe('integration', function () { }, }, 'package.json': JSON.stringify({ + name: 'white', dependencies: { + name: 'dark', provider: '*', }, }), @@ -1456,6 +1466,7 @@ describe('integration', function () { }, }, 'package.json': JSON.stringify({ + name: 'shark', dependencies: { provider: '*', }, @@ -1514,6 +1525,7 @@ describe('integration', function () { }, }, 'package.json': JSON.stringify({ + name: 'pork', dependencies: { provider: '*', }, @@ -1562,6 +1574,7 @@ describe('integration', function () { }, }, 'package.json': JSON.stringify({ + name: 'dog', dependencies: { provider: '*', }, @@ -1609,6 +1622,7 @@ describe('integration', function () { }, }, 'package.json': JSON.stringify({ + name: 'cat', dependencies: { provider: '*', }, @@ -1692,6 +1706,7 @@ describe('integration', function () { }, }, 'package.json': JSON.stringify({ + name: 'zoo', dependencies: { 'ember-holy-futuristic-template-namespacing-batman': '^1.0.2' }, 'ember-addon': { paths: ['lib/biz'], @@ -1730,6 +1745,7 @@ describe('integration', function () { }, }, 'package.json': JSON.stringify({ + name: 'boss', dependencies: { 'ember-holy-futuristic-template-namespacing-batman': '^1.0.2' }, }), }, diff --git a/test/test_helpers/integration-helpers.ts b/test/test_helpers/integration-helpers.ts index 6a35c784..c221c5ad 100644 --- a/test/test_helpers/integration-helpers.ts +++ b/test/test_helpers/integration-helpers.ts @@ -708,6 +708,7 @@ export function makeProject(appFiles = {}, addons = {}) { const fileStructure = Object.assign({}, appFiles, { node_modules, 'package.json': JSON.stringify({ + name: 'mock-project', dependencies, }), }); diff --git a/test/utils/layout-helpers-test.ts b/test/utils/layout-helpers-test.ts index a5f72ba9..bcd238ad 100644 --- a/test/utils/layout-helpers-test.ts +++ b/test/utils/layout-helpers-test.ts @@ -130,7 +130,7 @@ describe('definition-helpers', function () { describe('getProjectInRepoAddonsRoots()', function () { it('must discover in-repo addons for classic structure', async function () { const root = path.join(__dirname, './../fixtures/project-with-in-repo-addons'); - const items = await getProjectInRepoAddonsRoots(root); + const items = await getProjectInRepoAddonsRoots(root, new Map()); expect(items.length).toEqual(2); }); @@ -139,7 +139,7 @@ describe('definition-helpers', function () { describe('getProjectAddonsRoots()', function () { it('must resolve all related to project addons', async function () { const root = path.join(__dirname, './../fixtures/full-project'); - const items = await getProjectAddonsRoots(root, [], 'hope_modules'); + const items = await getProjectAddonsRoots(root, new Map(), [], 'hope_modules'); expect(items.length).toEqual(2); }); @@ -174,7 +174,7 @@ describe('definition-helpers', function () { }, }); - const items = await getProjectAddonsRoots(path.join(info.path, 'packages', 'touchstone')); + const items = await getProjectAddonsRoots(path.join(info.path, 'packages', 'touchstone'), new Map()); expect(items.length).toEqual(1); expect(items[0].split(path.sep).join('/').split('node_modules/')[1]).toEqual('@skylight/anvil');