Skip to content

Commit cae678c

Browse files
committed
feat: treat jsdoc with @typescript-eslint/parser
1 parent fd076a4 commit cae678c

17 files changed

+1300
-45
lines changed

src/context/index.ts

+16-6
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,17 @@ import { LetDirectiveCollections } from "./let-directive-collection.js";
1818
import { parseAttributes } from "../parser/html.js";
1919
import { sortedLastIndex } from "../utils/index.js";
2020
import {
21-
isTypeScript,
21+
getLanguage,
2222
type NormalizedParserOptions,
2323
} from "../parser/parser-options.js";
2424

2525
export class ScriptsSourceCode {
2626
private raw: string;
2727

28+
public getRaw(): string {
29+
return this.raw;
30+
}
31+
2832
private trimmedRaw: string;
2933

3034
public readonly attrs: Record<string, string | undefined>;
@@ -175,7 +179,7 @@ export class Context {
175179
public readonly snippets: SvelteSnippetBlock[] = [];
176180

177181
// ----- States ------
178-
private readonly state: { isTypeScript?: boolean } = {};
182+
private readonly state: { language?: "js" | "ts" | "jsdoc" | string } = {};
179183

180184
private readonly blocks: Block[] = [];
181185

@@ -321,11 +325,17 @@ export class Context {
321325
}
322326

323327
public isTypeScript(): boolean {
324-
if (this.state.isTypeScript != null) {
325-
return this.state.isTypeScript;
328+
return this.getLanguage() === "ts";
329+
}
330+
331+
public getLanguage(): "js" | "ts" | "jsdoc" | string {
332+
if (this.state.language != null) {
333+
return this.state.language;
326334
}
327-
const lang = this.sourceCode.scripts.attrs.lang;
328-
return (this.state.isTypeScript = isTypeScript(this.parserOptions, lang));
335+
const rawLang = this.sourceCode.scripts.attrs.lang;
336+
const code = this.sourceCode.scripts.getRaw();
337+
const language = getLanguage(this.parserOptions, rawLang, code);
338+
return (this.state.language = language);
329339
}
330340

331341
public stripScriptCode(start: number, end: number): void {

src/parser/index.ts

+30-17
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import {
3939
} from "./style-context.js";
4040
import { getGlobalsForSvelte, getGlobalsForSvelteScript } from "./globals.js";
4141
import type { NormalizedParserOptions } from "./parser-options.js";
42-
import { isTypeScript, normalizeParserOptions } from "./parser-options.js";
42+
import { getLanguage, normalizeParserOptions } from "./parser-options.js";
4343
import { getFragmentFromRoot } from "./compat.js";
4444
import {
4545
isEnableRunes,
@@ -143,18 +143,24 @@ function parseAsSvelte(
143143
);
144144

145145
const scripts = ctx.sourceCode.scripts;
146-
const resultScript = ctx.isTypeScript()
147-
? parseTypeScriptInSvelte(
148-
scripts.getCurrentVirtualCodeInfo(),
149-
scripts.attrs,
150-
parserOptions,
151-
{ slots: ctx.slots, svelteParseContext },
152-
)
153-
: parseScriptInSvelte(
154-
scripts.getCurrentVirtualCode(),
155-
scripts.attrs,
156-
parserOptions,
157-
);
146+
const language = ctx.getLanguage();
147+
148+
const resultScript =
149+
language === "ts" || language === "jsdoc"
150+
? parseTypeScriptInSvelte(
151+
scripts.getCurrentVirtualCodeInfo(),
152+
{
153+
...scripts.attrs,
154+
lang: language === "jsdoc" ? "ts" : scripts.attrs.lang,
155+
},
156+
parserOptions,
157+
{ slots: ctx.slots, svelteParseContext },
158+
)
159+
: parseScriptInSvelte(
160+
scripts.getCurrentVirtualCode(),
161+
scripts.attrs,
162+
parserOptions,
163+
);
158164
ctx.scriptLet.restore(resultScript);
159165
ctx.tokens.push(...resultScript.ast.tokens);
160166
ctx.comments.push(...resultScript.ast.comments);
@@ -252,10 +258,17 @@ function parseAsScript(
252258
parserOptions: NormalizedParserOptions,
253259
svelteParseContext: SvelteParseContext,
254260
): ParseResult {
255-
const lang = parserOptions.filePath?.split(".").pop();
256-
const resultScript = isTypeScript(parserOptions, lang)
257-
? parseTypeScript(code, { lang }, parserOptions, svelteParseContext)
258-
: parseScript(code, { lang }, parserOptions);
261+
const rawLang = parserOptions.filePath?.split(".").pop();
262+
const lang = getLanguage(parserOptions, rawLang, code);
263+
const resultScript =
264+
lang === "ts" || lang === "jsdoc"
265+
? parseTypeScript(
266+
code,
267+
{ lang: lang === "jsdoc" ? "ts" : rawLang },
268+
parserOptions,
269+
svelteParseContext,
270+
)
271+
: parseScript(code, { lang: rawLang }, parserOptions);
259272

260273
// Add $$xxx variable
261274
addGlobalVariables(

src/parser/parser-options.ts

+45-12
Original file line numberDiff line numberDiff line change
@@ -63,35 +63,50 @@ const TS_PARSER_NAMES = [
6363
"typescript-eslint-parser-for-extra-files",
6464
];
6565

66-
export function isTypeScript(
66+
export function getLanguage(
6767
parserOptions: NormalizedParserOptions,
6868
lang: string | undefined,
69-
): boolean {
70-
if (!lang) {
71-
return false;
69+
code: string | undefined,
70+
): "js" | "ts" | "jsdoc" | string {
71+
const hasJsDoc = code ? jsdocTags.some((tag) => tag.test(code)) : false;
72+
if (!lang && !hasJsDoc) {
73+
return "js";
7274
}
73-
const parserValue = getParserForLang(lang, parserOptions?.parser);
75+
76+
function getFinalLang(isTS: boolean): string {
77+
if (isTS) {
78+
if (lang) return lang;
79+
return hasJsDoc ? "jsdoc" : "ts";
80+
}
81+
return lang || "js";
82+
}
83+
84+
const parserValue = getParserForLang(
85+
lang || (hasJsDoc ? "ts" : undefined),
86+
parserOptions?.parser,
87+
);
7488
if (typeof parserValue !== "string") {
75-
return (
89+
const isTS =
7690
maybeTSESLintParserObject(parserValue) ||
77-
isTSESLintParserObject(parserValue)
78-
);
91+
isTSESLintParserObject(parserValue);
92+
return getFinalLang(isTS);
7993
}
8094
const parserName = parserValue;
8195
if (TS_PARSER_NAMES.includes(parserName)) {
82-
return true;
96+
return getFinalLang(true);
8397
}
8498
if (TS_PARSER_NAMES.some((nm) => parserName.includes(nm))) {
8599
let targetPath = parserName;
86100
while (targetPath) {
87101
const pkgPath = path.join(targetPath, "package.json");
88102
if (fs.existsSync(pkgPath)) {
89103
try {
90-
return TS_PARSER_NAMES.includes(
104+
const isTS = TS_PARSER_NAMES.includes(
91105
JSON.parse(fs.readFileSync(pkgPath, "utf-8"))?.name,
92106
);
107+
return getFinalLang(isTS);
93108
} catch {
94-
return false;
109+
return getFinalLang(false);
95110
}
96111
}
97112
const parent = path.dirname(targetPath);
@@ -102,5 +117,23 @@ export function isTypeScript(
102117
}
103118
}
104119

105-
return false;
120+
return getFinalLang(false);
106121
}
122+
123+
const jsdocTags = [
124+
/@type\s/,
125+
/@param\s/,
126+
/@arg\s/,
127+
/@argument\s/,
128+
/@returns\s/,
129+
/@return\s/,
130+
/@typedef\s/,
131+
/@callback\s/,
132+
/@template\s/,
133+
/@class\s/,
134+
/@constructor\s/,
135+
/@this\s/,
136+
/@extends\s/,
137+
/@augments\s/,
138+
/@enum\s/,
139+
] as const;

tests/fixtures/integrations/parser-object-tests/ts-multiple-parser-setup.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export function getConfig(): Linter.Config {
88
return {
99
languageOptions: {
1010
parser,
11-
parserOptions: generateParserOptions({ parser: { ts } }),
11+
parserOptions: generateParserOptions({ parser: ts }),
1212
globals: {
1313
...globals.browser,
1414
...globals.es2021,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<script>
2+
/** @param {number} x */
3+
function test(x) {
4+
return x + 1;
5+
}
6+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[
2+
{
3+
"ruleId": "@typescript-eslint/no-unsafe-return",
4+
"code": "return x + 1;",
5+
"line": 4,
6+
"column": 5,
7+
"message": "Unsafe return of a value of type `any`."
8+
}
9+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import type { Linter } from "eslint";
2+
import { generateParserOptions } from "../../../src/parser/test-utils";
3+
import { rules } from "@typescript-eslint/eslint-plugin";
4+
import * as ts from "@typescript-eslint/parser";
5+
import * as parser from "../../../../src";
6+
import globals from "globals";
7+
8+
export function getConfig(): Linter.Config {
9+
return {
10+
plugins: {
11+
"@typescript-eslint": {
12+
rules: rules as any,
13+
},
14+
},
15+
languageOptions: {
16+
parser,
17+
parserOptions: generateParserOptions({ parser: ts }),
18+
globals: {
19+
...globals.browser,
20+
...globals.es2021,
21+
},
22+
},
23+
rules: {
24+
"@typescript-eslint/no-unsafe-argument": "error",
25+
"@typescript-eslint/no-unsafe-assignment": "error",
26+
"@typescript-eslint/no-unsafe-call": "error",
27+
"@typescript-eslint/no-unsafe-member-access": "error",
28+
"@typescript-eslint/no-unsafe-return": "error",
29+
},
30+
};
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<script>
2+
/** @param {number} x */
3+
function test(x) {
4+
return x + 1;
5+
}
6+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[
2+
{
3+
"ruleId": "no-unused-vars",
4+
"code": "test",
5+
"line": 3,
6+
"column": 12
7+
}
8+
]

0 commit comments

Comments
 (0)