diff --git a/README.md b/README.md index 892fbb4f..6209dbc9 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,28 @@ If you want to switch the parser for each lang, specify the object. } ``` +#### Parser Object + +When using JavaScript configuration (`.eslintrc.js`), you can also give the parser object directly. + +```js +const tsParser = require("@typescript-eslint/parser") +const espree = require("espree") + +module.exports = { + parser: "svelte-eslint-parser", + parserOptions: { + // Single parser + parser: tsParser, + // Multiple parser + parser: { + js: espree, + ts: tsParser, + } + }, +} +``` + ## :computer: Editor Integrations ### Visual Studio Code diff --git a/explorer-v2/build-system/pre-build/webpack.config.js b/explorer-v2/build-system/pre-build/webpack.config.js index cc044e4d..e4f70963 100644 --- a/explorer-v2/build-system/pre-build/webpack.config.js +++ b/explorer-v2/build-system/pre-build/webpack.config.js @@ -74,19 +74,6 @@ export default [ 'svelte/compiler': '$$inject_svelte_compiler$$', espree: '$$inject_espree$$' }, - module: { - rules: [ - { - test: /\/resolve-parser\.js$/u, - loader: 'string-replace-loader', - options: { - search: 'require\\(name\\)', - replace: `__non_webpack_require__(name)`, - flags: '' - } - } - ] - }, plugins: [ new WrapperPlugin({ test: /svelte-eslint-parser\.js/, diff --git a/explorer-v2/src/lib/VirtualScriptCode.svelte b/explorer-v2/src/lib/VirtualScriptCode.svelte index 8ea021b6..d48dc018 100644 --- a/explorer-v2/src/lib/VirtualScriptCode.svelte +++ b/explorer-v2/src/lib/VirtualScriptCode.svelte @@ -4,6 +4,7 @@ import MonacoEditor from './MonacoEditor.svelte'; import * as svelteEslintParser from 'svelte-eslint-parser'; + let tsParser = undefined; let loaded = false; import('@typescript-eslint/parser') .then((parser) => { @@ -14,8 +15,8 @@ env: {} }; } - window.require.define('@typescript-eslint/parser', parser); } + tsParser = parser; }) .then(() => { loaded = true; @@ -49,7 +50,7 @@ const start = Date.now(); try { virtualScriptCode = svelteEslintParser.parseForESLint(svelteValue, { - parser: '@typescript-eslint/parser' + parser: tsParser })._virtualScriptCode; } catch (e) { // eslint-disable-next-line no-console -- Demo diff --git a/src/context/index.ts b/src/context/index.ts index b94603d5..4021ae63 100644 --- a/src/context/index.ts +++ b/src/context/index.ts @@ -11,9 +11,10 @@ import type { import type ESTree from "estree"; import { ScriptLetContext } from "./script-let"; import { LetDirectiveCollections } from "./let-directive-collection"; -import { getParserName } from "../parser/resolve-parser"; +import { getParserForLang } from "../parser/resolve-parser"; import type { AttributeToken } from "../parser/html"; import { parseAttributes } from "../parser/html"; +import { maybeTSESLintParserObject } from "../parser/parser-object"; export class ScriptsSourceCode { private raw: string; @@ -202,13 +203,20 @@ export class Context { if (!lang) { return (this.state.isTypeScript = false); } - const parserName = getParserName( + const parserValue = getParserForLang( this.sourceCode.scripts.attrs, this.parserOptions?.parser ); - if (parserName === "@typescript-eslint/parser") { + if ( + maybeTSESLintParserObject(parserValue) || + parserValue === "@typescript-eslint/parser" + ) { return (this.state.isTypeScript = true); } + if (typeof parserValue !== "string") { + return (this.state.isTypeScript = false); + } + const parserName = parserValue; if (parserName.includes("@typescript-eslint/parser")) { let targetPath = parserName; while (targetPath) { diff --git a/src/parser/espree.ts b/src/parser/espree.ts index a7d9796e..3c5b21ab 100644 --- a/src/parser/espree.ts +++ b/src/parser/espree.ts @@ -1,6 +1,6 @@ import Module from "module"; import path from "path"; -import type { ESLintCustomParser } from "./resolve-parser"; +import type { BasicParserObject } from "./parser-object"; const createRequire: (filename: string) => (modName: string) => any = // Added in v12.2.0 @@ -19,7 +19,7 @@ const createRequire: (filename: string) => (modName: string) => any = return mod.exports; }); -let espreeCache: ESLintCustomParser | null = null; +let espreeCache: BasicParserObject | null = null; /** Checks if given path is linter path */ function isLinterPath(p: string): boolean { @@ -35,7 +35,7 @@ function isLinterPath(p: string): boolean { * Load `espree` from the loaded ESLint. * If the loaded ESLint was not found, just returns `require("espree")`. */ -export function getEspree(): ESLintCustomParser { +export function getEspree(): BasicParserObject { if (!espreeCache) { // Lookup the loaded eslint const linterPath = Object.keys(require.cache || {}).find(isLinterPath); diff --git a/src/parser/parser-object.ts b/src/parser/parser-object.ts new file mode 100644 index 00000000..b7afa19d --- /dev/null +++ b/src/parser/parser-object.ts @@ -0,0 +1,54 @@ +import type { ESLintExtendedProgram, ESLintProgram } from "."; +import type * as tsESLintParser from "@typescript-eslint/parser"; +type TSESLintParser = typeof tsESLintParser; +/** + * The type of basic ESLint custom parser. + * e.g. espree + */ +export type BasicParserObject = { + parse(code: string, options: any): ESLintProgram; + parseForESLint: undefined; +}; +/** + * The type of ESLint custom parser enhanced for ESLint. + * e.g. @babel/eslint-parser, @typescript-eslint/parser + */ +export type EnhancedParserObject = { + parseForESLint(code: string, options: any): ESLintExtendedProgram; + parse: undefined; +}; + +/** + * The type of ESLint (custom) parsers. + */ +export type ParserObject = EnhancedParserObject | BasicParserObject; + +/** Checks whether given object is ParserObject */ +export function isParserObject(value: unknown): value is ParserObject { + return isEnhancedParserObject(value) || isBasicParserObject(value); +} +/** Checks whether given object is EnhancedParserObject */ +export function isEnhancedParserObject( + value: unknown +): value is EnhancedParserObject { + return Boolean(value && typeof (value as any).parseForESLint === "function"); +} +/** Checks whether given object is BasicParserObject */ +export function isBasicParserObject( + value: unknown +): value is BasicParserObject { + return Boolean(value && typeof (value as any).parse === "function"); +} + +/** Checks whether given object is "@typescript-eslint/parser" */ +export function maybeTSESLintParserObject( + value: unknown +): value is TSESLintParser { + return ( + isEnhancedParserObject(value) && + isBasicParserObject(value) && + typeof (value as any).createProgram === "function" && + typeof (value as any).clearCaches === "function" && + typeof (value as any).version === "string" + ); +} diff --git a/src/parser/resolve-parser.ts b/src/parser/resolve-parser.ts index aba22c39..3192eed4 100644 --- a/src/parser/resolve-parser.ts +++ b/src/parser/resolve-parser.ts @@ -1,29 +1,26 @@ -import type { ESLintExtendedProgram, ESLintProgram } from "."; import { getEspree } from "./espree"; -/** - * The interface of a result of ESLint custom parser. - */ -export type ESLintCustomParserResult = ESLintProgram | ESLintExtendedProgram; -/** - * The interface of ESLint custom parsers. - */ -export interface ESLintCustomParser { - parse(code: string, options: any): ESLintCustomParserResult; - parseForESLint?(code: string, options: any): ESLintCustomParserResult; -} +import type { ParserObject } from "./parser-object"; +import { isParserObject } from "./parser-object"; + +type UserOptionParser = + | string + | ParserObject + | Record + | undefined; -/** Get parser name */ -export function getParserName( +/** Get parser for script lang */ +export function getParserForLang( attrs: Record, - parser: any -): string { + parser: UserOptionParser +): string | ParserObject { if (parser) { - if (typeof parser === "string" && parser !== "espree") { + if (typeof parser === "string" || isParserObject(parser)) { return parser; - } else if (typeof parser === "object") { - const name = parser[attrs.lang || "js"]; - if (typeof name === "string") { - return getParserName(attrs, name); + } + if (typeof parser === "object") { + const value = parser[attrs.lang || "js"]; + if (typeof value === "string" || isParserObject(value)) { + return value; } } } @@ -33,12 +30,15 @@ export function getParserName( /** Get parser */ export function getParser( attrs: Record, - parser: any -): ESLintCustomParser { - const name = getParserName(attrs, parser); - if (name !== "espree") { + parser: UserOptionParser +): ParserObject { + const parserValue = getParserForLang(attrs, parser); + if (isParserObject(parserValue)) { + return parserValue; + } + if (parserValue !== "espree") { // eslint-disable-next-line @typescript-eslint/no-require-imports -- ignore - return require(name); + return require(parserValue); } return getEspree(); } diff --git a/src/parser/script.ts b/src/parser/script.ts index dcabaef0..cac6e906 100644 --- a/src/parser/script.ts +++ b/src/parser/script.ts @@ -3,6 +3,7 @@ import { analyzeScope } from "./analyze-scope"; import { traverseNodes } from "../traverse"; import type { ScriptsSourceCode } from "../context"; import { getParser } from "./resolve-parser"; +import { isEnhancedParserObject } from "./parser-object"; /** * Parse for script @@ -47,8 +48,9 @@ function parseScriptWithoutAnalyzeScope( ): ESLintExtendedProgram { const parser = getParser(attrs, options.parser); - const result = - parser.parseForESLint?.(vcode, options) ?? parser.parse?.(vcode, options); + const result = isEnhancedParserObject(parser) + ? parser.parseForESLint(vcode, options) + : parser.parse(vcode, options); if ("ast" in result && result.ast != null) { result._virtualScriptCode = vcode; diff --git a/tests/fixtures/integrations/parser-object-tests/ts-multiple-parser-input.svelte b/tests/fixtures/integrations/parser-object-tests/ts-multiple-parser-input.svelte new file mode 100644 index 00000000..3ae3912e --- /dev/null +++ b/tests/fixtures/integrations/parser-object-tests/ts-multiple-parser-input.svelte @@ -0,0 +1,5 @@ + + +{num} diff --git a/tests/fixtures/integrations/parser-object-tests/ts-multiple-parser-output.json b/tests/fixtures/integrations/parser-object-tests/ts-multiple-parser-output.json new file mode 100644 index 00000000..0637a088 --- /dev/null +++ b/tests/fixtures/integrations/parser-object-tests/ts-multiple-parser-output.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/fixtures/integrations/parser-object-tests/ts-multiple-parser-setup.ts b/tests/fixtures/integrations/parser-object-tests/ts-multiple-parser-setup.ts new file mode 100644 index 00000000..6f1b9034 --- /dev/null +++ b/tests/fixtures/integrations/parser-object-tests/ts-multiple-parser-setup.ts @@ -0,0 +1,17 @@ +/* eslint eslint-comments/require-description: 0, @typescript-eslint/explicit-module-boundary-types: 0 */ +import { BASIC_PARSER_OPTIONS } from "../../../src/parser/test-utils"; +import * as ts from "@typescript-eslint/parser"; + +export function getConfig() { + return { + parser: "svelte-eslint-parser", + parserOptions: { + ...BASIC_PARSER_OPTIONS, + parser: { ts }, + }, + env: { + browser: true, + es2021: true, + }, + }; +} diff --git a/tests/fixtures/integrations/parser-object-tests/ts-single-parser-input.svelte b/tests/fixtures/integrations/parser-object-tests/ts-single-parser-input.svelte new file mode 100644 index 00000000..3ae3912e --- /dev/null +++ b/tests/fixtures/integrations/parser-object-tests/ts-single-parser-input.svelte @@ -0,0 +1,5 @@ + + +{num} diff --git a/tests/fixtures/integrations/parser-object-tests/ts-single-parser-output.json b/tests/fixtures/integrations/parser-object-tests/ts-single-parser-output.json new file mode 100644 index 00000000..0637a088 --- /dev/null +++ b/tests/fixtures/integrations/parser-object-tests/ts-single-parser-output.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/fixtures/integrations/parser-object-tests/ts-single-parser-setup.ts b/tests/fixtures/integrations/parser-object-tests/ts-single-parser-setup.ts new file mode 100644 index 00000000..678b17c1 --- /dev/null +++ b/tests/fixtures/integrations/parser-object-tests/ts-single-parser-setup.ts @@ -0,0 +1,17 @@ +/* eslint eslint-comments/require-description: 0, @typescript-eslint/explicit-module-boundary-types: 0 */ +import { BASIC_PARSER_OPTIONS } from "../../../src/parser/test-utils"; +import * as parser from "@typescript-eslint/parser"; + +export function getConfig() { + return { + parser: "svelte-eslint-parser", + parserOptions: { + ...BASIC_PARSER_OPTIONS, + parser, + }, + env: { + browser: true, + es2021: true, + }, + }; +}