Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: update auto-import cache logic for TS 4.7 #1360

Merged
merged 2 commits into from May 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/vue-typescript/src/typescriptRuntime.ts
Expand Up @@ -43,7 +43,9 @@ export function createTypeScriptRuntime(options: {
let lastProjectVersion: string | undefined;
let tsProjectVersion = 0;

injectCacheLogicToLanguageServiceHost(ts, tsLsHost, tsLsRaw);
if (!options.isVueTsc) {
injectCacheLogicToLanguageServiceHost(ts, tsLsHost, tsLsRaw);
}

return {
vueLsHost: options.vueLsHost,
Expand Down
68 changes: 39 additions & 29 deletions packages/vue-typescript/src/utils/moduleSpecifierCache.ts
@@ -1,4 +1,4 @@
import type { Path, UserPreferences } from 'typescript/lib/tsserverlibrary';
import type { Path, UserPreferences, SourceFile } from 'typescript/lib/tsserverlibrary';

export interface ModulePath {
path: string;
Expand All @@ -9,35 +9,45 @@ export interface ModulePath {
export interface ResolvedModuleSpecifierInfo {
modulePaths: readonly ModulePath[] | undefined;
moduleSpecifiers: readonly string[] | undefined;
isAutoImportable: boolean | undefined;
isBlockedByPackageJsonDependencies: boolean | undefined;
}

export interface ModuleSpecifierOptions {
overrideImportMode?: SourceFile["impliedNodeFormat"];
}

export interface ModuleSpecifierCache {
get(fromFileName: Path, toFileName: Path, preferences: UserPreferences): Readonly<ResolvedModuleSpecifierInfo> | undefined;
set(fromFileName: Path, toFileName: Path, preferences: UserPreferences, modulePaths: readonly ModulePath[], moduleSpecifiers: readonly string[]): void;
setIsAutoImportable(fromFileName: Path, toFileName: Path, preferences: UserPreferences, isAutoImportable: boolean): void;
setModulePaths(fromFileName: Path, toFileName: Path, preferences: UserPreferences, modulePaths: readonly ModulePath[]): void;
get(fromFileName: Path, toFileName: Path, preferences: UserPreferences, options: ModuleSpecifierOptions): Readonly<ResolvedModuleSpecifierInfo> | undefined;
set(fromFileName: Path, toFileName: Path, preferences: UserPreferences, options: ModuleSpecifierOptions, modulePaths: readonly ModulePath[], moduleSpecifiers: readonly string[]): void;
setBlockedByPackageJsonDependencies(fromFileName: Path, toFileName: Path, preferences: UserPreferences, options: ModuleSpecifierOptions, isBlockedByPackageJsonDependencies: boolean): void;
setModulePaths(fromFileName: Path, toFileName: Path, preferences: UserPreferences, options: ModuleSpecifierOptions, modulePaths: readonly ModulePath[]): void;
clear(): void;
count(): number;
}

// export interface ModuleSpecifierResolutionCacheHost {
// watchNodeModulesForPackageJsonChanges(directoryPath: string): FileWatcher;
// }
export interface FileWatcher {
close(): void;
}

export interface ModuleSpecifierResolutionCacheHost {
watchNodeModulesForPackageJsonChanges(directoryPath: string): FileWatcher;
}

export const nodeModulesPathPart = "/node_modules/";

export function createModuleSpecifierCache(
// host: ModuleSpecifierResolutionCacheHost
): ModuleSpecifierCache {
// let containedNodeModulesWatchers: Map<string, FileWatcher> | undefined; // TODO
let containedNodeModulesWatchers: Map<string, FileWatcher> | undefined;
let cache: Map<Path, ResolvedModuleSpecifierInfo> | undefined;
let currentKey: string | undefined;
const result: ModuleSpecifierCache = {
get(fromFileName, toFileName, preferences) {
if (!cache || currentKey !== key(fromFileName, preferences)) return undefined;
get(fromFileName, toFileName, preferences, options) {
if (!cache || currentKey !== key(fromFileName, preferences, options)) return undefined;
return cache.get(toFileName);
},
set(fromFileName, toFileName, preferences, modulePaths, moduleSpecifiers) {
ensureCache(fromFileName, preferences).set(toFileName, createInfo(modulePaths, moduleSpecifiers, /*isAutoImportable*/ true));
set(fromFileName, toFileName, preferences, options, modulePaths, moduleSpecifiers) {
ensureCache(fromFileName, preferences, options).set(toFileName, createInfo(modulePaths, moduleSpecifiers, /*isBlockedByPackageJsonDependencies*/ false));

// If any module specifiers were generated based off paths in node_modules,
// a package.json file in that package was read and is an input to the cached.
Expand All @@ -59,30 +69,30 @@ export function createModuleSpecifierCache(
}
}
},
setModulePaths(fromFileName, toFileName, preferences, modulePaths) {
const cache = ensureCache(fromFileName, preferences);
setModulePaths(fromFileName, toFileName, preferences, options, modulePaths) {
const cache = ensureCache(fromFileName, preferences, options);
const info = cache.get(toFileName);
if (info) {
info.modulePaths = modulePaths;
}
else {
cache.set(toFileName, createInfo(modulePaths, /*moduleSpecifiers*/ undefined, /*isAutoImportable*/ undefined));
cache.set(toFileName, createInfo(modulePaths, /*moduleSpecifiers*/ undefined, /*isBlockedByPackageJsonDependencies*/ undefined));
}
},
setIsAutoImportable(fromFileName, toFileName, preferences, isAutoImportable) {
const cache = ensureCache(fromFileName, preferences);
setBlockedByPackageJsonDependencies(fromFileName, toFileName, preferences, options, isBlockedByPackageJsonDependencies) {
const cache = ensureCache(fromFileName, preferences, options);
const info = cache.get(toFileName);
if (info) {
info.isAutoImportable = isAutoImportable;
info.isBlockedByPackageJsonDependencies = isBlockedByPackageJsonDependencies;
}
else {
cache.set(toFileName, createInfo(/*modulePaths*/ undefined, /*moduleSpecifiers*/ undefined, isAutoImportable));
cache.set(toFileName, createInfo(/*modulePaths*/ undefined, /*moduleSpecifiers*/ undefined, isBlockedByPackageJsonDependencies));
}
},
clear() {
// containedNodeModulesWatchers?.forEach(watcher => watcher.close());
containedNodeModulesWatchers?.forEach(watcher => watcher.close());
cache?.clear();
// containedNodeModulesWatchers?.clear();
containedNodeModulesWatchers?.clear();
currentKey = undefined;
},
count() {
Expand All @@ -94,24 +104,24 @@ export function createModuleSpecifierCache(
// }
return result;

function ensureCache(fromFileName: Path, preferences: UserPreferences) {
const newKey = key(fromFileName, preferences);
function ensureCache(fromFileName: Path, preferences: UserPreferences, options: ModuleSpecifierOptions) {
const newKey = key(fromFileName, preferences, options);
if (cache && (currentKey !== newKey)) {
result.clear();
}
currentKey = newKey;
return cache ||= new Map();
}

function key(fromFileName: Path, preferences: UserPreferences) {
return `${fromFileName},${preferences.importModuleSpecifierEnding},${preferences.importModuleSpecifierPreference}`;
function key(fromFileName: Path, preferences: UserPreferences, options: ModuleSpecifierOptions) {
return `${fromFileName},${preferences.importModuleSpecifierEnding},${preferences.importModuleSpecifierPreference},${options.overrideImportMode}`;
}

function createInfo(
modulePaths: readonly ModulePath[] | undefined,
moduleSpecifiers: readonly string[] | undefined,
isAutoImportable: boolean | undefined,
isBlockedByPackageJsonDependencies: boolean | undefined,
): ResolvedModuleSpecifierInfo {
return { modulePaths, moduleSpecifiers, isAutoImportable };
return { modulePaths, moduleSpecifiers, isBlockedByPackageJsonDependencies };
}
}
46 changes: 23 additions & 23 deletions packages/vue-typescript/src/utils/packageJsonCache.ts
@@ -1,22 +1,28 @@
import type { Path, server } from 'typescript/lib/tsserverlibrary';

export const enum PackageJsonDependencyGroup {
Dependencies = 1 << 0,
DevDependencies = 1 << 1,
PeerDependencies = 1 << 2,
OptionalDependencies = 1 << 3,
All = Dependencies | DevDependencies | PeerDependencies | OptionalDependencies,
interface PackageJsonPathFields {
typings?: string;
types?: string;
typesVersions?: Map<string, Map<string, string[]>>;
main?: string;
tsconfig?: string;
type?: string;
imports?: object;
exports?: object;
name?: string;
}

interface VersionPaths {
version: string;
paths: Map<string, string[]>;
}

export interface PackageJsonInfo {
fileName: string;
parseable: boolean;
dependencies?: Map<string, string>;
devDependencies?: Map<string, string>;
peerDependencies?: Map<string, string>;
optionalDependencies?: Map<string, string>;
get(dependencyName: string, inGroups?: PackageJsonDependencyGroup): string | undefined;
has(dependencyName: string, inGroups?: PackageJsonDependencyGroup): boolean;
packageDirectory: string;
packageJsonContent: PackageJsonPathFields;
versionPaths: VersionPaths | undefined;
/** false: resolved to nothing. undefined: not yet resolved */
resolvedEntrypoints: string[] | false | undefined;
}

export const enum Ternary {
Expand All @@ -38,20 +44,15 @@ export interface PackageJsonCache {
searchDirectoryAndAncestors(directory: Path): void;
}

export function canCreatePackageJsonCache(ts: typeof import('typescript/lib/tsserverlibrary')) {
return 'createPackageJsonInfo' in ts && 'getDirectoryPath' in ts && 'combinePaths' in ts && 'tryFileExists' in ts && 'forEachAncestorDirectory' in ts;
}

export function createPackageJsonCache(
ts: typeof import('typescript/lib/tsserverlibrary'),
host: ProjectService,
): PackageJsonCache {
const { createPackageJsonInfo, getDirectoryPath, combinePaths, tryFileExists, forEachAncestorDirectory } = ts as any;
const packageJsons = new Map<string, PackageJsonInfo>();
const directoriesWithoutPackageJson = new Map<string, true>();
const packageJsons = new Map<Path, PackageJsonInfo>();
const directoriesWithoutPackageJson = new Map<Path, true>();
return {
addOrUpdate,
// @ts-expect-error
forEach: packageJsons.forEach.bind(packageJsons),
get: packageJsons.get.bind(packageJsons),
delete: fileName => {
Expand All @@ -63,8 +64,7 @@ export function createPackageJsonCache(
},
directoryHasPackageJson,
searchDirectoryAndAncestors: directory => {
// @ts-expect-error
forEachAncestorDirectory(directory, ancestor => {
forEachAncestorDirectory(directory, (ancestor: Path) => {
if (directoryHasPackageJson(ancestor) !== Ternary.Maybe) {
return true;
}
Expand Down
11 changes: 7 additions & 4 deletions packages/vue-typescript/src/utils/ts.ts
@@ -1,6 +1,6 @@
import type * as ts from 'typescript/lib/tsserverlibrary';
import { createModuleSpecifierCache } from './moduleSpecifierCache';
import { createPackageJsonCache, canCreatePackageJsonCache, PackageJsonInfo, Ternary } from './packageJsonCache';
import { createPackageJsonCache, PackageJsonInfo, Ternary } from './packageJsonCache';
import * as path from 'path';

export function injectCacheLogicToLanguageServiceHost(
Expand All @@ -10,8 +10,9 @@ export function injectCacheLogicToLanguageServiceHost(
) {

const versionNums = ts.version.split('.').map(s => Number(s));
if (versionNums[0] > 4 || (versionNums[0] === 4 && versionNums[1] >= 7)) {
console.log('Please update to v0.35.0 or higher for TypeScript version:', ts.version);
const greaterThan47 = versionNums[0] > 4 || (versionNums[0] === 4 && versionNums[1] >= 7);
if (!greaterThan47) {
console.log('Please downgrade to v0.34.17 or lower for TypeScript version:', ts.version);
return;
}

Expand All @@ -29,7 +30,6 @@ export function injectCacheLogicToLanguageServiceHost(
|| !_getDirectoryPath
|| !_toPath
|| !_createGetCanonicalFileName
|| !canCreatePackageJsonCache(ts)
) return;

const moduleSpecifierCache = createModuleSpecifierCache();
Expand All @@ -40,6 +40,9 @@ export function injectCacheLogicToLanguageServiceHost(
getPackageJsonAutoImportProvider() {
return service.getProgram();
},
getGlobalTypingsCacheLocation() {
return undefined;
},
});
const packageJsonCache = createPackageJsonCache(ts, {
...host,
Expand Down