• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts.projectSystem {
2    describe("unittests:: tsserver:: completions", () => {
3        it("works", () => {
4            const aTs: File = {
5                path: "/a.ts",
6                content: "export const foo = 0;",
7            };
8            const bTs: File = {
9                path: "/b.ts",
10                content: "foo",
11            };
12            const tsconfig: File = {
13                path: "/tsconfig.json",
14                content: "{}",
15            };
16
17            const session = createSession(createServerHost([aTs, bTs, tsconfig]));
18            openFilesForSession([aTs, bTs], session);
19
20            const requestLocation: protocol.FileLocationRequestArgs = {
21                file: bTs.path,
22                line: 1,
23                offset: 3,
24            };
25
26            const response = executeSessionRequest<protocol.CompletionsRequest, protocol.CompletionInfoResponse>(session, protocol.CommandTypes.CompletionInfo, {
27                ...requestLocation,
28                includeExternalModuleExports: true,
29                prefix: "foo",
30            });
31            const entry: protocol.CompletionEntry = {
32                hasAction: true,
33                insertText: undefined,
34                isRecommended: undefined,
35                kind: ScriptElementKind.constElement,
36                kindModifiers: ScriptElementKindModifier.exportedModifier,
37                name: "foo",
38                replacementSpan: undefined,
39                isPackageJsonImport: undefined,
40                sortText: Completions.SortText.AutoImportSuggestions,
41                source: "/a",
42                jsDoc: undefined,
43                displayParts: [
44                    {
45                        text: "const",
46                        kind: "keyword"
47                    },{
48                        text: " ",
49                        kind: "space"
50                    },{
51                        text: "foo",
52                        kind: "localName"
53                    },{
54                        text: ":",
55                        kind: "punctuation"
56                    },{
57                        text: " ",
58                        kind: "space"
59                    },{
60                        text: "0",
61                        kind: "stringLiteral"
62                    }
63                ]
64            };
65            assert.deepEqual<protocol.CompletionInfo | undefined>(response, {
66                isGlobalCompletion: true,
67                isMemberCompletion: false,
68                isNewIdentifierLocation: false,
69                optionalReplacementSpan: { start: { line: 1, offset: 1 }, end: { line: 1, offset: 4 } },
70                entries: [entry],
71            });
72
73            const detailsRequestArgs: protocol.CompletionDetailsRequestArgs = {
74                ...requestLocation,
75                entryNames: [{ name: "foo", source: "/a" }],
76            };
77
78            const detailsResponse = executeSessionRequest<protocol.CompletionDetailsRequest, protocol.CompletionDetailsResponse>(session, protocol.CommandTypes.CompletionDetails, detailsRequestArgs);
79            const detailsCommon: protocol.CompletionEntryDetails & CompletionEntryDetails = {
80                displayParts: [
81                    keywordPart(SyntaxKind.ConstKeyword),
82                    spacePart(),
83                    displayPart("foo", SymbolDisplayPartKind.localName),
84                    punctuationPart(SyntaxKind.ColonToken),
85                    spacePart(),
86                    displayPart("0", SymbolDisplayPartKind.stringLiteral),
87                ],
88                documentation: emptyArray,
89                kind: ScriptElementKind.constElement,
90                kindModifiers: ScriptElementKindModifier.exportedModifier,
91                name: "foo",
92                source: [{ text: "./a", kind: "text" }],
93                tags: undefined,
94            };
95            assert.deepEqual<readonly protocol.CompletionEntryDetails[] | undefined>(detailsResponse, [
96                {
97                    codeActions: [
98                        {
99                            description: `Import 'foo' from module "./a"`,
100                            changes: [
101                                {
102                                    fileName: "/b.ts",
103                                    textChanges: [
104                                        {
105                                            start: { line: 1, offset: 1 },
106                                            end: { line: 1, offset: 1 },
107                                            newText: 'import { foo } from "./a";\n\n',
108                                        },
109                                    ],
110                                },
111                            ],
112                            commands: undefined,
113                        },
114                    ],
115                    ...detailsCommon,
116                },
117            ]);
118
119            interface CompletionDetailsFullRequest extends protocol.FileLocationRequest {
120                readonly command: protocol.CommandTypes.CompletionDetailsFull;
121                readonly arguments: protocol.CompletionDetailsRequestArgs;
122            }
123            interface CompletionDetailsFullResponse extends protocol.Response {
124                readonly body?: readonly CompletionEntryDetails[];
125            }
126            const detailsFullResponse = executeSessionRequest<CompletionDetailsFullRequest, CompletionDetailsFullResponse>(session, protocol.CommandTypes.CompletionDetailsFull, detailsRequestArgs);
127            assert.deepEqual<readonly CompletionEntryDetails[] | undefined>(detailsFullResponse, [
128                {
129                    codeActions: [
130                        {
131                            description: `Import 'foo' from module "./a"`,
132                            changes: [
133                                {
134                                    fileName: "/b.ts",
135                                    textChanges: [createTextChange(createTextSpan(0, 0), 'import { foo } from "./a";\n\n')],
136                                },
137                            ],
138                            commands: undefined,
139                        }
140                    ],
141                    ...detailsCommon,
142                }
143            ]);
144        });
145
146        it("works add jsDoc info at interface getCompletionsAtPosition", () => {
147            const aTs: File = {
148                path: "/a.ts",
149                content: `export class Test {
150/**
151 * @devices tv
152 */
153public test(): void {
154
155}
156}`
157            };
158            const bTs: File = {
159                path: "/b.ts",
160                content: `import { Test } from "./a";
161const test = new Test();
162test.`,
163            };
164            const tsconfig: File = {
165                path: "/tsconfig.json",
166                content: "{}",
167            };
168
169            const session = createSession(createServerHost([aTs, bTs, tsconfig]));
170            openFilesForSession([aTs, bTs], session);
171
172            const requestLocation: protocol.FileLocationRequestArgs = {
173                file: bTs.path,
174                line: 3,
175                offset: 6
176            };
177
178            const response = executeSessionRequest<protocol.CompletionsRequest, protocol.CompletionInfoResponse>(session, protocol.CommandTypes.CompletionInfo, {
179                ...requestLocation,
180                includeExternalModuleExports: true,
181                prefix: ".",
182            });
183            const entry: protocol.CompletionEntry = {
184                hasAction: undefined,
185                insertText: undefined,
186                isRecommended: undefined,
187                kind: ScriptElementKind.memberFunctionElement,
188                kindModifiers: ScriptElementKindModifier.publicMemberModifier,
189                name: "test",
190                replacementSpan: undefined,
191                isPackageJsonImport: undefined,
192                sortText: Completions.SortText.LocationPriority,
193                source: undefined,
194                jsDoc: [{
195                    name: "devices",
196                    text: "tv"
197                }],
198                displayParts: [
199                    {
200                        text: "(",
201                        kind: "punctuation"
202                    },{
203                        text: "method",
204                        kind: "text"
205                    },{
206                        text: ")",
207                        kind: "punctuation"
208                    },{
209                        text: " ",
210                        kind: "space"
211                    },{
212                        text: "Test",
213                        kind: "className"
214                    },{
215                        text: ".",
216                        kind: "punctuation"
217                    },{
218                        text: "test",
219                        kind: "methodName"
220                    },{
221                        text: "(",
222                        kind: "punctuation"
223                    },{
224                        text: ")",
225                        kind: "punctuation"
226                    },{
227                        text: ":",
228                        kind: "punctuation"
229                    },{
230                        text: " ",
231                        kind: "space"
232                    },{
233                        text: "void",
234                        kind: "keyword"
235                    }
236                ]
237            };
238            assert.deepEqual<protocol.CompletionInfo | undefined>(response, {
239                isGlobalCompletion: false,
240                isMemberCompletion: true,
241                isNewIdentifierLocation: false,
242                optionalReplacementSpan: undefined,
243                entries: [entry],
244            });
245        });
246
247        it("works when files are included from two different drives of windows", () => {
248            const projectRoot = "e:/myproject";
249            const appPackage: File = {
250                path: `${projectRoot}/package.json`,
251                content: JSON.stringify({
252                    name: "test",
253                    version: "0.1.0",
254                    dependencies: {
255                        "react": "^16.12.0",
256                        "react-router-dom": "^5.1.2",
257                    }
258                })
259            };
260            const appFile: File = {
261                path: `${projectRoot}/src/app.js`,
262                content: `import React from 'react';
263import {
264  BrowserRouter as Router,
265} from "react-router-dom";
266`
267            };
268            const localNodeModules = `${projectRoot}/node_modules`;
269            const localAtTypes = `${localNodeModules}/@types`;
270            const localReactPackage: File = {
271                path: `${localAtTypes}/react/package.json`,
272                content: JSON.stringify({
273                    name: "@types/react",
274                    version: "16.9.14",
275                })
276            };
277            const localReact: File = {
278                path: `${localAtTypes}/react/index.d.ts`,
279                content: `import * as PropTypes from 'prop-types';
280`
281            };
282            const localReactRouterDomPackage: File = {
283                path: `${localNodeModules}/react-router-dom/package.json`,
284                content: JSON.stringify({
285                    name: "react-router-dom",
286                    version: "5.1.2",
287                })
288            };
289            const localReactRouterDom: File = {
290                path: `${localNodeModules}/react-router-dom/index.js`,
291                content: `export function foo() {}`
292            };
293            const localPropTypesPackage: File = {
294                path: `${localAtTypes}/prop-types/package.json`,
295                content: JSON.stringify({
296                    name: "@types/prop-types",
297                    version: "15.7.3",
298                })
299            };
300            const localPropTypes: File = {
301                path: `${localAtTypes}/prop-types/index.d.ts`,
302                content: `export type ReactComponentLike =
303    | string
304    | ((props: any, context?: any) => any)
305    | (new (props: any, context?: any) => any);
306`
307            };
308
309            const globalCacheLocation = `c:/typescript`;
310            const globalAtTypes = `${globalCacheLocation}/node_modules/@types`;
311            const globalReactRouterDomPackage: File = {
312                path: `${globalAtTypes}/react-router-dom/package.json`,
313                content: JSON.stringify({
314                    name: "@types/react-router-dom",
315                    version: "5.1.2",
316                })
317            };
318            const globalReactRouterDom: File = {
319                path: `${globalAtTypes}/react-router-dom/index.d.ts`,
320                content: `import * as React from 'react';
321export interface BrowserRouterProps {
322    basename?: string;
323    getUserConfirmation?: ((message: string, callback: (ok: boolean) => void) => void);
324    forceRefresh?: boolean;
325    keyLength?: number;
326}`
327            };
328            const globalReactPackage: File = {
329                path: `${globalAtTypes}/react/package.json`,
330                content: localReactPackage.content
331            };
332            const globalReact: File = {
333                path: `${globalAtTypes}/react/index.d.ts`,
334                content: localReact.content
335            };
336
337            const filesInProject = [
338                appFile,
339                localReact,
340                localPropTypes,
341                globalReactRouterDom,
342                globalReact,
343            ];
344            const files = [
345                ...filesInProject,
346                appPackage, libFile,
347                localReactPackage,
348                localReactRouterDomPackage, localReactRouterDom,
349                localPropTypesPackage,
350                globalReactRouterDomPackage,
351                globalReactPackage,
352            ];
353
354            const host = createServerHost(files, { windowsStyleRoot: "c:/" });
355            const session = createSession(host, {
356                typingsInstaller: new TestTypingsInstaller(globalCacheLocation, /*throttleLimit*/ 5, host),
357            });
358            const service = session.getProjectService();
359            openFilesForSession([appFile], session);
360            checkNumberOfProjects(service, { inferredProjects: 1 });
361            const windowsStyleLibFilePath = "c:/" + libFile.path.substring(1);
362            checkProjectActualFiles(service.inferredProjects[0], filesInProject.map(f => f.path).concat(windowsStyleLibFilePath));
363            session.executeCommandSeq<protocol.CompletionsRequest>({
364                command: protocol.CommandTypes.CompletionInfo,
365                arguments: {
366                    file: appFile.path,
367                    line: 5,
368                    offset: 1,
369                    includeExternalModuleExports: true,
370                    includeInsertTextCompletions: true
371                }
372            });
373        });
374    });
375}
376