diff --git a/.cspell.json b/.cspell.json index 415d46de1cf6..21ca6f8fc237 100644 --- a/.cspell.json +++ b/.cspell.json @@ -72,6 +72,7 @@ "esquery", "esrecurse", "estree", + "globby", "IDE's", "IIFE", "IIFEs", diff --git a/.eslintrc.js b/.eslintrc.js index 006f52f143c4..dc68bb8f7867 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -40,6 +40,11 @@ module.exports = { tsconfigRootDir: __dirname, warnOnUnsupportedTypeScriptVersion: false, EXPERIMENTAL_useSourceOfProjectReferenceRedirect: false, + cacheLifetime: { + // we pretty well never create/change tsconfig structure - so need to ever evict the cache + // in the rare case that we do - just need to manually restart their IDE. + glob: 'Infinity', + }, }, rules: { // make sure we're not leveraging any deprecated APIs diff --git a/.gitignore b/.gitignore index 2ce061c3d4d7..0973f04542e0 100644 --- a/.gitignore +++ b/.gitignore @@ -69,6 +69,9 @@ jspm_packages/ # Editor-specific metadata folders .vs +# nodejs cpu profiles +*.cpuprofile + .DS_Store .idea dist diff --git a/docs/architecture/Parser.mdx b/docs/architecture/Parser.mdx index d8d4f05ee43d..5039dc1bec55 100644 --- a/docs/architecture/Parser.mdx +++ b/docs/architecture/Parser.mdx @@ -30,6 +30,9 @@ The following additional configuration options are available by specifying them ```ts interface ParserOptions { + cacheLifetime?: { + glob?: number | 'Infinity'; + }; ecmaFeatures?: { jsx?: boolean; globalReturn?: boolean; @@ -49,6 +52,14 @@ interface ParserOptions { } ``` +### `cacheLifetime` + +This option allows you to granularly control our internal cache expiry lengths. + +You can specify the number of seconds as an integer number, or the string 'Infinity' if you never want the cache to expire. + +By default cache entries will be evicted after 30 seconds, or will persist indefinitely if the parser infers that it is a single run. + ### `ecmaFeatures` Optional additional options to describe how to parse the raw syntax. diff --git a/docs/architecture/TypeScript-ESTree.mdx b/docs/architecture/TypeScript-ESTree.mdx index 066030b0afee..f74aa65e2234 100644 --- a/docs/architecture/TypeScript-ESTree.mdx +++ b/docs/architecture/TypeScript-ESTree.mdx @@ -226,7 +226,23 @@ interface ParseAndGenerateServicesOptions extends ParseOptions { allowAutomaticSingleRunInference?: boolean; /** - * Path to a file exporting a custom ModuleResolver. + * Granular control of the expiry lifetime of our internal caches. + * You can specify the number of seconds as an integer number, or the string + * 'Infinity' if you never want the cache to expire. + * + * By default cache entries will be evicted after 30 seconds, or will persist + * indefinitely if `allowAutomaticSingleRunInference = true` AND the parser + * infers that it is a single run. + */ + cacheLifetime?: { + /** + * Glob resolution for `parserOptions.project` values. + */ + glob?: number | 'Infinity'; + }; + + /** + * Path to a file exporting a custom `ModuleResolver`. */ moduleResolver?: string; } @@ -273,6 +289,23 @@ const { ast, services } = parseAndGenerateServices(code, { }); ``` +##### `ModuleResolver` + +The `moduleResolver` option allows you to specify the path to a module with a custom module resolver implementation. The module is expected to adhere to the following interface: + +```ts +interface ModuleResolver { + version: 1; + resolveModuleNames( + moduleNames: string[], + containingFile: string, + reusedNames: string[] | undefined, + redirectedReference: ts.ResolvedProjectReference | undefined, + options: ts.CompilerOptions, + ): (ts.ResolvedModule | undefined)[]; +} +``` + #### `parseWithNodeMaps(code, options)` Parses the given string of code with the options provided and returns both the ESTree-compatible AST as well as the node maps. diff --git a/packages/types/src/parser-options.ts b/packages/types/src/parser-options.ts index b3149231215e..a7fe3ce3ab36 100644 --- a/packages/types/src/parser-options.ts +++ b/packages/types/src/parser-options.ts @@ -3,6 +3,7 @@ import type { Program } from 'typescript'; import type { Lib } from './lib'; type DebugLevel = boolean | ('typescript-eslint' | 'eslint' | 'typescript')[]; +type CacheDurationSeconds = number | 'Infinity'; type EcmaVersion = | 3 @@ -59,7 +60,17 @@ interface ParserOptions { tsconfigRootDir?: string; warnOnUnsupportedTypeScriptVersion?: boolean; moduleResolver?: string; + cacheLifetime?: { + glob?: CacheDurationSeconds; + }; + [additionalProperties: string]: unknown; } -export { DebugLevel, EcmaVersion, ParserOptions, SourceType }; +export { + CacheDurationSeconds, + DebugLevel, + EcmaVersion, + ParserOptions, + SourceType, +}; diff --git a/packages/typescript-estree/src/create-program/getWatchProgramsForProjects.ts b/packages/typescript-estree/src/create-program/getWatchProgramsForProjects.ts index 15d88e5f4540..d9d4de9c833f 100644 --- a/packages/typescript-estree/src/create-program/getWatchProgramsForProjects.ts +++ b/packages/typescript-estree/src/create-program/getWatchProgramsForProjects.ts @@ -8,6 +8,7 @@ import type { CanonicalPath } from './shared'; import { canonicalDirname, createDefaultCompilerOptionsFromExtra, + createHash, getCanonicalFileName, getModuleResolver, } from './shared'; @@ -105,19 +106,6 @@ function diagnosticReporter(diagnostic: ts.Diagnostic): void { ); } -/** - * Hash content for compare content. - * @param content hashed contend - * @returns hashed result - */ -function createHash(content: string): string { - // No ts.sys in browser environments. - if (ts.sys?.createHash) { - return ts.sys.createHash(content); - } - return content; -} - function updateCachedFileList( tsconfigPath: CanonicalPath, program: ts.Program, diff --git a/packages/typescript-estree/src/create-program/shared.ts b/packages/typescript-estree/src/create-program/shared.ts index dd50f757dce1..e8de97969283 100644 --- a/packages/typescript-estree/src/create-program/shared.ts +++ b/packages/typescript-estree/src/create-program/shared.ts @@ -124,12 +124,26 @@ function getModuleResolver(moduleResolverPath: string): ModuleResolver { return moduleResolver; } +/** + * Hash content for compare content. + * @param content hashed contend + * @returns hashed result + */ +function createHash(content: string): string { + // No ts.sys in browser environments. + if (ts.sys?.createHash) { + return ts.sys.createHash(content); + } + return content; +} + export { ASTAndProgram, CORE_COMPILER_OPTIONS, canonicalDirname, CanonicalPath, createDefaultCompilerOptionsFromExtra, + createHash, ensureAbsolutePath, getCanonicalFileName, getAstFromProgram, diff --git a/packages/typescript-estree/src/parseSettings/ExpiringCache.ts b/packages/typescript-estree/src/parseSettings/ExpiringCache.ts new file mode 100644 index 000000000000..f296c9f5f590 --- /dev/null +++ b/packages/typescript-estree/src/parseSettings/ExpiringCache.ts @@ -0,0 +1,69 @@ +import type { CacheDurationSeconds } from '@typescript-eslint/types'; + +export const DEFAULT_TSCONFIG_CACHE_DURATION_SECONDS = 30; +const ZERO_HR_TIME: [number, number] = [0, 0]; + +/** + * A map with key-level expiration. + */ +export class ExpiringCache { + readonly #cacheDurationSeconds: CacheDurationSeconds; + /** + * The mapping of path-like string to the resolved TSConfig(s) + */ + protected readonly map = new Map< + TKey, + Readonly<{ + value: TValue; + lastSeen: [number, number]; + }> + >(); + + constructor(cacheDurationSeconds: CacheDurationSeconds) { + this.#cacheDurationSeconds = cacheDurationSeconds; + } + + set(key: TKey, value: TValue): this { + this.map.set(key, { + value, + lastSeen: + this.#cacheDurationSeconds === 'Infinity' + ? // no need to waste time calculating the hrtime in infinity mode as there's no expiry + ZERO_HR_TIME + : process.hrtime(), + }); + return this; + } + + get(key: TKey): TValue | undefined { + const entry = this.map.get(key); + if (entry?.value != null) { + if (this.#cacheDurationSeconds === 'Infinity') { + return entry.value; + } + + const ageSeconds = process.hrtime(entry.lastSeen)[0]; + if (ageSeconds < this.#cacheDurationSeconds) { + // cache hit woo! + return entry.value; + } else { + // key has expired - clean it up to free up memory + this.cleanupKey(key); + } + } + // no hit :'( + return undefined; + } + + protected cleanupKey(key: TKey): void { + this.map.delete(key); + } + + get size(): number { + return this.map.size; + } + + clear(): void { + this.map.clear(); + } +} diff --git a/packages/typescript-estree/src/parseSettings/createParseSettings.ts b/packages/typescript-estree/src/parseSettings/createParseSettings.ts index b1cde9d4c9ad..e7267a852686 100644 --- a/packages/typescript-estree/src/parseSettings/createParseSettings.ts +++ b/packages/typescript-estree/src/parseSettings/createParseSettings.ts @@ -1,15 +1,10 @@ import debug from 'debug'; -import { sync as globSync } from 'globby'; -import isGlob from 'is-glob'; -import type { CanonicalPath } from '../create-program/shared'; -import { - ensureAbsolutePath, - getCanonicalFileName, -} from '../create-program/shared'; +import { ensureAbsolutePath } from '../create-program/shared'; import type { TSESTreeOptions } from '../parser-options'; import type { MutableParseSettings } from './index'; import { inferSingleRun } from './inferSingleRun'; +import { resolveProjectList } from './resolveProjectList'; import { warnAboutTSVersion } from './warnAboutTSVersion'; const log = debug( @@ -98,23 +93,13 @@ export function createParseSettings( // Providing a program overrides project resolution if (!parseSettings.programs) { - const projectFolderIgnoreList = ( - options.projectFolderIgnoreList ?? ['**/node_modules/**'] - ) - .reduce((acc, folder) => { - if (typeof folder === 'string') { - acc.push(folder); - } - return acc; - }, []) - // prefix with a ! for not match glob - .map(folder => (folder.startsWith('!') ? folder : `!${folder}`)); - - parseSettings.projects = prepareAndTransformProjects( - tsconfigRootDir, - options.project, - projectFolderIgnoreList, - ); + parseSettings.projects = resolveProjectList({ + cacheLifetime: options.cacheLifetime, + project: options.project, + projectFolderIgnoreList: options.projectFolderIgnoreList, + singleRun: parseSettings.singleRun, + tsconfigRootDir: tsconfigRootDir, + }); } warnAboutTSVersion(parseSettings); @@ -144,58 +129,3 @@ function enforceString(code: unknown): string { function getFileName(jsx?: boolean): string { return jsx ? 'estree.tsx' : 'estree.ts'; } - -function getTsconfigPath( - tsconfigPath: string, - tsconfigRootDir: string, -): CanonicalPath { - return getCanonicalFileName( - ensureAbsolutePath(tsconfigPath, tsconfigRootDir), - ); -} - -/** - * Normalizes, sanitizes, resolves and filters the provided project paths - */ -function prepareAndTransformProjects( - tsconfigRootDir: string, - projectsInput: string | string[] | undefined, - ignoreListInput: string[], -): CanonicalPath[] { - const sanitizedProjects: string[] = []; - - // Normalize and sanitize the project paths - if (typeof projectsInput === 'string') { - sanitizedProjects.push(projectsInput); - } else if (Array.isArray(projectsInput)) { - for (const project of projectsInput) { - if (typeof project === 'string') { - sanitizedProjects.push(project); - } - } - } - - if (sanitizedProjects.length === 0) { - return []; - } - - // Transform glob patterns into paths - const nonGlobProjects = sanitizedProjects.filter(project => !isGlob(project)); - const globProjects = sanitizedProjects.filter(project => isGlob(project)); - const uniqueCanonicalProjectPaths = new Set( - nonGlobProjects - .concat( - globSync([...globProjects, ...ignoreListInput], { - cwd: tsconfigRootDir, - }), - ) - .map(project => getTsconfigPath(project, tsconfigRootDir)), - ); - - log( - 'parserOptions.project (excluding ignored) matched projects: %s', - uniqueCanonicalProjectPaths, - ); - - return Array.from(uniqueCanonicalProjectPaths); -} diff --git a/packages/typescript-estree/src/parseSettings/index.ts b/packages/typescript-estree/src/parseSettings/index.ts index 0a9734d1b241..53b1acf4a6fb 100644 --- a/packages/typescript-estree/src/parseSettings/index.ts +++ b/packages/typescript-estree/src/parseSettings/index.ts @@ -98,7 +98,7 @@ export interface MutableParseSettings { /** * Normalized paths to provided project paths. */ - projects: CanonicalPath[]; + projects: readonly CanonicalPath[]; /** * Whether to add the `range` property to AST nodes. diff --git a/packages/typescript-estree/src/parseSettings/resolveProjectList.ts b/packages/typescript-estree/src/parseSettings/resolveProjectList.ts new file mode 100644 index 000000000000..72e9539d2b9b --- /dev/null +++ b/packages/typescript-estree/src/parseSettings/resolveProjectList.ts @@ -0,0 +1,146 @@ +import debug from 'debug'; +import { sync as globSync } from 'globby'; +import isGlob from 'is-glob'; + +import type { CanonicalPath } from '../create-program/shared'; +import { + createHash, + ensureAbsolutePath, + getCanonicalFileName, +} from '../create-program/shared'; +import type { TSESTreeOptions } from '../parser-options'; +import { + DEFAULT_TSCONFIG_CACHE_DURATION_SECONDS, + ExpiringCache, +} from './ExpiringCache'; + +const log = debug( + 'typescript-eslint:typescript-estree:parser:parseSettings:resolveProjectList', +); + +let RESOLUTION_CACHE: ExpiringCache | null = + null; + +/** + * Normalizes, sanitizes, resolves and filters the provided project paths + */ +export function resolveProjectList( + options: Readonly<{ + cacheLifetime?: TSESTreeOptions['cacheLifetime']; + project: TSESTreeOptions['project']; + projectFolderIgnoreList: TSESTreeOptions['projectFolderIgnoreList']; + singleRun: boolean; + tsconfigRootDir: string; + }>, +): readonly CanonicalPath[] { + const sanitizedProjects: string[] = []; + + // Normalize and sanitize the project paths + if (typeof options.project === 'string') { + sanitizedProjects.push(options.project); + } else if (Array.isArray(options.project)) { + for (const project of options.project) { + if (typeof project === 'string') { + sanitizedProjects.push(project); + } + } + } + + if (sanitizedProjects.length === 0) { + return []; + } + + const projectFolderIgnoreList = ( + options.projectFolderIgnoreList ?? ['**/node_modules/**'] + ) + .reduce((acc, folder) => { + if (typeof folder === 'string') { + acc.push(folder); + } + return acc; + }, []) + // prefix with a ! for not match glob + .map(folder => (folder.startsWith('!') ? folder : `!${folder}`)); + + const cacheKey = getHash({ + project: sanitizedProjects, + projectFolderIgnoreList, + tsconfigRootDir: options.tsconfigRootDir, + }); + if (RESOLUTION_CACHE == null) { + // note - we initialize the global cache based on the first config we encounter. + // this does mean that you can't have multiple lifetimes set per folder + // I doubt that anyone will really bother reconfiguring this, let alone + // try to do complicated setups, so we'll deal with this later if ever. + RESOLUTION_CACHE = new ExpiringCache( + options.singleRun + ? 'Infinity' + : options.cacheLifetime?.glob ?? + DEFAULT_TSCONFIG_CACHE_DURATION_SECONDS, + ); + } else { + const cached = RESOLUTION_CACHE.get(cacheKey); + if (cached) { + return cached; + } + } + + // Transform glob patterns into paths + const nonGlobProjects = sanitizedProjects.filter(project => !isGlob(project)); + const globProjects = sanitizedProjects.filter(project => isGlob(project)); + + const uniqueCanonicalProjectPaths = new Set( + nonGlobProjects + .concat( + globProjects.length === 0 + ? [] + : globSync([...globProjects, ...projectFolderIgnoreList], { + cwd: options.tsconfigRootDir, + }), + ) + .map(project => + getCanonicalFileName( + ensureAbsolutePath(project, options.tsconfigRootDir), + ), + ), + ); + + log( + 'parserOptions.project (excluding ignored) matched projects: %s', + uniqueCanonicalProjectPaths, + ); + + const returnValue = Array.from(uniqueCanonicalProjectPaths); + RESOLUTION_CACHE.set(cacheKey, returnValue); + return returnValue; +} + +function getHash({ + project, + projectFolderIgnoreList, + tsconfigRootDir, +}: Readonly<{ + project: readonly string[]; + projectFolderIgnoreList: readonly string[]; + tsconfigRootDir: string; +}>): string { + // create a stable representation of the config + const hashObject = { + tsconfigRootDir, + // the project order does matter and can impact the resolved globs + project, + // the ignore order won't doesn't ever matter + projectFolderIgnoreList: [...projectFolderIgnoreList].sort(), + }; + + return createHash(JSON.stringify(hashObject)); +} + +/** + * Exported for testing purposes only + * @internal + */ +export function clearGlobResolutionCache(): void { + RESOLUTION_CACHE?.clear(); + RESOLUTION_CACHE = null; +} diff --git a/packages/typescript-estree/src/parser-options.ts b/packages/typescript-estree/src/parser-options.ts index 632d9e6ae883..8cfe3c934c2c 100644 --- a/packages/typescript-estree/src/parser-options.ts +++ b/packages/typescript-estree/src/parser-options.ts @@ -1,4 +1,7 @@ -import type { DebugLevel } from '@typescript-eslint/types'; +import type { + CacheDurationSeconds, + DebugLevel, +} from '@typescript-eslint/types'; import type * as ts from 'typescript'; import type { TSESTree, TSESTreeToTSNode, TSNode, TSToken } from './ts-estree'; @@ -168,6 +171,25 @@ interface ParseAndGenerateServicesOptions extends ParseOptions { */ allowAutomaticSingleRunInference?: boolean; + /** + * Granular control of the expiry lifetime of our internal caches. + * You can specify the number of seconds as an integer number, or the string + * 'Infinity' if you never want the cache to expire. + * + * By default cache entries will be evicted after 30 seconds, or will persist + * indefinitely if `allowAutomaticSingleRunInference = true` AND the parser + * infers that it is a single run. + */ + cacheLifetime?: { + /** + * Glob resolution for `parserOptions.project` values. + */ + glob?: CacheDurationSeconds; + }; + + /** + * Path to a file exporting a custom `ModuleResolver`. + */ moduleResolver?: string; } diff --git a/packages/typescript-estree/tests/lib/parse.test.ts b/packages/typescript-estree/tests/lib/parse.test.ts index 3e84dd3e069f..0f286efdb191 100644 --- a/packages/typescript-estree/tests/lib/parse.test.ts +++ b/packages/typescript-estree/tests/lib/parse.test.ts @@ -1,10 +1,14 @@ +import type { CacheDurationSeconds } from '@typescript-eslint/types'; import debug from 'debug'; +import * as globbyModule from 'globby'; import { join, resolve } from 'path'; +import type * as typescriptModule from 'typescript'; import * as parser from '../../src'; import * as astConverterModule from '../../src/ast-converter'; import * as sharedParserUtilsModule from '../../src/create-program/shared'; import type { TSESTreeOptions } from '../../src/parser-options'; +import { clearGlobResolutionCache } from '../../src/parseSettings/resolveProjectList'; import { createSnapshotTestBlock } from '../../tools/test-utils'; const FIXTURES_DIR = join(__dirname, '../fixtures/simpleProject'); @@ -38,7 +42,7 @@ jest.mock('../../src/create-program/shared', () => { // Tests in CI by default run with lowercase program file names, // resulting in path.relative results starting with many "../"s jest.mock('typescript', () => { - const ts = jest.requireActual('typescript'); + const ts = jest.requireActual('typescript'); return { ...ts, sys: { @@ -48,10 +52,21 @@ jest.mock('typescript', () => { }; }); +jest.mock('globby', () => { + const globby = jest.requireActual('globby'); + return { + ...globby, + sync: jest.fn(globby.sync), + }; +}); + +const hrtimeSpy = jest.spyOn(process, 'hrtime'); + const astConverterMock = jest.mocked(astConverterModule.astConverter); const createDefaultCompilerOptionsFromExtra = jest.mocked( sharedParserUtilsModule.createDefaultCompilerOptionsFromExtra, ); +const globbySyncMock = jest.mocked(globbyModule.sync); /** * Aligns paths between environments, node for windows uses `\`, for linux and mac uses `/` @@ -63,6 +78,7 @@ function alignErrorPath(error: Error): never { beforeEach(() => { jest.clearAllMocks(); + clearGlobResolutionCache(); }); describe('parseWithNodeMaps()', () => { @@ -749,8 +765,69 @@ describe('parseAndGenerateServices', () => { it('ignores a folder when given a string glob', () => { const ignore = ['**/ignoreme/**']; + // cspell:disable-next-line expect(testParse('ignoreme', ignore)).toThrow(); + // cspell:disable-next-line expect(testParse('includeme', ignore)).not.toThrow(); }); }); + + describe('cacheLifetime', () => { + describe('glob', () => { + function doParse(lifetime: CacheDurationSeconds): void { + parser.parseAndGenerateServices('const x = 1', { + cacheLifetime: { + glob: lifetime, + }, + filePath: join(FIXTURES_DIR, 'file.ts'), + tsconfigRootDir: FIXTURES_DIR, + project: ['./**/tsconfig.json', './**/tsconfig.extra.json'], + }); + } + + it('should cache globs if the lifetime is non-zero', () => { + doParse(30); + expect(globbySyncMock).toHaveBeenCalledTimes(1); + doParse(30); + // shouldn't call globby again due to the caching + expect(globbySyncMock).toHaveBeenCalledTimes(1); + }); + + it('should not cache globs if the lifetime is zero', () => { + doParse(0); + expect(globbySyncMock).toHaveBeenCalledTimes(1); + doParse(0); + // should call globby again because we specified immediate cache expiry + expect(globbySyncMock).toHaveBeenCalledTimes(2); + }); + + it('should evict the cache if the entry expires', () => { + hrtimeSpy.mockReturnValueOnce([1, 0]); + + doParse(30); + expect(globbySyncMock).toHaveBeenCalledTimes(1); + + // wow so much time has passed + hrtimeSpy.mockReturnValueOnce([Number.MAX_VALUE, 0]); + + doParse(30); + // shouldn't call globby again due to the caching + expect(globbySyncMock).toHaveBeenCalledTimes(2); + }); + + it('should infinitely cache if passed Infinity', () => { + hrtimeSpy.mockReturnValueOnce([1, 0]); + + doParse('Infinity'); + expect(globbySyncMock).toHaveBeenCalledTimes(1); + + // wow so much time has passed + hrtimeSpy.mockReturnValueOnce([Number.MAX_VALUE, 0]); + + doParse('Infinity'); + // shouldn't call globby again due to the caching + expect(globbySyncMock).toHaveBeenCalledTimes(1); + }); + }); + }); }); diff --git a/packages/typescript-estree/tests/lib/semanticInfo-singleRun.test.ts b/packages/typescript-estree/tests/lib/semanticInfo-singleRun.test.ts index 33daa30a13f0..b605dd6ea5f3 100644 --- a/packages/typescript-estree/tests/lib/semanticInfo-singleRun.test.ts +++ b/packages/typescript-estree/tests/lib/semanticInfo-singleRun.test.ts @@ -2,6 +2,7 @@ import glob from 'glob'; import * as path from 'path'; import { getCanonicalFileName } from '../../src/create-program/shared'; +import { createProgramFromConfigFile as createProgramFromConfigFileOriginal } from '../../src/create-program/useProvidedPrograms'; import { clearParseAndGenerateServicesCalls, clearProgramCache, @@ -69,9 +70,9 @@ jest.mock('../../src/create-program/getWatchProgramsForProjects', () => { }; }); -const { - createProgramFromConfigFile, -} = require('../../src/create-program/useProvidedPrograms'); +const createProgramFromConfigFile = jest.mocked( + createProgramFromConfigFileOriginal, +); const FIXTURES_DIR = './tests/fixtures/semanticInfo'; const testFiles = glob.sync(`**/*.src.ts`, { @@ -97,7 +98,7 @@ describe('semanticInfo - singleRun', () => { // ensure caches are clean for each test clearProgramCache(); // ensure invocations of mock are clean for each test - (createProgramFromConfigFile as jest.Mock).mockClear(); + createProgramFromConfigFile.mockClear(); // Do not track invocations per file across tests clearParseAndGenerateServicesCalls(); }); @@ -232,7 +233,7 @@ describe('semanticInfo - singleRun', () => { const optionsWithReversedTsconfigs = { ...options, // Now the matching tsconfig comes first - project: options.project.reverse(), + project: [...options.project].reverse(), }; const resultProgram = parseAndGenerateServices( @@ -248,7 +249,7 @@ describe('semanticInfo - singleRun', () => { expect(createProgramFromConfigFile).toHaveBeenNthCalledWith( 1, - resolvedProject(tsconfigs[0]), + resolvedProject(tsconfigs[1]), ); // Restore process data diff --git a/yarn.lock b/yarn.lock index bbe5aba913b6..1c5ec83ed4aa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3246,6 +3246,13 @@ read-package-json-fast "^2.0.3" which "^2.0.2" +"@nrwl/cli@15.5.3": + version "15.5.3" + resolved "https://registry.yarnpkg.com/@nrwl/cli/-/cli-15.5.3.tgz#13277e5a0e8ba713850bcf13fa76717ea747a2bb" + integrity sha512-NWf9CWswvdYM6YzXuweaZPAZ2erMtQrrHZdgFbUGeojZBZ+b4TCGzLWNodZj4yQOa/eTwlyPMYO2LEw9CoapDQ== + dependencies: + nx "15.5.3" + "@nrwl/cli@15.6.3": version "15.6.3" resolved "https://registry.npmjs.org/@nrwl/cli/-/cli-15.6.3.tgz#999531d6efb30afc39373bdcbd7e78254a3a3fd3" @@ -3317,6 +3324,13 @@ tar "6.1.11" yargs-parser ">=21.0.1" +"@nrwl/tao@15.5.3": + version "15.5.3" + resolved "https://registry.yarnpkg.com/@nrwl/tao/-/tao-15.5.3.tgz#08c05715d2ecb108ed8b2c5381b9017cf1448b4a" + integrity sha512-vgPLIW9IoBfQ4IkHRT5RC4LqNwFBK5jmHYmFIRgbIeFRudFBbnpmOaKRME0OwN7qJ6964PVVbzahAPvYVD02xw== + dependencies: + nx "15.5.3" + "@nrwl/tao@15.6.3": version "15.6.3" resolved "https://registry.npmjs.org/@nrwl/tao/-/tao-15.6.3.tgz#b24e11345375dea96bc386c60b9b1102a7584932" @@ -10780,13 +10794,13 @@ nth-check@^2.0.0, nth-check@^2.0.1: dependencies: boolbase "^1.0.0" -nx@15.6.3: - version "15.6.3" - resolved "https://registry.npmjs.org/nx/-/nx-15.6.3.tgz#900087bce38c6e5975660c23ebd41ead1bf54f98" - integrity sha512-3t0A0GPLNen1yPAyE+VGZ3nkAzZYb5nfXtAcx8SHBlKq4u42yBY3khBmP1y4Og3jhIwFIj7J7Npeh8ZKrthmYQ== +nx@15.5.3, "nx@>=15.4.2 < 16": + version "15.5.3" + resolved "https://registry.npmjs.org/nx/-/nx-15.5.3.tgz#bf6252e7d9e17121dd82dec4f6fce319b9e005fa" + integrity sha512-PHB8VbiBLP108xb+yR8IGEsYWr7OcmDDOjHL+73oP4lVjyPgT8wdTMe6tI5LdBgv+KZ+0kiThK3ckvcPsfgvLQ== dependencies: - "@nrwl/cli" "15.6.3" - "@nrwl/tao" "15.6.3" + "@nrwl/cli" "15.5.3" + "@nrwl/tao" "15.5.3" "@parcel/watcher" "2.0.4" "@yarnpkg/lockfile" "^1.1.0" "@yarnpkg/parsers" "^3.0.0-rc.18" @@ -10821,13 +10835,13 @@ nx@15.6.3: yargs "^17.6.2" yargs-parser "21.1.1" -"nx@>=15.4.2 < 16": - version "15.5.3" - resolved "https://registry.npmjs.org/nx/-/nx-15.5.3.tgz#bf6252e7d9e17121dd82dec4f6fce319b9e005fa" - integrity sha512-PHB8VbiBLP108xb+yR8IGEsYWr7OcmDDOjHL+73oP4lVjyPgT8wdTMe6tI5LdBgv+KZ+0kiThK3ckvcPsfgvLQ== +nx@15.6.3: + version "15.6.3" + resolved "https://registry.npmjs.org/nx/-/nx-15.6.3.tgz#900087bce38c6e5975660c23ebd41ead1bf54f98" + integrity sha512-3t0A0GPLNen1yPAyE+VGZ3nkAzZYb5nfXtAcx8SHBlKq4u42yBY3khBmP1y4Og3jhIwFIj7J7Npeh8ZKrthmYQ== dependencies: - "@nrwl/cli" "15.5.3" - "@nrwl/tao" "15.5.3" + "@nrwl/cli" "15.6.3" + "@nrwl/tao" "15.6.3" "@parcel/watcher" "2.0.4" "@yarnpkg/lockfile" "^1.1.0" "@yarnpkg/parsers" "^3.0.0-rc.18" pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy