Skip to content

fix: wrong type when d.ts file exists #14

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/flat-bobcats-hang.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"typescript-eslint-parser-for-extra-files": patch
---

fix: wrong type when d.ts file exists
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"astrojs-compiler-sync": ">=0.3.1",
"svelte2tsx": ">=0.5.20",
"typescript": ">=4.8.4",
"vue": "^3.2.41"
"vue": "^3.2.45"
},
"peerDependenciesMeta": {
"astrojs-compiler-sync": {
Expand Down
73 changes: 49 additions & 24 deletions src/ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,11 @@ export class TSService {
// because it targets a file that does not actually exist.
this.fileWatchCallbacks.get(normalizeFileName(this.tsconfigPath))?.();
}
getFileNamesIncludingVirtualTSX(
targetPath,
this.extraFileExtensions
).forEach((vFilePath) => {
this.fileWatchCallbacks.get(vFilePath)?.();
});
getRefreshTargetFileNames(targetPath, this.extraFileExtensions).forEach(
(vFilePath) => {
this.fileWatchCallbacks.get(vFilePath)?.();
}
);
}

const program = this.watch.getProgram().getProgram();
Expand Down Expand Up @@ -169,9 +168,18 @@ export class TSService {
results.push(file.path);
}

return distinctArray(...results).map((result) =>
toVirtualTSXFileName(result, extraFileExtensions)
);
return distinctArray(...results).map((result) => {
if (!isExtra(result, extraFileExtensions)) {
return result;
}

if (original.fileExists.call(watchCompilerHost, `${result}.d.ts`)) {
// If the d.ts file exists, respect it and consider the virtual file not to exist.
return result;
}

return toVirtualTSXFileName(result, extraFileExtensions);
});
};
watchCompilerHost.readFile = (fileName, ...args) => {
const realFileName = toRealFileName(fileName, extraFileExtensions);
Expand Down Expand Up @@ -243,11 +251,19 @@ export class TSService {
// It is the file currently being parsed.
return true;
}
return original.fileExists.call(
watchCompilerHost,
toRealFileName(fileName, extraFileExtensions),
...args
);
const real = toRealFileName(fileName, extraFileExtensions);
if (original.fileExists.call(watchCompilerHost, real, ...args)) {
if (real !== fileName) {
if (
original.fileExists.call(watchCompilerHost, `${real}.d.ts`, ...args)
) {
// If the d.ts file exists, respect it and consider the virtual file not to exist.
return false;
}
}
return true;
}
return false;
};

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

