• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts.projectSystem {
2    describe("unittests:: tsserver:: Open-file", () => {
3        it("can be reloaded with empty content", () => {
4            const f = {
5                path: "/a/b/app.ts",
6                content: "let x = 1"
7            };
8            const projectFileName = "externalProject";
9            const host = createServerHost([f]);
10            const projectService = createProjectService(host);
11            // create a project
12            projectService.openExternalProject({ projectFileName, rootFiles: [toExternalFile(f.path)], options: {} });
13            projectService.checkNumberOfProjects({ externalProjects: 1 });
14
15            const p = projectService.externalProjects[0];
16            // force to load the content of the file
17            p.updateGraph();
18
19            const scriptInfo = p.getScriptInfo(f.path)!;
20            checkSnapLength(scriptInfo.getSnapshot(), f.content.length);
21
22            // open project and replace its content with empty string
23            projectService.openClientFile(f.path, "");
24            checkSnapLength(scriptInfo.getSnapshot(), 0);
25        });
26        function checkSnapLength(snap: IScriptSnapshot, expectedLength: number) {
27            assert.equal(snap.getLength(), expectedLength, "Incorrect snapshot size");
28        }
29
30        function verifyOpenFileWorks(useCaseSensitiveFileNames: boolean) {
31            const file1: File = {
32                path: "/a/b/src/app.ts",
33                content: "let x = 10;"
34            };
35            const file2: File = {
36                path: "/a/B/lib/module2.ts",
37                content: "let z = 10;"
38            };
39            const configFile: File = {
40                path: "/a/b/tsconfig.json",
41                content: ""
42            };
43            const configFile2: File = {
44                path: "/a/tsconfig.json",
45                content: ""
46            };
47            const host = createServerHost([file1, file2, configFile, configFile2], {
48                useCaseSensitiveFileNames
49            });
50            const service = createProjectService(host);
51
52            // Open file1 -> configFile
53            verifyConfigFileName(file1, "/a", configFile);
54            verifyConfigFileName(file1, "/a/b", configFile);
55            verifyConfigFileName(file1, "/a/B", configFile);
56
57            // Open file2 use root "/a/b"
58            verifyConfigFileName(file2, "/a", useCaseSensitiveFileNames ? configFile2 : configFile);
59            verifyConfigFileName(file2, "/a/b", useCaseSensitiveFileNames ? configFile2 : configFile);
60            verifyConfigFileName(file2, "/a/B", useCaseSensitiveFileNames ? undefined : configFile);
61
62            function verifyConfigFileName(file: File, projectRoot: string, expectedConfigFile: File | undefined) {
63                const { configFileName } = service.openClientFile(file.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectRoot);
64                assert.equal(configFileName, expectedConfigFile && expectedConfigFile.path);
65                service.closeClientFile(file.path);
66            }
67        }
68        it("works when project root is used with case-sensitive system", () => {
69            verifyOpenFileWorks(/*useCaseSensitiveFileNames*/ true);
70        });
71
72        it("works when project root is used with case-insensitive system", () => {
73            verifyOpenFileWorks(/*useCaseSensitiveFileNames*/ false);
74        });
75
76        it("uses existing project even if project refresh is pending", () => {
77            const projectFolder = "/user/someuser/projects/myproject";
78            const aFile: File = {
79                path: `${projectFolder}/src/a.ts`,
80                content: "export const x = 0;"
81            };
82            const configFile: File = {
83                path: `${projectFolder}/tsconfig.json`,
84                content: "{}"
85            };
86            const files = [aFile, configFile, libFile];
87            const host = createServerHost(files);
88            const service = createProjectService(host);
89            service.openClientFile(aFile.path, /*fileContent*/ undefined, ScriptKind.TS, projectFolder);
90            verifyProject();
91
92            const bFile: File = {
93                path: `${projectFolder}/src/b.ts`,
94                content: `export {}; declare module "./a" {  export const y: number; }`
95            };
96            files.push(bFile);
97            host.writeFile(bFile.path, bFile.content);
98            service.openClientFile(bFile.path, /*fileContent*/ undefined, ScriptKind.TS, projectFolder);
99            verifyProject();
100
101            function verifyProject() {
102                assert.isDefined(service.configuredProjects.get(configFile.path));
103                const project = service.configuredProjects.get(configFile.path)!;
104                checkProjectActualFiles(project, files.map(f => f.path));
105            }
106        });
107
108        it("can open same file again", () => {
109            const projectFolder = "/user/someuser/projects/myproject";
110            const aFile: File = {
111                path: `${projectFolder}/src/a.ts`,
112                content: "export const x = 0;"
113            };
114            const configFile: File = {
115                path: `${projectFolder}/tsconfig.json`,
116                content: "{}"
117            };
118            const files = [aFile, configFile, libFile];
119            const host = createServerHost(files);
120            const service = createProjectService(host);
121            verifyProject(aFile.content);
122            verifyProject(`${aFile.content}export const y = 10;`);
123
124            function verifyProject(aFileContent: string) {
125                service.openClientFile(aFile.path, aFileContent, ScriptKind.TS, projectFolder);
126                const project = service.configuredProjects.get(configFile.path)!;
127                checkProjectActualFiles(project, files.map(f => f.path));
128                assert.equal(project.getCurrentProgram()?.getSourceFile(aFile.path)!.text, aFileContent);
129            }
130        });
131
132        it("when file makes edits to add/remove comment directives, they are handled correcrly", () => {
133            const file: File = {
134                path: `${tscWatch.projectRoot}/file.ts`,
135                content: `const x = 10;
136function foo() {
137    // @ts-ignore
138    let y: string = x;
139    return y;
140}
141function bar() {
142    // @ts-ignore
143    let z : string = x;
144    return z;
145}
146foo();
147bar();`
148            };
149            const host = createServerHost([file, libFile]);
150            const session = createSession(host, { canUseEvents: true, });
151            openFilesForSession([file], session);
152            verifyGetErrRequestNoErrors({ session, host, files: [file] });
153
154            // Remove first ts-ignore and check only first error is reported
155            const tsIgnoreComment = `// @ts-ignore`;
156            const locationOfTsIgnore = protocolTextSpanFromSubstring(file.content, tsIgnoreComment);
157            session.executeCommandSeq<protocol.UpdateOpenRequest>({
158                command: protocol.CommandTypes.UpdateOpen,
159                arguments: {
160                    changedFiles: [{
161                        fileName: file.path,
162                        textChanges: [{
163                            newText: "             ",
164                            ...locationOfTsIgnore
165                        }]
166                    }]
167                }
168            });
169            const locationOfY = protocolTextSpanFromSubstring(file.content, "y");
170            verifyGetErrRequest({
171                session,
172                host,
173                expected: [
174                    {
175                        file,
176                        syntax: [],
177                        semantic: [
178                            createDiagnostic(locationOfY.start, locationOfY.end, Diagnostics.Type_0_is_not_assignable_to_type_1, ["number", "string"]),
179                        ],
180                        suggestion: []
181                    },
182                ]
183            });
184
185            // Revert the change and no errors should be reported
186            session.executeCommandSeq<protocol.UpdateOpenRequest>({
187                command: protocol.CommandTypes.UpdateOpen,
188                arguments: {
189                    changedFiles: [{
190                        fileName: file.path,
191                        textChanges: [{
192                            newText: tsIgnoreComment,
193                            ...locationOfTsIgnore
194                        }]
195                    }]
196                }
197            });
198            verifyGetErrRequestNoErrors({ session, host, files: [file] });
199        });
200    });
201}
202