• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts {
2    const _chai: typeof import("chai") = require("chai");
3    const expect: typeof _chai.expect = _chai.expect;
4    describe("unittests:: services:: languageService", () => {
5        const files: {[index: string]: string} = {
6            "foo.ts": `import Vue from "./vue";
7import Component from "./vue-class-component";
8import { vueTemplateHtml } from "./variables";
9
10@Component({
11    template: vueTemplateHtml,
12})
13class Carousel<T> extends Vue {
14}`,
15            "variables.ts": `export const vueTemplateHtml = \`<div></div>\`;`,
16            "vue.d.ts": `export namespace Vue { export type Config = { template: string }; }`,
17            "vue-class-component.d.ts": `import Vue from "./vue";
18export function Component(x: Config): any;`
19        };
20
21        function createLanguageService() {
22            return ts.createLanguageService({
23                getCompilationSettings() {
24                    return {};
25                },
26                getScriptFileNames() {
27                    return ["foo.ts", "variables.ts", "vue.d.ts", "vue-class-component.d.ts"];
28                },
29                getScriptVersion(_fileName) {
30                    return "";
31                },
32                getScriptSnapshot(fileName) {
33                    if (fileName === ".ts") {
34                        return ScriptSnapshot.fromString("");
35                    }
36                    return ScriptSnapshot.fromString(files[fileName] || "");
37                },
38                getCurrentDirectory: () => ".",
39                getDefaultLibFileName(options) {
40                    return getDefaultLibFilePath(options);
41                },
42                fileExists: name => !!files[name],
43                readFile: name => files[name]
44            });
45        }
46        // Regression test for GH #18245 - bug in single line comment writer caused a debug assertion when attempting
47        //  to write an alias to a module's default export was referrenced across files and had no default export
48        it("should be able to create a language service which can respond to deinition requests without throwing", () => {
49            const languageService = createLanguageService();
50            const definitions = languageService.getDefinitionAtPosition("foo.ts", 160); // 160 is the latter `vueTemplateHtml` position
51            expect(definitions).to.exist; // eslint-disable-line @typescript-eslint/no-unused-expressions
52        });
53
54        it("getEmitOutput on language service has way to force dts emit", () => {
55            const languageService = createLanguageService();
56            assert.deepEqual(
57                languageService.getEmitOutput(
58                    "foo.ts",
59                    /*emitOnlyDtsFiles*/ true
60                ),
61                {
62                    emitSkipped: true,
63                    diagnostics: emptyArray,
64                    outputFiles: emptyArray,
65                }
66            );
67
68            assert.deepEqual(
69                languageService.getEmitOutput(
70                    "foo.ts",
71                    /*emitOnlyDtsFiles*/ true,
72                    /*forceDtsEmit*/ true
73                ),
74                {
75                    emitSkipped: false,
76                    diagnostics: emptyArray,
77                    outputFiles: [{
78                        name: "foo.d.ts",
79                        text: "export {};\r\n",
80                        writeByteOrderMark: false
81                    }],
82                }
83            );
84        });
85
86        describe("detects program upto date correctly", () => {
87            function verifyProgramUptoDate(useProjectVersion: boolean) {
88                let projectVersion = "1";
89                const files = new Map<string, { version: string, text: string; }>();
90                files.set("/project/root.ts", { version: "1", text: `import { foo } from "./other"` });
91                files.set("/project/other.ts", { version: "1", text: `export function foo() { }` });
92                files.set("/lib/lib.d.ts", { version: "1", text: projectSystem.libFile.content });
93                const host: LanguageServiceHost = {
94                    useCaseSensitiveFileNames: returnTrue,
95                    getCompilationSettings: getDefaultCompilerOptions,
96                    fileExists: path => files.has(path),
97                    readFile: path => files.get(path)?.text,
98                    getProjectVersion: !useProjectVersion ? undefined : () => projectVersion,
99                    getScriptFileNames: () => ["/project/root.ts"],
100                    getScriptVersion: path => files.get(path)?.version || "",
101                    getScriptSnapshot: path => {
102                        const text = files.get(path)?.text;
103                        return text ? ScriptSnapshot.fromString(text) : undefined;
104                    },
105                    getCurrentDirectory: () => "/project",
106                    getDefaultLibFileName: () => "/lib/lib.d.ts"
107                };
108                const ls = ts.createLanguageService(host);
109                const program1 = ls.getProgram()!;
110                const program2 = ls.getProgram()!;
111                assert.strictEqual(program1, program2);
112                verifyProgramFiles(program1);
113
114                // Change other
115                projectVersion = "2";
116                files.set("/project/other.ts", { version: "2", text: `export function foo() { } export function bar() { }` });
117                const program3 = ls.getProgram()!;
118                assert.notStrictEqual(program2, program3);
119                verifyProgramFiles(program3);
120
121                // change root
122                projectVersion = "3";
123                files.set("/project/root.ts", { version: "2", text: `import { foo, bar } from "./other"` });
124                const program4 = ls.getProgram()!;
125                assert.notStrictEqual(program3, program4);
126                verifyProgramFiles(program4);
127
128                function verifyProgramFiles(program: Program) {
129                    assert.deepEqual(
130                        program.getSourceFiles().map(f => f.fileName),
131                        ["/lib/lib.d.ts", "/project/other.ts", "/project/root.ts"]
132                    );
133                }
134            }
135            it("when host implements getProjectVersion", () => {
136                verifyProgramUptoDate(/*useProjectVersion*/ true);
137            });
138            it("when host does not implement getProjectVersion", () => {
139                verifyProgramUptoDate(/*useProjectVersion*/ false);
140            });
141        });
142
143        describe("detects program upto date when new file is added to the referenced project", () => {
144            function setup(useSourceOfProjectReferenceRedirect: (() => boolean) | undefined) {
145                const config1: TestFSWithWatch.File = {
146                    path: `${tscWatch.projectRoot}/projects/project1/tsconfig.json`,
147                    content: JSON.stringify({
148                        compilerOptions: {
149                            module: "none",
150                            composite: true
151                        },
152                        exclude: ["temp"]
153                    })
154                };
155                const class1: TestFSWithWatch.File = {
156                    path: `${tscWatch.projectRoot}/projects/project1/class1.ts`,
157                    content: `class class1 {}`
158                };
159                const class1Dts: TestFSWithWatch.File = {
160                    path: `${tscWatch.projectRoot}/projects/project1/class1.d.ts`,
161                    content: `declare class class1 {}`
162                };
163                const config2: TestFSWithWatch.File = {
164                    path: `${tscWatch.projectRoot}/projects/project2/tsconfig.json`,
165                    content: JSON.stringify({
166                        compilerOptions: {
167                            module: "none",
168                            composite: true
169                        },
170                        references: [
171                            { path: "../project1" }
172                        ]
173                    })
174                };
175                const class2: TestFSWithWatch.File = {
176                    path: `${tscWatch.projectRoot}/projects/project2/class2.ts`,
177                    content: `class class2 {}`
178                };
179                const system = projectSystem.createServerHost([config1, class1, class1Dts, config2, class2, projectSystem.libFile]);
180                const result = getParsedCommandLineOfConfigFile(`${tscWatch.projectRoot}/projects/project2/tsconfig.json`, /*optionsToExtend*/ undefined, {
181                    useCaseSensitiveFileNames: true,
182                    fileExists: path => system.fileExists(path),
183                    readFile: path => system.readFile(path),
184                    getCurrentDirectory: () => system.getCurrentDirectory(),
185                    readDirectory: (path, extensions, excludes, includes, depth) => system.readDirectory(path, extensions, excludes, includes, depth),
186                    onUnRecoverableConfigFileDiagnostic: noop,
187                })!;
188                const host: LanguageServiceHost = {
189                    useCaseSensitiveFileNames: returnTrue,
190                    useSourceOfProjectReferenceRedirect,
191                    getCompilationSettings: () => result.options,
192                    fileExists: path => system.fileExists(path),
193                    readFile: path => system.readFile(path),
194                    getScriptFileNames: () => result.fileNames,
195                    getScriptVersion: path => {
196                        const text = system.readFile(path);
197                        return text !== undefined ? system.createHash(path) : "";
198                    },
199                    getScriptSnapshot: path => {
200                        const text = system.readFile(path);
201                        return text ? ScriptSnapshot.fromString(text) : undefined;
202                    },
203                    readDirectory: (path, extensions, excludes, includes, depth) => system.readDirectory(path, extensions, excludes, includes, depth),
204                    getCurrentDirectory: () => system.getCurrentDirectory(),
205                    getDefaultLibFileName: () => projectSystem.libFile.path,
206                    getProjectReferences: () => result.projectReferences,
207                };
208                const ls = ts.createLanguageService(host);
209                return { system, ls, class1, class1Dts, class2 };
210            }
211            it("detects program upto date when new file is added to the referenced project", () => {
212                const { ls, system, class1, class2 } = setup(returnTrue);
213                assert.deepEqual(
214                    ls.getProgram()!.getSourceFiles().map(f => f.fileName),
215                    [projectSystem.libFile.path, class1.path, class2.path]
216                );
217                // Add new file to referenced project
218                const class3 = `${tscWatch.projectRoot}/projects/project1/class3.ts`;
219                system.writeFile(class3, `class class3 {}`);
220                const program = ls.getProgram()!;
221                assert.deepEqual(
222                    program.getSourceFiles().map(f => f.fileName),
223                    [projectSystem.libFile.path, class1.path, class3, class2.path]
224                );
225                // Add excluded file to referenced project
226                system.ensureFileOrFolder({ path: `${tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` });
227                assert.strictEqual(ls.getProgram(), program);
228                // Add output from new class to referenced project
229                system.writeFile(`${tscWatch.projectRoot}/projects/project1/class3.d.ts`, `declare class class3 {}`);
230                assert.strictEqual(ls.getProgram(), program);
231            });
232
233            it("detects program upto date when new file is added to the referenced project without useSourceOfProjectReferenceRedirect", () => {
234                const { ls, system, class1Dts, class2 } = setup(/*useSourceOfProjectReferenceRedirect*/ undefined);
235                const program1 = ls.getProgram()!;
236                assert.deepEqual(
237                    program1.getSourceFiles().map(f => f.fileName),
238                    [projectSystem.libFile.path, class1Dts.path, class2.path]
239                );
240                // Add new file to referenced project
241                const class3 = `${tscWatch.projectRoot}/projects/project1/class3.ts`;
242                system.writeFile(class3, `class class3 {}`);
243                assert.notStrictEqual(ls.getProgram(), program1);
244                assert.deepEqual(
245                    ls.getProgram()!.getSourceFiles().map(f => f.fileName),
246                    [projectSystem.libFile.path, class1Dts.path, class2.path]
247                );
248                // Add class3 output
249                const class3Dts = `${tscWatch.projectRoot}/projects/project1/class3.d.ts`;
250                system.writeFile(class3Dts, `declare class class3 {}`);
251                const program2 = ls.getProgram()!;
252                assert.deepEqual(
253                    program2.getSourceFiles().map(f => f.fileName),
254                    [projectSystem.libFile.path, class1Dts.path, class3Dts, class2.path]
255                );
256                // Add excluded file to referenced project
257                system.ensureFileOrFolder({ path: `${tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` });
258                assert.strictEqual(ls.getProgram(), program2);
259                // Delete output from new class to referenced project
260                system.deleteFile(class3Dts);
261                assert.deepEqual(
262                    ls.getProgram()!.getSourceFiles().map(f => f.fileName),
263                    [projectSystem.libFile.path, class1Dts.path, class2.path]
264                );
265                // Write output again
266                system.writeFile(class3Dts, `declare class class3 {}`);
267                assert.deepEqual(
268                    ls.getProgram()!.getSourceFiles().map(f => f.fileName),
269                    [projectSystem.libFile.path, class1Dts.path, class3Dts, class2.path]
270                );
271            });
272        });
273    });
274}
275