Skip to content

Commit 717ba6c

Browse files
authored
fix: wrong type when d.ts file exists (#14)
* fix: wrong type when d.ts file exists * Create flat-bobcats-hang.md
1 parent bf17333 commit 717ba6c

File tree

10 files changed

+299
-25
lines changed

10 files changed

+299
-25
lines changed

.changeset/flat-bobcats-hang.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"typescript-eslint-parser-for-extra-files": patch
3+
---
4+
5+
fix: wrong type when d.ts file exists

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
"astrojs-compiler-sync": ">=0.3.1",
5353
"svelte2tsx": ">=0.5.20",
5454
"typescript": ">=4.8.4",
55-
"vue": "^3.2.41"
55+
"vue": "^3.2.45"
5656
},
5757
"peerDependenciesMeta": {
5858
"astrojs-compiler-sync": {

src/ts.ts

+49-24
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,11 @@ export class TSService {
8585
// because it targets a file that does not actually exist.
8686
this.fileWatchCallbacks.get(normalizeFileName(this.tsconfigPath))?.();
8787
}
88-
getFileNamesIncludingVirtualTSX(
89-
targetPath,
90-
this.extraFileExtensions
91-
).forEach((vFilePath) => {
92-
this.fileWatchCallbacks.get(vFilePath)?.();
93-
});
88+
getRefreshTargetFileNames(targetPath, this.extraFileExtensions).forEach(
89+
(vFilePath) => {
90+
this.fileWatchCallbacks.get(vFilePath)?.();
91+
}
92+
);
9493
}
9594

9695
const program = this.watch.getProgram().getProgram();
@@ -169,9 +168,18 @@ export class TSService {
169168
results.push(file.path);
170169
}
171170

172-
return distinctArray(...results).map((result) =>
173-
toVirtualTSXFileName(result, extraFileExtensions)
174-
);
171+
return distinctArray(...results).map((result) => {
172+
if (!isExtra(result, extraFileExtensions)) {
173+
return result;
174+
}
175+
176+
if (original.fileExists.call(watchCompilerHost, `${result}.d.ts`)) {
177+
// If the d.ts file exists, respect it and consider the virtual file not to exist.
178+
return result;
179+
}
180+
181+
return toVirtualTSXFileName(result, extraFileExtensions);
182+
});
175183
};
176184
watchCompilerHost.readFile = (fileName, ...args) => {
177185
const realFileName = toRealFileName(fileName, extraFileExtensions);
@@ -243,11 +251,19 @@ export class TSService {
243251
// It is the file currently being parsed.
244252
return true;
245253
}
246-
return original.fileExists.call(
247-
watchCompilerHost,
248-
toRealFileName(fileName, extraFileExtensions),
249-
...args
250-
);
254+
const real = toRealFileName(fileName, extraFileExtensions);
255+
if (original.fileExists.call(watchCompilerHost, real, ...args)) {
256+
if (real !== fileName) {
257+
if (
258+
original.fileExists.call(watchCompilerHost, `${real}.d.ts`, ...args)
259+
) {
260+
// If the d.ts file exists, respect it and consider the virtual file not to exist.
261+
return false;
262+
}
263+
}
264+
return true;
265+
}
266+
return false;
251267
};
252268

253269
// It keeps a callback to mark the parsed file as changed so that it can be reparsed.
@@ -292,25 +308,24 @@ export class TSService {
292308
}
293309
}
294310

