Skip to content

Commit

Permalink
feat: update auto-import cache logic for TS 4.7 (#1360)
Browse files Browse the repository at this point in the history
  • Loading branch information
johnsoncodehk committed May 28, 2022
1 parent a8cc362 commit f92fae1
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 92 deletions.
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

0 comments on commit f92fae1

Please sign in to comment.