• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts.projectSystem {
2    const packageJson: File = {
3        path: "/package.json",
4        content: `{ "dependencies": { "mobx": "*" } }`
5    };
6    const aTs: File = {
7        path: "/src/a.ts",
8        content: "export const foo = 0;",
9    };
10    const bTs: File = {
11        path: "/src/b.ts",
12        content: "foo",
13    };
14    const cTs: File = {
15        path: "/src/c.ts",
16        content: "import ",
17    };
18    const bSymlink: SymLink = {
19        path: "/src/b-link.ts",
20        symLink: "./b.ts",
21    };
22    const tsconfig: File = {
23        path: "/tsconfig.json",
24        content: `{ "include": ["src"] }`,
25    };
26    const ambientDeclaration: File = {
27        path: "/src/ambient.d.ts",
28        content: "declare module 'ambient' {}"
29    };
30    const mobxPackageJson: File = {
31        path: "/node_modules/mobx/package.json",
32        content: `{ "name": "mobx", "version": "1.0.0" }`
33    };
34    const mobxDts: File = {
35        path: "/node_modules/mobx/index.d.ts",
36        content: "export declare function observable(): unknown;"
37    };
38
39    describe("unittests:: tsserver:: moduleSpecifierCache", () => {
40        it("caches importability within a file", () => {
41            const { moduleSpecifierCache } = setup();
42            assert.isFalse(moduleSpecifierCache.get(bTs.path as Path, aTs.path as Path, {}, {})?.isBlockedByPackageJsonDependencies);
43        });
44
45        it("caches module specifiers within a file", () => {
46            const { moduleSpecifierCache, triggerCompletions } = setup();
47            // Completion at an import statement will calculate and cache module specifiers
48            triggerCompletions({ file: cTs.path, line: 1, offset: cTs.content.length + 1 });
49            const mobxCache = moduleSpecifierCache.get(cTs.path as Path, mobxDts.path as Path, {}, {});
50            assert.deepEqual(mobxCache, {
51                modulePaths: [{
52                    path: mobxDts.path,
53                    isInNodeModules: true,
54                    isRedirect: false
55                }],
56                moduleSpecifiers: ["mobx"],
57                isBlockedByPackageJsonDependencies: false,
58            });
59        });
60
61        it("invalidates module specifiers when changes happen in contained node_modules directories", () => {
62            const { host, session, moduleSpecifierCache, triggerCompletions } = setup(host => createLoggerWithInMemoryLogs(host));
63            // Completion at an import statement will calculate and cache module specifiers
64            triggerCompletions({ file: cTs.path, line: 1, offset: cTs.content.length + 1 });
65            host.writeFile("/node_modules/.staging/mobx-12345678/package.json", "{}");
66            host.runQueuedTimeoutCallbacks();
67            assert.equal(moduleSpecifierCache.count(), 0);
68            baselineTsserverLogs("moduleSpecifierCache", "invalidates module specifiers when changes happen in contained node_modules directories", session);
69        });
70
71        it("does not invalidate the cache when new files are added", () => {
72            const { host, moduleSpecifierCache } = setup();
73            host.writeFile("/src/a2.ts", aTs.content);
74            host.runQueuedTimeoutCallbacks();
75            assert.isFalse(moduleSpecifierCache.get(bTs.path as Path, aTs.path as Path, {}, {})?.isBlockedByPackageJsonDependencies);
76        });
77
78        it("invalidates the cache when symlinks are added or removed", () => {
79            const { host, moduleSpecifierCache } = setup();
80            host.renameFile(bSymlink.path, "/src/b-link2.ts");
81            host.runQueuedTimeoutCallbacks();
82            assert.equal(moduleSpecifierCache.count(), 0);
83        });
84
85        it("invalidates the cache when local package.json changes", () => {
86            const { host, moduleSpecifierCache } = setup();
87            host.writeFile("/package.json", `{}`);
88            host.runQueuedTimeoutCallbacks();
89            assert.equal(moduleSpecifierCache.count(), 0);
90        });
91
92        it("invalidates the cache when module resolution settings change", () => {
93            const { host, moduleSpecifierCache } = setup();
94            host.writeFile(tsconfig.path, `{ "compilerOptions": { "moduleResolution": "classic" }, "include": ["src"] }`);
95            host.runQueuedTimeoutCallbacks();
96            assert.equal(moduleSpecifierCache.count(), 0);
97        });
98
99        it("invalidates the cache when user preferences change", () => {
100            const { moduleSpecifierCache, session, triggerCompletions } = setup();
101            const preferences: UserPreferences = { importModuleSpecifierPreference: "project-relative" };
102
103            assert.ok(getWithPreferences({}));
104            executeSessionRequest<protocol.ConfigureRequest, protocol.ConfigureResponse>(session, protocol.CommandTypes.Configure, { preferences });
105            // Nothing changes yet
106            assert.ok(getWithPreferences({}));
107            assert.isUndefined(getWithPreferences(preferences));
108            // Completions will request (getting nothing) and set the cache with new preferences
109            triggerCompletions({ file: bTs.path, line: 1, offset: 3 });
110            assert.isUndefined(getWithPreferences({}));
111            assert.ok(getWithPreferences(preferences));
112
113            // Test other affecting preference
114            executeSessionRequest<protocol.ConfigureRequest, protocol.ConfigureResponse>(session, protocol.CommandTypes.Configure, {
115                preferences: { importModuleSpecifierEnding: "js" },
116            });
117            triggerCompletions({ file: bTs.path, line: 1, offset: 3 });
118            assert.isUndefined(getWithPreferences(preferences));
119
120            function getWithPreferences(preferences: UserPreferences) {
121                return moduleSpecifierCache.get(bTs.path as Path, aTs.path as Path, preferences, {});
122            }
123        });
124    });
125
126    function setup(createLogger?: (host: TestServerHost) => Logger) {
127        const host = createServerHost([aTs, bTs, cTs, bSymlink, ambientDeclaration, tsconfig, packageJson, mobxPackageJson, mobxDts]);
128        const session = createSession(host, createLogger && { logger: createLogger(host) });
129        openFilesForSession([aTs, bTs, cTs], session);
130        const projectService = session.getProjectService();
131        const project = configuredProjectAt(projectService, 0);
132        executeSessionRequest<protocol.ConfigureRequest, protocol.ConfigureResponse>(session, protocol.CommandTypes.Configure, {
133            preferences: {
134                includeCompletionsForImportStatements: true,
135                includeCompletionsForModuleExports: true,
136                includeCompletionsWithInsertText: true,
137                includeCompletionsWithSnippetText: true,
138            },
139        });
140        triggerCompletions({ file: bTs.path, line: 1, offset: 3 });
141
142        return { host, project, projectService, session, moduleSpecifierCache: project.getModuleSpecifierCache(), triggerCompletions };
143
144        function triggerCompletions(requestLocation: protocol.FileLocationRequestArgs) {
145            executeSessionRequest<protocol.CompletionsRequest, protocol.CompletionInfoResponse>(session, protocol.CommandTypes.CompletionInfo, {
146                ...requestLocation,
147            });
148        }
149    }
150}
151