295-
/** If the given filename is a `.vue` file, return a list of filenames containing virtual filename (.vue.tsx). */
296-
function getFileNamesIncludingVirtualTSX(
311+
/**
312+
* If the given filename is a extra extension file (.vue),
313+
* return a list of filenames containing virtual filename (.vue.tsx) and type def filename (.vue.d.ts).
314+
*/
315+
function getRefreshTargetFileNames(
297316
fileName: string,
298317
extraFileExtensions: string[]
299318
) {
300-
for (const extraFileExtension of extraFileExtensions) {
301-
if (fileName.endsWith(extraFileExtension)) {
302-
return [`${fileName}.tsx`, fileName];
303-
}
319+
if (isExtra(fileName, extraFileExtensions)) {
320+
return [`${fileName}.tsx`, `${fileName}.d.ts`, fileName];
304321
}
305322
return [fileName];
306323
}
307324

308325
/** If the given filename has extra file extensions, returns the real virtual filename. */
309326
function toVirtualTSXFileName(fileName: string, extraFileExtensions: string[]) {
310-
for (const extraFileExtension of extraFileExtensions) {
311-
if (fileName.endsWith(extraFileExtension)) {
312-
return `${fileName}.tsx`;
313-
}
327+
if (isExtra(fileName, extraFileExtensions)) {
328+
return `${fileName}.tsx`;
314329
}
315330
return fileName;
316331
}
@@ -325,6 +340,16 @@ function toRealFileName(fileName: string, extraFileExtensions: string[]) {
325340
return fileName;
326341
}
327342

343+
/** Checks the given filename has extra file extension or not. */
344+
function isExtra(fileName: string, extraFileExtensions: string[]): boolean {
345+
for (const extraFileExtension of extraFileExtensions) {
346+
if (fileName.endsWith(extraFileExtension)) {
347+
return true;
348+
}
349+
}
350+
return false;
351+
}
352+
328353
function formatDiagnostics(diagnostics: ts.Diagnostic[]) {
329354
return ts.formatDiagnostics(diagnostics, {
330355
getCanonicalFileName: (f) => f,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<script setup lang="ts">
2+
let exposeA = $ref(1);
3+
4+
type Ret = {
5+
num: () => number;
6+
};
7+
8+
// eslint-disable-next-line func-style -- ignore
9+
let exposeFn: () => Ret | null = function exposeFn(): Ret {
10+
return { num: () => 42 };
11+
};
12+
13+
exposeFn = () => null;
14+
15+
function exposeFn2(): number {
16+
return 42;
17+
}
18+
19+
defineExpose({
20+
exposeA,
21+
exposeFn() {
22+
return exposeFn();
23+
},
24+
exposeFn3() {
25+
return exposeFn2();
26+
},
27+
});
28+
</script>
29+
30+
<template>
31+
<div>{{ exposeA }}</div>
32+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<script setup lang="ts">
2+
import { watchEffect } from "vue";
3+
import Foo from "./component.vue";
4+
type VMFoo = InstanceType<typeof Foo>;
5+
let a = $ref<VMFoo | null>(null);
6+
watchEffect(() => {
7+
if (a) fn(a);
8+
});
9+
10+
const comp = {} as VMFoo | undefined;
11+
const r = comp?.exposeFn();
12+
const r2 = r?.num();
13+
console.log(r2);
14+
15+
function fn(vm: VMFoo) {
16+
const b = vm.exposeA;
17+
console.log(b);
18+
const c = vm.exposeFn();
19+
console.log(c);
20+
const d = vm.exposeFn2();
21+
console.log(d);
22+
const e = vm.exposeFn3();
23+
console.log(e);
24+
}
25+
</script>
26+
27+
<template>
28+
<Foo ref="a" />
29+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<script setup lang="ts">
2+
import { watchEffect } from "vue"; // watchEffect: (effect: WatchEffect, options?: WatchOptionsBase | undefined) => WatchStopHandle, watchEffect: (effect: WatchEffect, options?: WatchOptionsBase | undefined) => WatchStopHandle
3+
import Foo from "./component.vue"; // Foo: DefineComponent<{}, { exposeA: Ref<number>; exposeFn: () => Ret | null; exposeFn2: () => number; }, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, ... 4 more ..., {}>
4+
type VMFoo = InstanceType<typeof Foo>; // VMFoo: { $: ComponentInternalInstance; $data: {}; $props: Partial<{}> & Omit<Readonly<ExtractPropTypes<{}>> & VNodeProps & AllowedComponentProps & ComponentCustomProps, never>; ... 10 more ...; $watch<T extends string | ((...args: any) => any)>(source: T, cb: T extends (...args: any) => infer R ? (args_0: R, args_1: R) => ..., InstanceType: InstanceType<T>, Foo: DefineComponent<{}, { exposeA: Ref<number>; exposeFn: () => Ret | null; exposeFn2: () => number; }, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, ... 4 more ..., {}>
5+
let a = $ref<VMFoo | null>(null); // a: any, $ref<VMFoo | null>(null): any
6+
watchEffect(() => { // watchEffect(() => { if (a) fn(a); }): WatchStopHandle
7+
if (a) fn(a);
8+
});
9+
10+
const comp = {} as VMFoo | undefined; // comp: ({ $: ComponentInternalInstance; $data: {}; $props: Partial<{}> & Omit<Readonly<ExtractPropTypes<{}>> & VNodeProps & AllowedComponentProps & ComponentCustomProps, never>; ... 10 more ...; $watch<T extends string | ((...args: any) => any)>(source: T, cb: T extends (...args: any) => infer R ? (args_0: R, args_1: R) =>..., VMFoo: { $: ComponentInternalInstance; $data: {}; $props: Partial<{}> & Omit<Readonly<ExtractPropTypes<{}>> & VNodeProps & AllowedComponentProps & ComponentCustomProps, never>; ... 10 more ...; $watch<T extends string | ((...args: any) => any)>(source: T, cb: T extends (...args: any) => infer R ? (args_0: R, args_1: R) => ...
11+
const r = comp?.exposeFn(); // r: Ret | null | undefined, comp?.exposeFn(): Ret | null | undefined
12+
const r2 = r?.num(); // r2: number | undefined, r?.num(): number | undefined
13+
console.log(r2); // console.log(r2): void
14+
15+
function fn(vm: VMFoo) { // fn: (vm: { $: ComponentInternalInstance; $data: {}; $props: Partial<{}> & Omit<Readonly<ExtractPropTypes<{}>> & VNodeProps & AllowedComponentProps & ComponentCustomProps, never>; ... 10 more ...; $watch<T extends string | ((...args: any) => any)>(source: T, cb: T extends (...args: any) => infer R ? (args_0: R, args_1: R..., vm: { $: ComponentInternalInstance; $data: {}; $props: Partial<{}> & Omit<Readonly<ExtractPropTypes<{}>> & VNodeProps & AllowedComponentProps & ComponentCustomProps, never>; ... 10 more ...; $watch<T extends string | ((...args: any) => any)>(source: T, cb: T extends (...args: any) => infer R ? (args_0: R, args_1: R) => ...
16+
const b = vm.exposeA; // b: number, vm.exposeA: number
17+
console.log(b); // console.log(b): void
18+
const c = vm.exposeFn(); // c: Ret | null, vm.exposeFn(): Ret | null
19+
console.log(c); // console.log(c): void
20+
const d = vm.exposeFn2(); // d: number, vm.exposeFn2(): number
21+
console.log(d); // console.log(d): void
22+
const e = vm.exposeFn3(); // e: any, vm.exposeFn3(): any
23+
console.log(e); // console.log(e): void
24+
}
25+
</script>
26+
27+
<template>
28+
<Foo ref="a" />
29+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<script setup lang="ts">
2+
let exposeA = $ref(1);
3+
4+
type Ret = {
5+
num: () => number;
6+
};
7+
8+
// eslint-disable-next-line func-style -- ignore
9+
let exposeFn: () => Ret | null = function exposeFn(): Ret {
10+
return { num: () => 42 };
11+
};
12+
13+
exposeFn = () => null;
14+
15+
function exposeFn2(): number {
16+
return 42;
17+
}
18+
19+
defineExpose({
20+
exposeA,
21+
exposeFn() {
22+
return exposeFn();
23+
},
24+
exposeFn3() {
25+
return exposeFn2();
26+
},
27+
});
28+
</script>
29+
30+
<template>
31+
<div>{{ exposeA }}</div>
32+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/* eslint eslint-comments/no-unlimited-disable: 0, eslint-comments/disable-enable-pair: 0 -- ignore */
2+
/* eslint-disable -- ignore */
3+
declare const _default: {
4+
new (...args: any[]): {
5+
$: import("vue").ComponentInternalInstance;
6+
$data: {};
7+
$props: Partial<{}> & Omit<Readonly<import("vue").ExtractPropTypes<{}>> & import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, never>;
8+
$attrs: {
9+
[x: string]: unknown;
10+
};
11+
$refs: {
12+
[x: string]: unknown;
13+
};
14+
$slots: Readonly<{
15+
[name: string]: import("vue").Slot | undefined;
16+
}>;
17+
$root: import("vue").ComponentPublicInstance<{}, {}, {}, {}, {}, {}, {}, {}, false, import("vue").ComponentOptionsBase<any, any, any, any, any, any, any, any, any, {}, {}, string>, {}> | null;
18+
$parent: import("vue").ComponentPublicInstance<{}, {}, {}, {}, {}, {}, {}, {}, false, import("vue").ComponentOptionsBase<any, any, any, any, any, any, any, any, any, {}, {}, string>, {}> | null;
19+
$emit: (event: string, ...args: any[]) => void;
20+
$el: any;
21+
$options: import("vue").ComponentOptionsBase<Readonly<import("vue").ExtractPropTypes<{}>>, {
22+
exposeA: any;
23+
exposeFn(): {
24+
num: () => number;
25+
} | null;
26+
exposeFn3(): number;
27+
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, {}, {}, string> & {
28+
beforeCreate?: ((() => void) | (() => void)[]) | undefined;
29+
created?: ((() => void) | (() => void)[]) | undefined;
30+
beforeMount?: ((() => void) | (() => void)[]) | undefined;
31+
mounted?: ((() => void) | (() => void)[]) | undefined;
32+
beforeUpdate?: ((() => void) | (() => void)[]) | undefined;
33+
updated?: ((() => void) | (() => void)[]) | undefined;
34+
activated?: ((() => void) | (() => void)[]) | undefined;
35+
deactivated?: ((() => void) | (() => void)[]) | undefined;
36+
beforeDestroy?: ((() => void) | (() => void)[]) | undefined;
37+
beforeUnmount?: ((() => void) | (() => void)[]) | undefined;
38+
destroyed?: ((() => void) | (() => void)[]) | undefined;
39+
unmounted?: ((() => void) | (() => void)[]) | undefined;
40+
renderTracked?: (((e: import("vue").DebuggerEvent) => void) | ((e: import("vue").DebuggerEvent) => void)[]) | undefined;
41+
renderTriggered?: (((e: import("vue").DebuggerEvent) => void) | ((e: import("vue").DebuggerEvent) => void)[]) | undefined;
42+
errorCaptured?: (((err: unknown, instance: import("vue").ComponentPublicInstance<{}, {}, {}, {}, {}, {}, {}, {}, false, import("vue").ComponentOptionsBase<any, any, any, any, any, any, any, any, any, {}, {}, string>, {}> | null, info: string) => boolean | void) | ((err: unknown, instance: import("vue").ComponentPublicInstance<{}, {}, {}, {}, {}, {}, {}, {}, false, import("vue").ComponentOptionsBase<any, any, any, any, any, any, any, any, any, {}, {}, string>, {}> | null, info: string) => boolean | void)[]) | undefined;
43+
};
44+
$forceUpdate: () => void;
45+
$nextTick: typeof import("vue").nextTick;
46+
$watch<T extends string | ((...args: any) => any)>(source: T, cb: T extends (...args: any) => infer R ? (args_0: R, args_1: R) => any : (...args: any) => any, options?: import("vue").WatchOptions<boolean> | undefined): import("vue").WatchStopHandle;
47+
} & Readonly<import("vue").ExtractPropTypes<{}>> & import("vue").ShallowUnwrapRef<{
48+
exposeA: any;
49+
exposeFn(): {
50+
num: () => number;
51+
} | null;
52+
exposeFn3(): number;
53+
}> & {} & import("vue").ComponentCustomProperties & {};
54+
__isFragment?: undefined;
55+
__isTeleport?: undefined;
56+
__isSuspense?: undefined;
57+
} & import("vue").ComponentOptionsBase<Readonly<import("vue").ExtractPropTypes<{}>>, {
58+
exposeA: any;
59+
exposeFn(): {
60+
num: () => number;
61+
} | null;
62+
exposeFn3(): number;
63+
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, {}, {}, string> & import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps;
64+
export default _default;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<script setup lang="ts">
2+
import { watchEffect } from "vue";
3+
import Foo from "./component.vue";
4+
type VMFoo = InstanceType<typeof Foo>;
5+
let a = $ref<VMFoo | null>(null);
6+
watchEffect(() => {
7+
if (a) fn(a);
8+
});
9+
10+
const comp = {} as VMFoo | undefined;
11+
const r = comp?.exposeFn();
12+
const r2 = r?.num();
13+
console.log(r2);
14+
15+
function fn(vm: VMFoo) {
16+
const b = vm.exposeA;
17+
console.log(b);
18+
const c = vm.exposeFn();
19+
console.log(c);
20+
const d = vm.exposeFn2();
21+
console.log(d);
22+
const e = vm.exposeFn3();
23+
console.log(e);
24+
}
25+
</script>
26+
27+
<template>
28+
<Foo ref="a" />
29+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<script setup lang="ts">
2+
import { watchEffect } from "vue"; // watchEffect: (effect: WatchEffect, options?: WatchOptionsBase | undefined) => WatchStopHandle, watchEffect: (effect: WatchEffect, options?: WatchOptionsBase | undefined) => WatchStopHandle
3+
import Foo from "./component.vue"; // Foo: { new (...args: any[]): { $: ComponentInternalInstance; $data: {}; $props: Partial<{}> & Omit<Readonly<ExtractPropTypes<{}>> & VNodeProps & AllowedComponentProps & ComponentCustomProps, never>; ... 10 more ...; $watch<T extends string | ((...args: any) => any)>(source: T, cb: T extends (...args: any) => infer R ? (a...
4+
type VMFoo = InstanceType<typeof Foo>; // VMFoo: { $: ComponentInternalInstance; $data: {}; $props: Partial<{}> & Omit<Readonly<ExtractPropTypes<{}>> & VNodeProps & AllowedComponentProps & ComponentCustomProps, never>; ... 10 more ...; $watch<T extends string | ((...args: any) => any)>(source: T, cb: T extends (...args: any) => infer R ? (args_0: R, args_1: R) => ..., InstanceType: InstanceType<T>, Foo: { new (...args: any[]): { $: ComponentInternalInstance; $data: {}; $props: Partial<{}> & Omit<Readonly<ExtractPropTypes<{}>> & VNodeProps & AllowedComponentProps & ComponentCustomProps, never>; ... 10 more ...; $watch<T extends string | ((...args: any) => any)>(source: T, cb: T extends (...args: any) => infer R ? (a...
5+
let a = $ref<VMFoo | null>(null); // a: any, $ref<VMFoo | null>(null): any
6+
watchEffect(() => { // watchEffect(() => { if (a) fn(a); }): WatchStopHandle
7+
if (a) fn(a);
8+
});
9+
10+
const comp = {} as VMFoo | undefined; // comp: ({ $: ComponentInternalInstance; $data: {}; $props: Partial<{}> & Omit<Readonly<ExtractPropTypes<{}>> & VNodeProps & AllowedComponentProps & ComponentCustomProps, never>; ... 10 more ...; $watch<T extends string | ((...args: any) => any)>(source: T, cb: T extends (...args: any) => infer R ? (args_0: R, args_1: R) =>..., VMFoo: { $: ComponentInternalInstance; $data: {}; $props: Partial<{}> & Omit<Readonly<ExtractPropTypes<{}>> & VNodeProps & AllowedComponentProps & ComponentCustomProps, never>; ... 10 more ...; $watch<T extends string | ((...args: any) => any)>(source: T, cb: T extends (...args: any) => infer R ? (args_0: R, args_1: R) => ...
11+
const r = comp?.exposeFn(); // r: { num: () => number; } | null | undefined, comp?.exposeFn(): { num: () => number; } | null | undefined
12+
const r2 = r?.num(); // r2: number | undefined, r?.num(): number | undefined
13+
console.log(r2); // console.log(r2): void
14+
15+
function fn(vm: VMFoo) { // fn: (vm: { $: ComponentInternalInstance; $data: {}; $props: Partial<{}> & Omit<Readonly<ExtractPropTypes<{}>> & VNodeProps & AllowedComponentProps & ComponentCustomProps, never>; ... 10 more ...; $watch<T extends string | ((...args: any) => any)>(source: T, cb: T extends (...args: any) => infer R ? (args_0: R, args_1: R..., vm: { $: ComponentInternalInstance; $data: {}; $props: Partial<{}> & Omit<Readonly<ExtractPropTypes<{}>> & VNodeProps & AllowedComponentProps & ComponentCustomProps, never>; ... 10 more ...; $watch<T extends string | ((...args: any) => any)>(source: T, cb: T extends (...args: any) => infer R ? (args_0: R, args_1: R) => ...
16+
const b = vm.exposeA; // b: any, vm.exposeA: any
17+
console.log(b); // console.log(b): void
18+
const c = vm.exposeFn(); // c: { num: () => number; } | null, vm.exposeFn(): { num: () => number; } | null
19+
console.log(c); // console.log(c): void
20+
const d = vm.exposeFn2(); // d: any, vm.exposeFn2(): any
21+
console.log(d); // console.log(d): void
22+
const e = vm.exposeFn3(); // e: number, vm.exposeFn3(): number
23+
console.log(e); // console.log(e): void
24+
}
25+
</script>
26+
27+
<template>
28+
<Foo ref="a" />
29+
</template>

0 commit comments

Comments
 (0)