Skip to content

Commit 3bc4178

Browse files
authored
Fix issue with wildcard with supported extensions when plugins add external files and override getScriptKind to add script kinds for the additional extensions (#55716)
1 parent a0c51b5 commit 3bc4178

File tree

5 files changed

+286
-3
lines changed

5 files changed

+286
-3
lines changed

src/compiler/watchUtilities.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@ import {
2020
FileWatcherCallback,
2121
FileWatcherEventKind,
2222
find,
23+
getAllowJSCompilerOption,
2324
getBaseFileName,
2425
getDirectoryPath,
2526
getNormalizedAbsolutePath,
27+
getResolveJsonModule,
2628
hasExtension,
2729
identity,
2830
insertSorted,
@@ -44,6 +46,7 @@ import {
4446
removeIgnoredPath,
4547
returnNoopFileWatcher,
4648
returnTrue,
49+
ScriptKind,
4750
setSysLog,
4851
SortedArray,
4952
SortedReadonlyArray,
@@ -563,6 +566,7 @@ export interface IsIgnoredFileFromWildCardWatchingInput {
563566
useCaseSensitiveFileNames: boolean;
564567
writeLog: (s: string) => void;
565568
toPath: (fileName: string) => Path;
569+
getScriptKind?: (fileName: string) => ScriptKind;
566570
}
567571
/** @internal */
568572
export function isIgnoredFileFromWildCardWatching({
@@ -577,6 +581,7 @@ export function isIgnoredFileFromWildCardWatching({
577581
useCaseSensitiveFileNames,
578582
writeLog,
579583
toPath,
584+
getScriptKind,
580585
}: IsIgnoredFileFromWildCardWatchingInput): boolean {
581586
const newPath = removeIgnoredPath(fileOrDirectoryPath);
582587
if (!newPath) {
@@ -588,8 +593,12 @@ export function isIgnoredFileFromWildCardWatching({
588593
if (fileOrDirectoryPath === watchedDirPath) return false;
589594

590595
// If the the added or created file or directory is not supported file name, ignore the file
591-
// But when watched directory is added/removed, we need to reload the file list
592-
if (hasExtension(fileOrDirectoryPath) && !isSupportedSourceFileName(fileOrDirectory, options, extraFileExtensions)) {
596+
if (
597+
hasExtension(fileOrDirectoryPath) && !(
598+
isSupportedSourceFileName(fileOrDirectory, options, extraFileExtensions) ||
599+
isSupportedScriptKind()
600+
)
601+
) {
593602
writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileOrDirectory}`);
594603
return true;
595604
}
@@ -634,6 +643,25 @@ export function isIgnoredFileFromWildCardWatching({
634643
builderProgram.getState().fileInfos.has(file) :
635644
!!find(program as readonly string[], rootFile => toPath(rootFile) === file);
636645
}
646+
647+
function isSupportedScriptKind() {
648+
if (!getScriptKind) return false;
649+
const scriptKind = getScriptKind(fileOrDirectory);
650+
switch (scriptKind) {
651+
case ScriptKind.TS:
652+
case ScriptKind.TSX:
653+
case ScriptKind.Deferred:
654+
case ScriptKind.External:
655+
return true;
656+
case ScriptKind.JS:
657+
case ScriptKind.JSX:
658+
return getAllowJSCompilerOption(options);
659+
case ScriptKind.JSON:
660+
return getResolveJsonModule(options);
661+
case ScriptKind.Unknown:
662+
return false;
663+
}
664+
}
637665
}
638666

639667
function isBuilderProgram<T extends BuilderProgram>(program: Program | T): program is T {

src/server/editorServices.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1526,6 +1526,7 @@ export class ProjectService {
15261526
useCaseSensitiveFileNames: this.host.useCaseSensitiveFileNames,
15271527
writeLog: s => this.logger.info(s),
15281528
toPath: s => this.toPath(s),
1529+
getScriptKind: configuredProjectForConfig ? (fileName => configuredProjectForConfig.getScriptKind(fileName)) : undefined,
15291530
})
15301531
) return;
15311532

src/server/project.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -663,7 +663,7 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
663663
}
664664

665665
getScriptKind(fileName: string) {
666-
const info = this.getOrCreateScriptInfoAndAttachToProject(fileName);
666+
const info = this.projectService.getScriptInfoForPath(this.toPath(fileName));
667667
return (info && info.scriptKind)!; // TODO: GH#18217
668668
}
669669

src/testRunner/unittests/tsserver/plugins.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,3 +218,72 @@ describe("unittests:: tsserver:: plugins:: overriding getSupportedCodeFixes", ()
218218
baselineTsserverLogs("plugins", "getSupportedCodeFixes can be proxied", session);
219219
});
220220
});
221+
222+
describe("unittests:: tsserver:: plugins:: supportedExtensions::", () => {
223+
it("new files with non ts extensions and wildcard matching", () => {
224+
const aTs: File = {
225+
path: "/user/username/projects/myproject/a.ts",
226+
content: `export const a = 10;`,
227+
};
228+
const bVue: File = {
229+
path: "/user/username/projects/myproject/b.vue",
230+
content: "bVue file",
231+
};
232+
const config: File = {
233+
path: "/user/username/projects/myproject/tsconfig.json",
234+
content: JSON.stringify(
235+
{
236+
compilerOptions: { composite: true },
237+
include: ["*.ts", "*.vue"],
238+
},
239+
undefined,
240+
" ",
241+
),
242+
};
243+
const host = createServerHost([aTs, bVue, config, libFile]);
244+
host.require = () => {
245+
return {
246+
module: () => ({
247+
create(info: ts.server.PluginCreateInfo) {
248+
const proxy = Harness.LanguageService.makeDefaultProxy(info);
249+
const originalScriptKind = info.languageServiceHost.getScriptKind!.bind(info.languageServiceHost);
250+
info.languageServiceHost.getScriptKind = fileName =>
251+
ts.fileExtensionIs(fileName, ".vue") ?
252+
ts.ScriptKind.TS :
253+
originalScriptKind(fileName);
254+
const originalGetScriptSnapshot = info.languageServiceHost.getScriptSnapshot.bind(info.languageServiceHost);
255+
info.languageServiceHost.getScriptSnapshot = fileName =>
256+
ts.fileExtensionIs(fileName, ".vue") ?
257+
ts.ScriptSnapshot.fromString(`export const y = "${info.languageServiceHost.readFile(fileName)}";`) :
258+
originalGetScriptSnapshot(fileName);
259+
return proxy;
260+
},
261+
getExternalFiles: (project: ts.server.Project) => {
262+
if (project.projectKind !== ts.server.ProjectKind.Configured) return [];
263+
const configFile = project.getProjectName();
264+
const config = ts.readJsonConfigFile(configFile, project.readFile.bind(project));
265+
const parseHost: ts.ParseConfigHost = {
266+
useCaseSensitiveFileNames: project.useCaseSensitiveFileNames(),
267+
fileExists: project.fileExists.bind(project),
268+
readFile: project.readFile.bind(project),
269+
readDirectory: (...args) => {
270+
args[1] = [".vue"];
271+
return project.readDirectory(...args);
272+
},
273+
};
274+
const parsed = ts.parseJsonSourceFileConfigFileContent(config, parseHost, project.getCurrentDirectory());
275+
return parsed.fileNames;
276+
},
277+
}),
278+
error: undefined,
279+
};
280+
};
281+
const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host), globalPlugins: ["myplugin"] });
282+
openFilesForSession([aTs], session);
283+
284+
host.writeFile("/user/username/projects/myproject/c.vue", "cVue file");
285+
host.runQueuedTimeoutCallbacks();
286+
287+
baselineTsserverLogs("plugins", "new files with non ts extensions with wildcard matching", session);
288+
});
289+
});
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
currentDirectory:: / useCaseSensitiveFileNames: false
2+
Info seq [hh:mm:ss:mss] Provided types map file "/a/lib/typesMap.json" doesn't exist
3+
Before request
4+
//// [/user/username/projects/myproject/a.ts]
5+
export const a = 10;
6+
7+
//// [/user/username/projects/myproject/b.vue]
8+
bVue file
9+
10+
//// [/user/username/projects/myproject/tsconfig.json]
11+
{
12+
"compilerOptions": {
13+
"composite": true
14+
},
15+
"include": [
16+
"*.ts",
17+
"*.vue"
18+
]
19+
}
20+
21+
//// [/a/lib/lib.d.ts]
22+
/// <reference no-default-lib="true"/>
23+
interface Boolean {}
24+
interface Function {}
25+
interface CallableFunction {}
26+
interface NewableFunction {}
27+
interface IArguments {}
28+
interface Number { toExponential: any; }
29+
interface Object {}
30+
interface RegExp {}
31+
interface String { charAt: any; }
32+
interface Array<T> { length: number; [n: number]: T; }
33+
34+
35+
Info seq [hh:mm:ss:mss] request:
36+
{
37+
"command": "open",
38+
"arguments": {
39+
"file": "/user/username/projects/myproject/a.ts"
40+
},
41+
"seq": 1,
42+
"type": "request"
43+
}
44+
Info seq [hh:mm:ss:mss] Search path: /user/username/projects/myproject
45+
Info seq [hh:mm:ss:mss] For info: /user/username/projects/myproject/a.ts :: Config file name: /user/username/projects/myproject/tsconfig.json
46+
Info seq [hh:mm:ss:mss] Creating configuration project /user/username/projects/myproject/tsconfig.json
47+
Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /user/username/projects/myproject/tsconfig.json 2000 undefined Project: /user/username/projects/myproject/tsconfig.json WatchType: Config file
48+
Info seq [hh:mm:ss:mss] Config: /user/username/projects/myproject/tsconfig.json : {
49+
"rootNames": [
50+
"/user/username/projects/myproject/a.ts"
51+
],
52+
"options": {
53+
"composite": true,
54+
"configFilePath": "/user/username/projects/myproject/tsconfig.json"
55+
}
56+
}
57+
Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject 0 undefined Config: /user/username/projects/myproject/tsconfig.json WatchType: Wild card directory
58+
Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject 0 undefined Config: /user/username/projects/myproject/tsconfig.json WatchType: Wild card directory
59+
Info seq [hh:mm:ss:mss] Loading global plugin myplugin
60+
Info seq [hh:mm:ss:mss] Enabling plugin myplugin from candidate paths: /a/lib/tsc.js/../../..
61+
Info seq [hh:mm:ss:mss] Loading myplugin from /a/lib/tsc.js/../../.. (resolved to /a/lib/tsc.js/../../../node_modules)
62+
Info seq [hh:mm:ss:mss] Plugin validation succeeded
63+
Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /user/username/projects/myproject/b.vue 500 undefined WatchType: Closed Script info
64+
Info seq [hh:mm:ss:mss] Starting updateGraphWorker: Project: /user/username/projects/myproject/tsconfig.json
65+
Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /a/lib/lib.d.ts 500 undefined WatchType: Closed Script info
66+
Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules/@types 1 undefined Project: /user/username/projects/myproject/tsconfig.json WatchType: Type roots
67+
Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules/@types 1 undefined Project: /user/username/projects/myproject/tsconfig.json WatchType: Type roots
68+
Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/node_modules/@types 1 undefined Project: /user/username/projects/myproject/tsconfig.json WatchType: Type roots
69+
Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/node_modules/@types 1 undefined Project: /user/username/projects/myproject/tsconfig.json WatchType: Type roots
70+
Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /user/username/projects/myproject/tsconfig.json Version: 1 structureChanged: true structureIsReused:: Not Elapsed:: *ms
71+
Info seq [hh:mm:ss:mss] Project '/user/username/projects/myproject/tsconfig.json' (Configured)
72+
Info seq [hh:mm:ss:mss] Files (3)
73+
/a/lib/lib.d.ts Text-1 "/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }"
74+
/user/username/projects/myproject/a.ts SVC-1-0 "export const a = 10;"
75+
/user/username/projects/myproject/b.vue Text-1 "export const y = \"bVue file\";"
76+
77+
78+
../../../../a/lib/lib.d.ts
79+
Default library for target 'es5'
80+
a.ts
81+
Matched by include pattern '*.ts' in 'tsconfig.json'
82+
b.vue
83+
Matched by include pattern '*.vue' in 'tsconfig.json'
84+
85+
Info seq [hh:mm:ss:mss] -----------------------------------------------
86+
Info seq [hh:mm:ss:mss] Search path: /user/username/projects/myproject
87+
Info seq [hh:mm:ss:mss] For info: /user/username/projects/myproject/tsconfig.json :: No config files found.
88+
Info seq [hh:mm:ss:mss] Project '/user/username/projects/myproject/tsconfig.json' (Configured)
89+
Info seq [hh:mm:ss:mss] Files (3)
90+
91+
Info seq [hh:mm:ss:mss] -----------------------------------------------
92+
Info seq [hh:mm:ss:mss] Open files:
93+
Info seq [hh:mm:ss:mss] FileName: /user/username/projects/myproject/a.ts ProjectRootPath: undefined
94+
Info seq [hh:mm:ss:mss] Projects: /user/username/projects/myproject/tsconfig.json
95+
Info seq [hh:mm:ss:mss] response:
96+
{
97+
"responseRequired": false
98+
}
99+
After request
100+
101+
PolledWatches::
102+
/user/username/projects/myproject/node_modules/@types: *new*
103+
{"pollingInterval":500}
104+
/user/username/projects/node_modules/@types: *new*
105+
{"pollingInterval":500}
106+
107+
FsWatches::
108+
/a/lib/lib.d.ts: *new*
109+
{}
110+
/user/username/projects/myproject: *new*
111+
{}
112+
/user/username/projects/myproject/b.vue: *new*
113+
{}
114+
/user/username/projects/myproject/tsconfig.json: *new*
115+
{}
116+
117+
Info seq [hh:mm:ss:mss] DirectoryWatcher:: Triggered with /user/username/projects/myproject/c.vue :: WatchInfo: /user/username/projects/myproject 0 undefined Config: /user/username/projects/myproject/tsconfig.json WatchType: Wild card directory
118+
Info seq [hh:mm:ss:mss] Scheduled: /user/username/projects/myproject/tsconfig.json
119+
Info seq [hh:mm:ss:mss] Scheduled: *ensureProjectForOpenFiles*
120+
Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Triggered with /user/username/projects/myproject/c.vue :: WatchInfo: /user/username/projects/myproject 0 undefined Config: /user/username/projects/myproject/tsconfig.json WatchType: Wild card directory
121+
Before running Timeout callback:: count: 2
122+
1: /user/username/projects/myproject/tsconfig.json
123+
2: *ensureProjectForOpenFiles*
124+
//// [/user/username/projects/myproject/c.vue]
125+
cVue file
126+
127+
128+
Info seq [hh:mm:ss:mss] Running: /user/username/projects/myproject/tsconfig.json
129+
Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /user/username/projects/myproject/c.vue 500 undefined WatchType: Closed Script info
130+
Info seq [hh:mm:ss:mss] Starting updateGraphWorker: Project: /user/username/projects/myproject/tsconfig.json
131+
Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /user/username/projects/myproject/tsconfig.json Version: 2 structureChanged: true structureIsReused:: Not Elapsed:: *ms
132+
Info seq [hh:mm:ss:mss] Project '/user/username/projects/myproject/tsconfig.json' (Configured)
133+
Info seq [hh:mm:ss:mss] Files (4)
134+
/a/lib/lib.d.ts Text-1 "/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }"
135+
/user/username/projects/myproject/a.ts SVC-1-0 "export const a = 10;"
136+
/user/username/projects/myproject/b.vue Text-1 "export const y = \"bVue file\";"
137+
/user/username/projects/myproject/c.vue Text-1 "export const y = \"cVue file\";"
138+
139+
140+
../../../../a/lib/lib.d.ts
141+
Default library for target 'es5'
142+
a.ts
143+
Matched by include pattern '*.ts' in 'tsconfig.json'
144+
b.vue
145+
Matched by include pattern '*.vue' in 'tsconfig.json'
146+
c.vue
147+
Matched by include pattern '*.vue' in 'tsconfig.json'
148+
149+
Info seq [hh:mm:ss:mss] -----------------------------------------------
150+
Info seq [hh:mm:ss:mss] Running: *ensureProjectForOpenFiles*
151+
Info seq [hh:mm:ss:mss] Before ensureProjectForOpenFiles:
152+
Info seq [hh:mm:ss:mss] Project '/user/username/projects/myproject/tsconfig.json' (Configured)
153+
Info seq [hh:mm:ss:mss] Files (4)
154+
155+
Info seq [hh:mm:ss:mss] -----------------------------------------------
156+
Info seq [hh:mm:ss:mss] Open files:
157+
Info seq [hh:mm:ss:mss] FileName: /user/username/projects/myproject/a.ts ProjectRootPath: undefined
158+
Info seq [hh:mm:ss:mss] Projects: /user/username/projects/myproject/tsconfig.json
159+
Info seq [hh:mm:ss:mss] After ensureProjectForOpenFiles:
160+
Info seq [hh:mm:ss:mss] Project '/user/username/projects/myproject/tsconfig.json' (Configured)
161+
Info seq [hh:mm:ss:mss] Files (4)
162+
163+
Info seq [hh:mm:ss:mss] -----------------------------------------------
164+
Info seq [hh:mm:ss:mss] Open files:
165+
Info seq [hh:mm:ss:mss] FileName: /user/username/projects/myproject/a.ts ProjectRootPath: undefined
166+
Info seq [hh:mm:ss:mss] Projects: /user/username/projects/myproject/tsconfig.json
167+
After running Timeout callback:: count: 0
168+
169+
PolledWatches::
170+
/user/username/projects/myproject/node_modules/@types:
171+
{"pollingInterval":500}
172+
/user/username/projects/node_modules/@types:
173+
{"pollingInterval":500}
174+
175+
FsWatches::
176+
/a/lib/lib.d.ts:
177+
{}
178+
/user/username/projects/myproject:
179+
{}
180+
/user/username/projects/myproject/b.vue:
181+
{}
182+
/user/username/projects/myproject/c.vue: *new*
183+
{}
184+
/user/username/projects/myproject/tsconfig.json:
185+
{}

0 commit comments

Comments
 (0)