/** If the given filename is a `.vue` file, return a list of filenames containing virtual filename (.vue.tsx). */
function getFileNamesIncludingVirtualTSX(
/**
* If the given filename is a extra extension file (.vue),
* return a list of filenames containing virtual filename (.vue.tsx) and type def filename (.vue.d.ts).
*/
function getRefreshTargetFileNames(
fileName: string,
extraFileExtensions: string[]
) {
for (const extraFileExtension of extraFileExtensions) {
if (fileName.endsWith(extraFileExtension)) {
return [`${fileName}.tsx`, fileName];
}
if (isExtra(fileName, extraFileExtensions)) {
return [`${fileName}.tsx`, `${fileName}.d.ts`, fileName];
}
return [fileName];
}

/** If the given filename has extra file extensions, returns the real virtual filename. */
function toVirtualTSXFileName(fileName: string, extraFileExtensions: string[]) {
for (const extraFileExtension of extraFileExtensions) {
if (fileName.endsWith(extraFileExtension)) {
return `${fileName}.tsx`;
}
if (isExtra(fileName, extraFileExtensions)) {
return `${fileName}.tsx`;
}
return fileName;
}
Expand All @@ -325,6 +340,16 @@ function toRealFileName(fileName: string, extraFileExtensions: string[]) {
return fileName;
}

/** Checks the given filename has extra file extension or not. */
function isExtra(fileName: string, extraFileExtensions: string[]): boolean {
for (const extraFileExtension of extraFileExtensions) {
if (fileName.endsWith(extraFileExtension)) {
return true;
}
}
return false;
}

function formatDiagnostics(diagnostics: ts.Diagnostic[]) {
return ts.formatDiagnostics(diagnostics, {
getCanonicalFileName: (f) => f,
Expand Down
32 changes: 32 additions & 0 deletions tests/fixtures/types/vue/vue-script-setup03/component.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<script setup lang="ts">
let exposeA = $ref(1);

type Ret = {
num: () => number;
};

// eslint-disable-next-line func-style -- ignore
let exposeFn: () => Ret | null = function exposeFn(): Ret {
return { num: () => 42 };
};

exposeFn = () => null;

function exposeFn2(): number {
return 42;
}

defineExpose({
exposeA,
exposeFn() {
return exposeFn();
},
exposeFn3() {
return exposeFn2();
},
});
</script>

<template>
<div>{{ exposeA }}</div>
</template>
29 changes: 29 additions & 0 deletions tests/fixtures/types/vue/vue-script-setup03/source.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<script setup lang="ts">
import { watchEffect } from "vue";
import Foo from "./component.vue";
type VMFoo = InstanceType<typeof Foo>;
let a = $ref<VMFoo | null>(null);
watchEffect(() => {
if (a) fn(a);
});

const comp = {} as VMFoo | undefined;
const r = comp?.exposeFn();
const r2 = r?.num();
console.log(r2);

function fn(vm: VMFoo) {
const b = vm.exposeA;
console.log(b);
const c = vm.exposeFn();
console.log(c);
const d = vm.exposeFn2();
console.log(d);
const e = vm.exposeFn3();
console.log(e);
}
</script>

<template>
<Foo ref="a" />
</template>
29 changes: 29 additions & 0 deletions tests/fixtures/types/vue/vue-script-setup03/types.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<script setup lang="ts">
import { watchEffect } from "vue"; // watchEffect: (effect: WatchEffect, options?: WatchOptionsBase | undefined) => WatchStopHandle, watchEffect: (effect: WatchEffect, options?: WatchOptionsBase | undefined) => WatchStopHandle
import Foo from "./component.vue"; // Foo: DefineComponent<{}, { exposeA: Ref<number>; exposeFn: () => Ret | null; exposeFn2: () => number; }, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, ... 4 more ..., {}>
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 ..., {}>
let a = $ref<VMFoo | null>(null); // a: any, $ref<VMFoo | null>(null): any
watchEffect(() => { // watchEffect(() => { if (a) fn(a); }): WatchStopHandle
if (a) fn(a);
});

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) => ...
const r = comp?.exposeFn(); // r: Ret | null | undefined, comp?.exposeFn(): Ret | null | undefined
const r2 = r?.num(); // r2: number | undefined, r?.num(): number | undefined
console.log(r2); // console.log(r2): void

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) => ...
const b = vm.exposeA; // b: number, vm.exposeA: number
console.log(b); // console.log(b): void
const c = vm.exposeFn(); // c: Ret | null, vm.exposeFn(): Ret | null
console.log(c); // console.log(c): void
const d = vm.exposeFn2(); // d: number, vm.exposeFn2(): number
console.log(d); // console.log(d): void
const e = vm.exposeFn3(); // e: any, vm.exposeFn3(): any
console.log(e); // console.log(e): void
}
</script>

<template>
<Foo ref="a" />
</template>
32 changes: 32 additions & 0 deletions tests/fixtures/types/vue/vue-script-setup04/component.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<script setup lang="ts">
let exposeA = $ref(1);

type Ret = {
num: () => number;
};

// eslint-disable-next-line func-style -- ignore
let exposeFn: () => Ret | null = function exposeFn(): Ret {
return { num: () => 42 };
};

exposeFn = () => null;

function exposeFn2(): number {
return 42;
}

defineExpose({
exposeA,
exposeFn() {
return exposeFn();
},
exposeFn3() {
return exposeFn2();
},
});
</script>

<template>
<div>{{ exposeA }}</div>
</template>
64 changes: 64 additions & 0 deletions tests/fixtures/types/vue/vue-script-setup04/component.vue.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/* eslint eslint-comments/no-unlimited-disable: 0, eslint-comments/disable-enable-pair: 0 -- ignore */
/* eslint-disable -- ignore */
declare const _default: {
new (...args: any[]): {
$: import("vue").ComponentInternalInstance;
$data: {};
$props: Partial<{}> & Omit<Readonly<import("vue").ExtractPropTypes<{}>> & import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, never>;
$attrs: {
[x: string]: unknown;
};
$refs: {
[x: string]: unknown;
};
$slots: Readonly<{
[name: string]: import("vue").Slot | undefined;
}>;
$root: import("vue").ComponentPublicInstance<{}, {}, {}, {}, {}, {}, {}, {}, false, import("vue").ComponentOptionsBase<any, any, any, any, any, any, any, any, any, {}, {}, string>, {}> | null;
$parent: import("vue").ComponentPublicInstance<{}, {}, {}, {}, {}, {}, {}, {}, false, import("vue").ComponentOptionsBase<any, any, any, any, any, any, any, any, any, {}, {}, string>, {}> | null;
$emit: (event: string, ...args: any[]) => void;
$el: any;
$options: import("vue").ComponentOptionsBase<Readonly<import("vue").ExtractPropTypes<{}>>, {
exposeA: any;
exposeFn(): {
num: () => number;
} | null;
exposeFn3(): number;
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, {}, {}, string> & {
beforeCreate?: ((() => void) | (() => void)[]) | undefined;
created?: ((() => void) | (() => void)[]) | undefined;
beforeMount?: ((() => void) | (() => void)[]) | undefined;
mounted?: ((() => void) | (() => void)[]) | undefined;
beforeUpdate?: ((() => void) | (() => void)[]) | undefined;
updated?: ((() => void) | (() => void)[]) | undefined;
activated?: ((() => void) | (() => void)[]) | undefined;
deactivated?: ((() => void) | (() => void)[]) | undefined;
beforeDestroy?: ((() => void) | (() => void)[]) | undefined;
beforeUnmount?: ((() => void) | (() => void)[]) | undefined;
destroyed?: ((() => void) | (() => void)[]) | undefined;
unmounted?: ((() => void) | (() => void)[]) | undefined;
renderTracked?: (((e: import("vue").DebuggerEvent) => void) | ((e: import("vue").DebuggerEvent) => void)[]) | undefined;
renderTriggered?: (((e: import("vue").DebuggerEvent) => void) | ((e: import("vue").DebuggerEvent) => void)[]) | undefined;
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;
};
$forceUpdate: () => void;
$nextTick: typeof import("vue").nextTick;
$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;
} & Readonly<import("vue").ExtractPropTypes<{}>> & import("vue").ShallowUnwrapRef<{
exposeA: any;
exposeFn(): {
num: () => number;
} | null;
exposeFn3(): number;
}> & {} & import("vue").ComponentCustomProperties & {};
__isFragment?: undefined;
__isTeleport?: undefined;
__isSuspense?: undefined;
} & import("vue").ComponentOptionsBase<Readonly<import("vue").ExtractPropTypes<{}>>, {
exposeA: any;
exposeFn(): {
num: () => number;
} | null;
exposeFn3(): number;
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, {}, {}, string> & import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps;
export default _default;
29 changes: 29 additions & 0 deletions tests/fixtures/types/vue/vue-script-setup04/source.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<script setup lang="ts">
import { watchEffect } from "vue";
import Foo from "./component.vue";
type VMFoo = InstanceType<typeof Foo>;
let a = $ref<VMFoo | null>(null);
watchEffect(() => {
if (a) fn(a);
});

const comp = {} as VMFoo | undefined;
const r = comp?.exposeFn();
const r2 = r?.num();
console.log(r2);

function fn(vm: VMFoo) {
const b = vm.exposeA;
console.log(b);
const c = vm.exposeFn();
console.log(c);
const d = vm.exposeFn2();
console.log(d);
const e = vm.exposeFn3();
console.log(e);
}
</script>

<template>
<Foo ref="a" />
</template>
29 changes: 29 additions & 0 deletions tests/fixtures/types/vue/vue-script-setup04/types.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<script setup lang="ts">
import { watchEffect } from "vue"; // watchEffect: (effect: WatchEffect, options?: WatchOptionsBase | undefined) => WatchStopHandle, watchEffect: (effect: WatchEffect, options?: WatchOptionsBase | undefined) => WatchStopHandle
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...
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...
let a = $ref<VMFoo | null>(null); // a: any, $ref<VMFoo | null>(null): any
watchEffect(() => { // watchEffect(() => { if (a) fn(a); }): WatchStopHandle
if (a) fn(a);
});

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) => ...
const r = comp?.exposeFn(); // r: { num: () => number; } | null | undefined, comp?.exposeFn(): { num: () => number; } | null | undefined
const r2 = r?.num(); // r2: number | undefined, r?.num(): number | undefined
console.log(r2); // console.log(r2): void

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) => ...
const b = vm.exposeA; // b: any, vm.exposeA: any
console.log(b); // console.log(b): void
const c = vm.exposeFn(); // c: { num: () => number; } | null, vm.exposeFn(): { num: () => number; } | null
console.log(c); // console.log(c): void
const d = vm.exposeFn2(); // d: any, vm.exposeFn2(): any
console.log(d); // console.log(d): void
const e = vm.exposeFn3(); // e: number, vm.exposeFn3(): number
console.log(e); // console.log(e): void
}
</script>

<template>
<Foo ref="a" />
</template>