Skip to content

Commit 2d4b9e4

Browse files
authored
Add support for store access type infomation (#192)
* Add support for store access type infomation * update * update * add test case
1 parent c5b9ebb commit 2d4b9e4

30 files changed

+35198
-11
lines changed

.github/workflows/NodeCI.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ jobs:
4444
node-version: ${{ matrix.node-version }}
4545
- name: Install @typescript-eslint v4
4646
run: |+
47-
yarn add -D @typescript-eslint/parser@4 @typescript-eslint/eslint-plugin@4 --ignore-engines
47+
yarn add -D @typescript-eslint/parser@4 @typescript-eslint/eslint-plugin@4 eslint@7 --ignore-engines
4848
rm -rf node_modules
4949
- name: Install Packages
5050
run: yarn install --ignore-engines

src/context/script-let.ts

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,26 @@ export class ScriptLetContext {
528528
this.closeScopeCallbacks.pop()!();
529529
}
530530

531+
public appendDeclareMaybeStores(maybeStores: Set<string>): void {
532+
const reservedNames = new Set<string>([
533+
"$$props",
534+
"$$restProps",
535+
"$$slots",
536+
]);
537+
for (const nm of maybeStores) {
538+
if (reservedNames.has(nm)) continue;
539+
540+
this.appendScriptWithoutOffset(
541+
`declare let $${nm}: Parameters<Parameters<(typeof ${nm})["subscribe"]>[0]>[0];`,
542+
(node, tokens, comments, result) => {
543+
tokens.length = 0;
544+
comments.length = 0;
545+
removeAllScope(node, result);
546+
}
547+
);
548+
}
549+
}
550+
531551
private appendScript(
532552
text: string,
533553
offset: number,
@@ -912,7 +932,7 @@ function removeAllScope(target: ESTree.Node, result: ScriptLetCallbackOption) {
912932
return;
913933
}
914934
if (node.type === "Identifier") {
915-
let scope = result.getScope(node);
935+
let scope = result.getInnermostScope(node);
916936
if (
917937
(scope.block as any).type === "TSTypeAliasDeclaration" &&
918938
(scope.block as any).id === node
@@ -939,16 +959,37 @@ function removeAllScope(target: ESTree.Node, result: ScriptLetCallbackOption) {
939959

940960
/** Remove variable */
941961
function removeIdentifierVariable(node: ESTree.Identifier, scope: Scope): void {
942-
const varIndex = scope.variables.findIndex((v) =>
943-
v.defs.some((def) => def.name === node)
944-
);
945-
if (varIndex >= 0) {
962+
for (let varIndex = 0; varIndex < scope.variables.length; varIndex++) {
946963
const variable = scope.variables[varIndex];
947-
scope.variables.splice(varIndex, 1);
948-
const name = node.name;
949-
if (variable === scope.set.get(name)) {
950-
scope.set.delete(name);
964+
const defIndex = variable.defs.findIndex((def) => def.name === node);
965+
if (defIndex < 0) {
966+
continue;
951967
}
968+
variable.defs.splice(defIndex, 1);
969+
if (variable.defs.length === 0) {
970+
// Remove variable
971+
referencesToThrough(variable.references, scope);
972+
scope.variables.splice(varIndex, 1);
973+
const name = node.name;
974+
if (variable === scope.set.get(name)) {
975+
scope.set.delete(name);
976+
}
977+
} else {
978+
const idIndex = variable.identifiers.indexOf(node);
979+
if (idIndex >= 0) {
980+
variable.identifiers.splice(idIndex, 1);
981+
}
982+
}
983+
return;
984+
}
985+
}
986+
987+
/** Move reference to through */
988+
function referencesToThrough(references: Reference[], baseScope: Scope) {
989+
let scope: Scope | null = baseScope;
990+
while (scope) {
991+
scope.through.push(...references);
992+
scope = scope.upper;
952993
}
953994
}
954995

src/parser/analyze-type/index.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { Context } from "../../context";
2+
3+
/**
4+
* Append store type declarations.
5+
* Append TypeScript code like
6+
* `declare let $foo: Parameters<Parameters<(typeof foo)["subscribe"]>[0]>[0];`
7+
* to define the type information for like $foo variable.
8+
*/
9+
export function appendDeclareStoreTypes(ctx: Context): void {
10+
const vcode = ctx.sourceCode.scripts.vcode;
11+
const extractStoreRe = /\$[\p{ID_Start}$_][\p{ID_Continue}$\u200c\u200d]*/giu;
12+
let m;
13+
const maybeStores = new Set<string>();
14+
while ((m = extractStoreRe.exec(vcode))) {
15+
const storeName = m[0];
16+
const originalName = storeName.slice(1);
17+
maybeStores.add(originalName);
18+
}
19+
20+
ctx.scriptLet.appendDeclareMaybeStores(maybeStores);
21+
}

src/parser/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
analyzeStoreScope,
2020
} from "./analyze-scope";
2121
import { ParseError } from "../errors";
22+
import { appendDeclareStoreTypes } from "./analyze-type";
2223

2324
export interface ESLintProgram extends Program {
2425
comments: Comment[];
@@ -76,6 +77,8 @@ export function parseForESLint(
7677
parserOptions
7778
);
7879

80+
if (ctx.isTypeScript()) appendDeclareStoreTypes(ctx);
81+
7982
const resultScript = parseScript(ctx.sourceCode.scripts, parserOptions);
8083
ctx.scriptLet.restore(resultScript);
8184
ctx.tokens.push(...resultScript.ast.tokens);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script lang="ts">
2+
import { _ } from './svelte-i18n';
3+
</script>
4+
5+
<main>
6+
<input name={$_('test')}>
7+
</main>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/* eslint eslint-comments/require-description: 0, @typescript-eslint/explicit-module-boundary-types: 0 */
2+
import type { Linter } from "eslint";
3+
import { BASIC_PARSER_OPTIONS } from "../../../src/parser/test-utils";
4+
import { rules } from "@typescript-eslint/eslint-plugin";
5+
export function setupLinter(linter: Linter) {
6+
linter.defineRule(
7+
"@typescript-eslint/no-unsafe-call",
8+
rules["no-unsafe-call"] as never
9+
);
10+
}
11+
12+
export function getConfig() {
13+
return {
14+
parser: "svelte-eslint-parser",
15+
parserOptions: BASIC_PARSER_OPTIONS,
16+
rules: {
17+
"@typescript-eslint/no-unsafe-call": "error",
18+
},
19+
env: {
20+
browser: true,
21+
es2021: true,
22+
},
23+
};
24+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import type { Readable } from "svelte/store";
2+
3+
declare type MessageFormatter = (id: string) => string;
4+
declare const $format: Readable<MessageFormatter>;
5+
6+
export { $format as _ };
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script lang="ts">
2+
import { _ } from './i18n-test-svelte-i18n';
3+
</script>
4+
5+
<main>
6+
<input name={$_('test')}>
7+
</main>

0 commit comments

Comments
 (0)