• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts.projectSystem {
2    describe("unittests:: tsserver:: jsdoc @link ", () => {
3        const config: File = {
4            path: "/a/tsconfig.json",
5            content: `{
6"compilerOptions": {
7"checkJs": true,
8"noEmit": true
9}
10"files": ["someFile1.js"]
11}
12`
13        };
14        function assertQuickInfoJSDoc(file: File, options: {
15            displayPartsForJSDoc: boolean,
16            command: protocol.CommandTypes,
17            tags: string | unknown[] | undefined,
18            documentation: string | unknown[]
19        }) {
20
21            const { command, displayPartsForJSDoc, tags, documentation } = options;
22            const session = createSession(createServerHost([file, config]));
23            session.getProjectService().setHostConfiguration({ preferences: { displayPartsForJSDoc } });
24            openFilesForSession([file], session);
25            const indexOfX = file.content.indexOf("x");
26            const quickInfo = session.executeCommandSeq<protocol.QuickInfoRequest>({
27                command: command as protocol.CommandTypes.Quickinfo,
28                arguments: {
29                    file: file.path,
30                    position: indexOfX,
31                } as protocol.FileLocationRequestArgs
32            }).response;
33            const summaryAndLocation = command === protocol.CommandTypes.Quickinfo ? {
34                displayString: "var x: number",
35                start: {
36                    line: 3,
37                    offset: 5,
38                },
39                end: {
40                    line: 3,
41                    offset: 6,
42                }
43            } : {
44                displayParts: [{
45                    kind: "keyword",
46                    text: "var",
47                }, {
48                    kind: "space",
49                    text: " ",
50                }, {
51                    kind: "localName",
52                    text: "x",
53                }, {
54                    kind: "punctuation",
55                    text: ":",
56                }, {
57                    kind: "space",
58                    text: " ",
59                }, {
60                    kind: "keyword",
61                    text: "number",
62                }],
63                textSpan: {
64                    length: 1,
65                    start: 38,
66                }
67            };
68            assert.deepEqual(quickInfo, {
69                kind: "var",
70                kindModifiers: "",
71                ...summaryAndLocation,
72                documentation,
73                tags
74            });
75        }
76
77        const linkInTag: File = {
78            path: "/a/someFile1.js",
79            content: `class C { }
80/** @wat {@link C} */
81var x = 1`
82        };
83        const linkInComment: File = {
84            path: "/a/someFile1.js",
85            content: `class C { }
86     /** {@link C} */
87var x = 1
88;`
89        };
90
91        it("for quickinfo, should provide display parts plus a span for a working link in a tag", () => {
92            assertQuickInfoJSDoc(linkInTag, {
93                command: protocol.CommandTypes.Quickinfo,
94                displayPartsForJSDoc: true,
95                documentation: [],
96                tags: [{
97                    name: "wat",
98                    text: [{
99                        kind: "text",
100                        text: "",
101                    }, {
102                        kind: "link",
103                        text: "{@link ",
104                    }, {
105                        kind: "linkName",
106                        target: {
107                            end: {
108                                line: 1,
109                                offset: 12,
110                            },
111                            file: "/a/someFile1.js",
112                            start: {
113                                line: 1,
114                                offset: 1,
115                            },
116                        },
117                        text: "C",
118                    }, {
119                        kind: "link",
120                        text: "}",
121                    }]
122                }],
123            });
124        });
125        it("for quickinfo, should provide a string for a working link in a tag", () => {
126            assertQuickInfoJSDoc(linkInTag, {
127                command: protocol.CommandTypes.Quickinfo,
128                displayPartsForJSDoc: false,
129                documentation: "",
130                tags: [{
131                    name: "wat",
132                    text: "{@link C}"
133                }],
134            });
135        });
136        it("for quickinfo, should provide display parts for a working link in a comment", () => {
137            assertQuickInfoJSDoc(linkInComment, {
138                command: protocol.CommandTypes.Quickinfo,
139                displayPartsForJSDoc: true,
140                documentation: [{
141                    kind: "text",
142                    text: "",
143                }, {
144                    kind: "link",
145                    text: "{@link ",
146                }, {
147                    kind: "linkName",
148                    target: {
149                        end: {
150                            line: 1,
151                            offset: 12,
152                        },
153                        file: "/a/someFile1.js",
154                        start: {
155                            line: 1,
156                            offset: 1,
157                        },
158                    },
159                    text: "C",
160                }, {
161                    kind: "link",
162                    text: "}",
163                }],
164                tags: [],
165            });
166        });
167        it("for quickinfo, should provide a string for a working link in a comment", () => {
168            assertQuickInfoJSDoc(linkInComment, {
169                command: protocol.CommandTypes.Quickinfo,
170                displayPartsForJSDoc: false,
171                documentation: "{@link C}",
172                tags: [],
173            });
174        });
175
176        it("for quickinfo-full, should provide display parts plus a span for a working link in a tag", () => {
177            assertQuickInfoJSDoc(linkInTag, {
178                command: protocol.CommandTypes.QuickinfoFull,
179                displayPartsForJSDoc: true,
180                documentation: [],
181                tags: [{
182                    name: "wat",
183                    text: [{
184                        kind: "text",
185                        text: "",
186                    }, {
187                        kind: "link",
188                        text: "{@link ",
189                    }, {
190                        kind: "linkName",
191                        target: {
192                            fileName: "/a/someFile1.js",
193                            textSpan: {
194                                length: 11,
195                                start: 0
196                            },
197                        },
198                        text: "C",
199                    }, {
200                        kind: "link",
201                        text: "}",
202                    }]
203                }],
204            });
205        });
206        it("for quickinfo-full, should provide a string for a working link in a tag", () => {
207            assertQuickInfoJSDoc(linkInTag, {
208                command: protocol.CommandTypes.QuickinfoFull,
209                displayPartsForJSDoc: false,
210                documentation: [],
211                tags: [{
212                    name: "wat",
213                    text: "{@link C}"
214                }],
215            });
216        });
217        it("for quickinfo-full, should provide display parts plus a span for a working link in a comment", () => {
218            assertQuickInfoJSDoc(linkInComment, {
219                command: protocol.CommandTypes.QuickinfoFull,
220                displayPartsForJSDoc: true,
221                documentation: [{
222                    kind: "text",
223                    text: "",
224                }, {
225                    kind: "link",
226                    text: "{@link ",
227                }, {
228                    kind: "linkName",
229                    target: {
230                        fileName: "/a/someFile1.js",
231                        textSpan: {
232                            length: 11,
233                            start: 0
234                        },
235                    },
236                    text: "C",
237                }, {
238                    kind: "link",
239                    text: "}",
240                }],
241                tags: undefined,
242            });
243        });
244        it("for quickinfo-full, should provide a string for a working link in a comment", () => {
245            assertQuickInfoJSDoc(linkInComment, {
246                command: protocol.CommandTypes.QuickinfoFull,
247                displayPartsForJSDoc: false,
248                documentation: [{
249                        kind: "text",
250                        text: "",
251                    }, {
252                        kind: "link",
253                        text: "{@link ",
254                    }, {
255                        kind: "linkName",
256                        target: {
257                            fileName: "/a/someFile1.js",
258                            textSpan: {
259                                length: 11,
260                                start: 0
261                            },
262                        },
263                        text: "C",
264                    }, {
265                        kind: "link",
266                        text: "}",
267                    }],
268                tags: [],
269            });
270        });
271
272        function assertSignatureHelpJSDoc(options: {
273            displayPartsForJSDoc: boolean,
274            command: protocol.CommandTypes,
275            documentation: string | unknown[],
276            tags: unknown[]
277        }) {
278            const linkInParamTag: File = {
279                path: "/a/someFile1.js",
280                content: `class C { }
281/** @param y - {@link C} */
282function x(y) { }
283x(1)`
284            };
285
286            const { command, displayPartsForJSDoc, documentation, tags } = options;
287            const session = createSession(createServerHost([linkInParamTag, config]));
288            session.getProjectService().setHostConfiguration({ preferences: { displayPartsForJSDoc } });
289            openFilesForSession([linkInParamTag], session);
290            const indexOfX = linkInParamTag.content.lastIndexOf("1");
291            const signatureHelp = session.executeCommandSeq<protocol.SignatureHelpRequest>({
292                command: command as protocol.CommandTypes.SignatureHelp,
293                arguments: {
294                    triggerReason: {
295                        kind: "invoked"
296                    },
297                    file: linkInParamTag.path,
298                    position: indexOfX,
299                } as protocol.SignatureHelpRequestArgs
300            }).response;
301            const applicableSpan = command === protocol.CommandTypes.SignatureHelp ? {
302                end: {
303                    line: 4,
304                    offset: 4
305                },
306                start: {
307                    line: 4,
308                    offset: 3
309                }
310            } : {
311                length: 1,
312                start: 60
313            };
314            assert.deepEqual(signatureHelp, {
315                applicableSpan,
316                argumentCount: 1,
317                argumentIndex: 0,
318                selectedItemIndex: 0,
319                items: [{
320                    documentation: [],
321                    isVariadic: false,
322                    parameters: [{
323                        displayParts: [{
324                            kind: "parameterName",
325                            text: "y"
326                        }, {
327                            kind: "punctuation",
328                            text: ":"
329                        }, {
330                            kind: "space",
331                            text: " "
332                        }, {
333                            kind: "keyword",
334                            text: "any"
335                        }],
336                        documentation,
337                        isOptional: false,
338                        isRest: false,
339                        name: "y"
340                    }],
341                    prefixDisplayParts: [
342                        {
343                            kind: "functionName",
344                            text: "x",
345                        },
346                        {
347                            kind: "punctuation",
348                            text: "(",
349                        },
350                    ],
351                    separatorDisplayParts: [
352                        {
353                            kind: "punctuation",
354                            text: ",",
355                        },
356                        {
357                            kind: "space",
358                            text: " ",
359                        },
360                    ],
361                    suffixDisplayParts: [
362                        {
363                            kind: "punctuation",
364                            text: ")",
365                        },
366                        {
367                            kind: "punctuation",
368                            text: ":",
369                        },
370                        {
371                            kind: "space",
372                            text: " ",
373                        },
374                        {
375                            kind: "keyword",
376                            text: "void",
377                        }
378                    ],
379                    tags,
380                }],
381            });
382        }
383        it("for signature help, should provide a string for a working link in a comment", () => {
384            assertSignatureHelpJSDoc({
385                command: protocol.CommandTypes.SignatureHelp,
386                displayPartsForJSDoc: false,
387                tags: [{
388                    name: "param",
389                    text: "y - {@link C}"
390                }],
391                documentation: [{
392                    kind: "text",
393                    text: "- "
394                }, {
395                    kind: "link",
396                    text: "{@link "
397                }, {
398                    kind: "linkName",
399                    target: {
400                        file: "/a/someFile1.js",
401                        start: {
402                            line: 1,
403                            offset: 1
404                        },
405                        end: {
406                            line: 1,
407                            offset: 12
408                        }
409                    },
410                    text: "C"
411                }, {
412                    kind: "link",
413                    text: "}"
414                }],
415            });
416        });
417        it("for signature help, should provide display parts for a working link in a comment", () => {
418            const tags = [{
419                name: "param",
420                text: [{
421                    kind: "parameterName",
422                    text: "y"
423                }, {
424                    kind: "space",
425                    text: " "
426                }, {
427                    kind: "text",
428                    text: "- "
429                }, {
430                    kind: "link",
431                    text: "{@link "
432                }, {
433                    kind: "linkName",
434                    target: {
435                        file: "/a/someFile1.js",
436                        start: {
437                            line: 1,
438                            offset: 1
439                        },
440                        end: {
441                            line: 1,
442                            offset: 12
443                        }
444                    },
445                    text: "C"
446                }, {
447                    kind: "link",
448                    text: "}"
449                }]
450            }];
451            assertSignatureHelpJSDoc({
452                command: protocol.CommandTypes.SignatureHelp,
453                displayPartsForJSDoc: true,
454                tags,
455                documentation: tags[0].text.slice(2)
456            });
457        });
458        it("for signature help-full, should provide a string for a working link in a comment", () => {
459            assertSignatureHelpJSDoc({
460                command: protocol.CommandTypes.SignatureHelpFull,
461                displayPartsForJSDoc: false,
462                tags: [{
463                    name: "param",
464                    text: "y - {@link C}"
465                }],
466                documentation: [{
467                    kind: "text",
468                    text: "- "
469                }, {
470                    kind: "link",
471                    text: "{@link "
472                }, {
473                    kind: "linkName",
474                    target: {
475                        fileName: "/a/someFile1.js",
476                        textSpan: {
477                            length: 11,
478                            start: 0
479                        }
480                    },
481                    text: "C"
482                }, {
483                    kind: "link",
484                    text: "}"
485                }],
486            });
487        });
488        it("for signature help-full, should provide display parts for a working link in a comment", () => {
489            const tags = [{
490                name: "param",
491                text: [{
492                    kind: "parameterName",
493                    text: "y"
494                }, {
495                    kind: "space",
496                    text: " "
497                }, {
498                    kind: "text",
499                    text: "- "
500                }, {
501                    kind: "link",
502                    text: "{@link "
503                }, {
504                    kind: "linkName",
505                    target: {
506                        fileName: "/a/someFile1.js",
507                        textSpan: {
508                            length: 11,
509                            start: 0
510                        }
511                    },
512                    text: "C"
513                }, {
514                    kind: "link",
515                    text: "}"
516                }]
517            }];
518            assertSignatureHelpJSDoc({
519                command: protocol.CommandTypes.SignatureHelpFull,
520                displayPartsForJSDoc: true,
521                tags,
522                documentation: tags[0].text.slice(2),
523            });
524        });
525
526        function assertCompletionsJSDoc(options: {
527            displayPartsForJSDoc: boolean,
528            command: protocol.CommandTypes,
529            tags: unknown[]
530        }) {
531            const linkInParamJSDoc: File = {
532                path: "/a/someFile1.js",
533                content: `class C { }
534/** @param x - see {@link C} */
535function foo (x) { }
536foo`
537            };
538            const { command, displayPartsForJSDoc, tags } = options;
539            const session = createSession(createServerHost([linkInParamJSDoc, config]));
540            session.getProjectService().setHostConfiguration({ preferences: { displayPartsForJSDoc } });
541            openFilesForSession([linkInParamJSDoc], session);
542            const indexOfFoo = linkInParamJSDoc.content.lastIndexOf("fo");
543            const completions = session.executeCommandSeq<protocol.CompletionDetailsRequest>({
544                command: command as protocol.CommandTypes.CompletionDetails,
545                arguments: {
546                    entryNames: ["foo"],
547                    file: linkInParamJSDoc.path,
548                    position: indexOfFoo,
549                } as protocol.CompletionDetailsRequestArgs
550            }).response;
551            assert.deepEqual(completions, [{
552                codeActions: undefined,
553                displayParts: [{
554                    kind: "keyword",
555                    text: "function",
556                }, {
557                    kind: "space",
558                    text: " ",
559                }, {
560                    kind: "functionName",
561                    text: "foo",
562                }, {
563                    kind: "punctuation",
564                    text: "(",
565                }, {
566                    kind: "parameterName",
567                    text: "x",
568                }, {
569                    kind: "punctuation",
570                    text: ":",
571                }, {
572                    kind: "space",
573                    text: " ",
574                }, {
575                    kind: "keyword",
576                    text: "any",
577                }, {
578                    kind: "punctuation",
579                    text: ")",
580                }, {
581                    kind: "punctuation",
582                    text: ":",
583                }, {
584                    kind: "space",
585                    text: " ",
586                }, {
587                    kind: "keyword",
588                    text: "void",
589                }],
590                documentation: [],
591                kind: "function",
592                kindModifiers: "",
593                name: "foo",
594                source: undefined,
595                sourceDisplay: undefined,
596                tags,
597            }]);
598        }
599        it("for completions, should provide display parts for a working link in a comment", () => {
600            assertCompletionsJSDoc({
601                command: protocol.CommandTypes.CompletionDetails,
602                displayPartsForJSDoc: true,
603                tags: [{
604                    name: "param",
605                    text: [{
606                        kind: "parameterName",
607                        text: "x"
608                    }, {
609                        kind: "space",
610                        text: " "
611                    }, {
612                        kind: "text",
613                        text: "- see "
614                    }, {
615                        kind: "link",
616                        text: "{@link "
617                    }, {
618                        kind: "linkName",
619                        target: {
620                            file: "/a/someFile1.js",
621                            end: {
622                                line: 1,
623                                offset: 12,
624                            },
625                            start: {
626                                line: 1,
627                                offset: 1,
628                            }
629                        },
630                        text: "C"
631                    }, {
632                        kind: "link",
633                        text: "}"
634                    }],
635                }],
636            });
637        });
638        it("for completions, should provide a string for a working link in a comment", () => {
639            assertCompletionsJSDoc({
640                command: protocol.CommandTypes.CompletionDetails,
641                displayPartsForJSDoc: false,
642                tags: [{
643                    name: "param",
644                    text: "x - see {@link C}",
645                }],
646            });
647        });
648        it("for completions-full, should provide display parts for a working link in a comment", () => {
649            assertCompletionsJSDoc({
650                command: protocol.CommandTypes.CompletionDetailsFull,
651                displayPartsForJSDoc: true,
652                tags: [{
653                    name: "param",
654                    text: [{
655                        kind: "parameterName",
656                        text: "x"
657                    }, {
658                        kind: "space",
659                        text: " "
660                    }, {
661                        kind: "text",
662                        text: "- see "
663                    }, {
664                        kind: "link",
665                        text: "{@link "
666                    }, {
667                        kind: "linkName",
668                        target: {
669                            fileName: "/a/someFile1.js",
670                            textSpan: {
671                                length: 11,
672                                start: 0
673                            }
674                        },
675                        text: "C"
676                    }, {
677                        kind: "link",
678                        text: "}"
679                    }],
680                }],
681            });
682        });
683        it("for completions-full, should provide a string for a working link in a comment", () => {
684            assertCompletionsJSDoc({
685                command: protocol.CommandTypes.CompletionDetailsFull,
686                displayPartsForJSDoc: false,
687                tags: [{
688                    name: "param",
689                    text: "x - see {@link C}",
690                }],
691            });
692        });
693    });
694
695    describe("unittests:: tsserver:: jsDoc tag check", () => {
696        it("works jsDoc tag check", () => {
697            const aUser: File = {
698                path: "/a.ts",
699                content: `import { y } from "./b";
700    y.test()
701    y.test2()
702`
703            };
704            const bUser: File = {
705                path: "/b.ts",
706                content: `
707export class y {
708/**
709 * @ignore
710 */
711static test(): void {
712
713}
714/**
715 * @systemApi
716 */
717static test2(): void {
718
719}
720}`
721            };
722            const tsconfigFile: File = {
723                path: "/tsconfig.json",
724                content: JSON.stringify({
725                    compilerOptions: {
726                        target: "es6",
727                        module: "es6",
728                        baseUrl: "./",  // all paths are relative to the baseUrl
729                        paths: {
730                            "~/*": ["*"]   // resolve any `~/foo/bar` to `<baseUrl>/foo/bar`
731                        }
732                    },
733                    exclude: [
734                        "api",
735                        "build",
736                        "node_modules",
737                        "public",
738                        "seeds",
739                        "sql_updates",
740                        "tests.build"
741                    ]
742                })
743            };
744
745            const projectFiles = [aUser, bUser, tsconfigFile];
746            const host = createServerHost(projectFiles);
747            host.getFileCheckedModuleInfo = (sourceFilePath: string) => {
748                Debug.log(sourceFilePath);
749                return {
750                    fileNeedCheck: true,
751                    checkPayload: undefined,
752                    currentFileName: "",
753                }
754            }
755            host.getJsDocNodeCheckedConfig = (jsDocFileCheckInfo: FileCheckModuleInfo, sourceFilePath: string) => {
756                Debug.log(jsDocFileCheckInfo.fileNeedCheck.toString());
757                Debug.log(sourceFilePath);
758                return {
759                    nodeNeedCheck: true,
760                    checkConfig: [{
761                        tagName: ["ignore"],
762                        message: "This API has been ignored. exercise caution when using this API.",
763                        needConditionCheck: false,
764                        type: DiagnosticCategory.Warning,
765                        specifyCheckConditionFuncName: "",
766                        tagNameShouldExisted: false,
767                    },{
768                        tagName: ["systemApi"],
769                        message: "This API is used to develop system apps. exercise caution when using this API.",
770                        needConditionCheck: false,
771                        type: DiagnosticCategory.Warning,
772                        specifyCheckConditionFuncName: "",
773                        tagNameShouldExisted: false,
774                    }]
775                };
776            };
777            host.getJsDocNodeConditionCheckedResult = (jsDocFileCheckInfo: FileCheckModuleInfo,  jsDocs: JSDocTagInfo[]) => {
778                Debug.log(jsDocFileCheckInfo.fileNeedCheck.toString());
779                Debug.log(jsDocs.toString());
780                return {
781                    valid: false,
782                    message: "",
783                    type: DiagnosticCategory.Warning
784                };
785            };
786            const session = createSession(host);
787            const projectService = session.getProjectService();
788            const { configFileName } = projectService.openClientFile(aUser.path);
789
790            assert.isDefined(configFileName, `should find config`);
791
792            const project = projectService.configuredProjects.get(tsconfigFile.path)!;
793            const response = project.getLanguageService().getSuggestionDiagnostics(aUser.path);
794            assert.deepEqual(response[0].messageText, "This API has been ignored. exercise caution when using this API.");
795            assert.deepEqual(response[1].messageText, "This API is used to develop system apps. exercise caution when using this API.");
796        });
797    });
798}
799