From 652376fa410a117db0b6860d4e85d054d57fcfd7 Mon Sep 17 00:00:00 2001 From: armano2 Date: Sun, 22 May 2022 17:21:35 +0200 Subject: [PATCH 1/5] chore(website): rewrite web linter to typescript - reuse Linter instance - reuse virtual CompilerHost and use utilize typescript-vfs - update ecmaVersion to latest - correct minor issue in ESLintParseResult --- packages/utils/src/ts-eslint/Linter.ts | 4 +- .../website-eslint/src/linter/CompilerHost.js | 92 ------------- packages/website-eslint/src/linter/linter.js | 73 +---------- packages/website-eslint/src/linter/parser.js | 62 --------- packages/website-eslint/types/index.d.ts | 43 ++----- .../src/components/ASTViewerESTree.tsx | 2 +- .../website/src/components/Playground.tsx | 8 +- .../ast/serializer/serializerESTree.ts | 2 +- .../ast/serializer/serializerScope.ts | 2 +- .../src/components/config/ConfigEslint.tsx | 3 +- .../src/components/editor/LoadedEditor.tsx | 10 +- .../editor/createProvideCodeActions.ts | 4 +- .../src/components/editor/loadSandbox.ts | 10 +- .../website/src/components/editor/types.ts | 2 +- .../components/editor/useSandboxServices.ts | 18 +-- .../src/components/linter/CompilerHost.ts | 63 +++++++++ .../src/components/linter/WebLinter.ts | 121 ++++++++++++++++++ .../src/components/linter/config.ts} | 4 +- .../components/{editor => linter}/lintCode.ts | 9 +- .../components/{editor => linter}/utils.ts | 0 packages/website/src/components/types.ts | 10 +- 21 files changed, 244 insertions(+), 298 deletions(-) delete mode 100644 packages/website-eslint/src/linter/CompilerHost.js delete mode 100644 packages/website-eslint/src/linter/parser.js create mode 100644 packages/website/src/components/linter/CompilerHost.ts create mode 100644 packages/website/src/components/linter/WebLinter.ts rename packages/{website-eslint/src/linter/config.js => website/src/components/linter/config.ts} (81%) rename packages/website/src/components/{editor => linter}/lintCode.ts (91%) rename packages/website/src/components/{editor => linter}/utils.ts (100%) diff --git a/packages/utils/src/ts-eslint/Linter.ts b/packages/utils/src/ts-eslint/Linter.ts index 77c338f5a753..c2a8e67fe3a3 100644 --- a/packages/utils/src/ts-eslint/Linter.ts +++ b/packages/utils/src/ts-eslint/Linter.ts @@ -76,7 +76,7 @@ declare class LinterBase { /** * Performs multiple autofix passes over the text until as many fixes as possible have been applied. - * @param text The source text to apply fixes to. + * @param code The source text to apply fixes to. * @param config The ESLint config object to use. * @param options The ESLint options object to use. * @returns The result of the fix operation as returned from the SourceCodeFixer. @@ -316,7 +316,7 @@ namespace Linter { export interface ESLintParseResult { ast: TSESTree.Program; - parserServices?: ParserServices; + services?: ParserServices; scopeManager?: Scope.ScopeManager; visitorKeys?: SourceCode.VisitorKeys; } diff --git a/packages/website-eslint/src/linter/CompilerHost.js b/packages/website-eslint/src/linter/CompilerHost.js deleted file mode 100644 index f48c77109c96..000000000000 --- a/packages/website-eslint/src/linter/CompilerHost.js +++ /dev/null @@ -1,92 +0,0 @@ -import { - getDefaultLibFileName, - ScriptKind, - createSourceFile, - ScriptTarget, -} from 'typescript'; - -function getScriptKind(isJsx, filePath) { - const extension = (/(\.[a-z]+)$/.exec(filePath)?.[0] || '').toLowerCase(); - - switch (extension) { - case '.ts': - return ScriptKind.TS; - case '.tsx': - return ScriptKind.TSX; - case '.js': - return ScriptKind.JS; - - case '.jsx': - return ScriptKind.JSX; - - case '.json': - return ScriptKind.JSON; - - default: - // unknown extension, force typescript to ignore the file extension, and respect the user's setting - return isJsx ? ScriptKind.TSX : ScriptKind.TS; - } -} - -export class CompilerHost { - constructor(libs, isJsx) { - this.files = []; - this.isJsx = isJsx || false; - - if (libs) { - for (const [key, value] of libs) { - this.files[key] = value; - } - } - } - - fileExists(name) { - return !!this.files[name]; - } - - getCanonicalFileName(name) { - return name; - } - - getCurrentDirectory() { - return '/'; - } - - getDirectories() { - return []; - } - - getDefaultLibFileName(options) { - return '/' + getDefaultLibFileName(options); - } - - getNewLine() { - return '\n'; - } - - useCaseSensitiveFileNames() { - return true; - } - - writeFile() { - return null; - } - - readFile(name) { - if (this.fileExists(name)) { - return this.files[name]; - } else { - return ''; // fallback, in case if file is not available - } - } - - getSourceFile(name) { - return createSourceFile( - name, - this.readFile(name), - ScriptTarget.Latest, - /* setParentNodes */ true, - getScriptKind(this.isJsx, name), - ); - } -} diff --git a/packages/website-eslint/src/linter/linter.js b/packages/website-eslint/src/linter/linter.js index 06a4db0d3fb2..34f2c1900db8 100644 --- a/packages/website-eslint/src/linter/linter.js +++ b/packages/website-eslint/src/linter/linter.js @@ -1,74 +1,15 @@ import 'vs/language/typescript/tsWorker'; -import { parseForESLint } from './parser'; import { Linter } from 'eslint'; import rules from '@typescript-eslint/eslint-plugin/dist/rules'; -const PARSER_NAME = '@typescript-eslint/parser'; - -export function loadLinter(libs, options) { +export function createLinter() { const linter = new Linter(); - let storedAST; - let storedTsAST; - let storedScope; - - let compilerOptions = options; - - linter.defineParser(PARSER_NAME, { - parseForESLint(code, eslintOptions) { - const toParse = parseForESLint( - code, - eslintOptions, - compilerOptions, - libs, - ); - storedAST = toParse.ast; - storedTsAST = toParse.tsAst; - storedScope = toParse.scopeManager; - return toParse; - }, - // parse(code: string, options: ParserOptions): ParseForESLintResult['ast'] { - // const toParse = parseForESLint(code, options); - // storedAST = toParse.ast; - // return toParse.ast; - // }, - }); - - for (const name of Object.keys(rules)) { + for (const name in rules) { linter.defineRule(`@typescript-eslint/${name}`, rules[name]); } - - const ruleNames = Array.from(linter.getRules()).map(value => { - return { - name: value[0], - description: value[1]?.meta?.docs?.description, - }; - }); - - return { - ruleNames: ruleNames, - - updateOptions(options) { - compilerOptions = options || {}; - }, - - getScope() { - return storedScope; - }, - - getAst() { - return storedAST; - }, - - getTsAst() { - return storedTsAST; - }, - - lint(code, parserOptions, rules) { - return linter.verify(code, { - parser: PARSER_NAME, - parserOptions, - rules, - }); - }, - }; + return linter; } + +export { analyze } from '@typescript-eslint/scope-manager/dist/analyze'; +export { visitorKeys } from '@typescript-eslint/visitor-keys/dist/visitor-keys'; +export { astConverter } from '@typescript-eslint/typescript-estree/dist/ast-converter'; diff --git a/packages/website-eslint/src/linter/parser.js b/packages/website-eslint/src/linter/parser.js deleted file mode 100644 index fc637fe65b7d..000000000000 --- a/packages/website-eslint/src/linter/parser.js +++ /dev/null @@ -1,62 +0,0 @@ -import { analyze } from '@typescript-eslint/scope-manager/dist/analyze'; -import { visitorKeys } from '@typescript-eslint/visitor-keys/dist/visitor-keys'; -import { astConverter } from '@typescript-eslint/typescript-estree/dist/ast-converter'; -import { extra } from './config.js'; -import { CompilerHost } from './CompilerHost'; -import { createProgram } from 'typescript'; - -export function createASTProgram(code, isJsx, compilerOptions, libs) { - const fileName = isJsx ? '/demo.tsx' : '/demo.ts'; - const compilerHost = new CompilerHost(libs, isJsx); - - compilerHost.files[fileName] = code; - const program = createProgram( - Object.keys(compilerHost.files), - compilerOptions, - compilerHost, - ); - const ast = program.getSourceFile(fileName); - return { - ast, - program, - }; -} - -export function parseForESLint(code, eslintOptions, compilerOptions, libs) { - const isJsx = eslintOptions.ecmaFeatures?.jsx ?? false; - - const { ast: tsAst, program } = createASTProgram( - code, - isJsx, - compilerOptions, - libs, - ); - - const { estree: ast, astMaps } = astConverter( - tsAst, - { ...extra, code, jsx: isJsx }, - true, - ); - - const services = { - hasFullTypeInformation: true, - program, - esTreeNodeToTSNodeMap: astMaps.esTreeNodeToTSNodeMap, - tsNodeToESTreeNodeMap: astMaps.tsNodeToESTreeNodeMap, - }; - - const scopeManager = analyze(ast, { - ecmaVersion: - eslintOptions.ecmaVersion === 'latest' ? 1e8 : eslintOptions.ecmaVersion, - globalReturn: eslintOptions.ecmaFeatures?.globalReturn ?? false, - sourceType: eslintOptions.sourceType ?? 'script', - }); - - return { - ast, - tsAst, - services, - scopeManager, - visitorKeys, - }; -} diff --git a/packages/website-eslint/types/index.d.ts b/packages/website-eslint/types/index.d.ts index aa5a2a43ed7f..d0ee27a6280c 100644 --- a/packages/website-eslint/types/index.d.ts +++ b/packages/website-eslint/types/index.d.ts @@ -1,38 +1,11 @@ -import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; -import type { ParserOptions } from '@typescript-eslint/types'; -import type { SourceFile, CompilerOptions } from 'typescript'; +import type { TSESLint } from '@typescript-eslint/utils'; -export type LintMessage = TSESLint.Linter.LintMessage; -export type RuleFix = TSESLint.RuleFix; -export type RulesRecord = TSESLint.Linter.RulesRecord; -export type RuleEntry = TSESLint.Linter.RuleEntry; +import { analyze } from '@typescript-eslint/scope-manager/dist/analyze'; +import { astConverter } from '@typescript-eslint/typescript-estree/dist/ast-converter'; -export interface WebLinter { - ruleNames: { name: string; description?: string }[]; - - getAst(): TSESTree.Program; - getTsAst(): SourceFile; - getScope(): Record; - updateOptions(options?: Record): void; - - lint( - code: string, - parserOptions: ParserOptions, - rules?: RulesRecord, - ): LintMessage[]; +export interface LintUtils { + createLinter: () => TSESLint.Linter; + analyze: typeof analyze; + visitorKeys: TSESLint.SourceCode.VisitorKeys; + astConverter: typeof astConverter; } - -export interface LinterLoader { - loadLinter( - libMap: Map, - compilerOptions: CompilerOptions, - ): WebLinter; -} - -export type { - DebugLevel, - EcmaVersion, - ParserOptions, - SourceType, - TSESTree, -} from '@typescript-eslint/types'; diff --git a/packages/website/src/components/ASTViewerESTree.tsx b/packages/website/src/components/ASTViewerESTree.tsx index 7d46efdfb110..7dfcef130cf2 100644 --- a/packages/website/src/components/ASTViewerESTree.tsx +++ b/packages/website/src/components/ASTViewerESTree.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'; import ASTViewer from './ast/ASTViewer'; import type { ASTViewerBaseProps, ASTViewerModelMap } from './ast/types'; -import type { TSESTree } from '@typescript-eslint/website-eslint'; +import type { TSESTree } from '@typescript-eslint/utils'; import { serialize } from './ast/serializer/serializer'; import { createESTreeSerializer } from './ast/serializer/serializerESTree'; diff --git a/packages/website/src/components/Playground.tsx b/packages/website/src/components/Playground.tsx index 6f28f21d36e9..d602e4574825 100644 --- a/packages/website/src/components/Playground.tsx +++ b/packages/website/src/components/Playground.tsx @@ -17,7 +17,7 @@ import ASTViewerTS from './ASTViewerTS'; import type { RuleDetails, SelectedRange } from './types'; -import type { TSESTree } from '@typescript-eslint/website-eslint'; +import type { TSESTree } from '@typescript-eslint/utils'; import type { SourceFile } from 'typescript'; import ASTViewerScope from '@site/src/components/ASTViewerScope'; @@ -44,11 +44,11 @@ function Playground(): JSX.Element { showAST: false, sourceType: 'module', code: '', - ts: process.env.TS_VERSION, + ts: process.env.TS_VERSION!, rules: {}, tsConfig: {}, }); - const { isDarkTheme } = useColorMode(); + const { colorMode } = useColorMode(); const [esAst, setEsAst] = useState(); const [tsAst, setTsAST] = useState(); const [scope, setScope] = useState | string | null>(); @@ -83,7 +83,7 @@ function Playground(): JSX.Element { jsx={state.jsx} code={state.code} tsConfig={state.tsConfig} - darkTheme={isDarkTheme} + darkTheme={colorMode === 'dark'} sourceType={state.sourceType} rules={state.rules} showAST={state.showAST} diff --git a/packages/website/src/components/ast/serializer/serializerESTree.ts b/packages/website/src/components/ast/serializer/serializerESTree.ts index 7f467757c8a3..76aabfd024bd 100644 --- a/packages/website/src/components/ast/serializer/serializerESTree.ts +++ b/packages/website/src/components/ast/serializer/serializerESTree.ts @@ -1,6 +1,6 @@ import type { ASTViewerModel, Serializer } from '../types'; +import type { TSESTree } from '@typescript-eslint/utils'; import { isRecord } from '../utils'; -import type { TSESTree } from '@typescript-eslint/website-eslint'; export const propsToFilter = ['parent', 'comments', 'tokens']; diff --git a/packages/website/src/components/ast/serializer/serializerScope.ts b/packages/website/src/components/ast/serializer/serializerScope.ts index 176f49f92c5c..b000aba7db1e 100644 --- a/packages/website/src/components/ast/serializer/serializerScope.ts +++ b/packages/website/src/components/ast/serializer/serializerScope.ts @@ -1,5 +1,5 @@ import type { ASTViewerModel, Serializer, SelectedRange } from '../types'; -import type { TSESTree } from '@typescript-eslint/website-eslint'; +import type { TSESTree } from '@typescript-eslint/utils'; import { isRecord } from '../utils'; function isESTreeNode( diff --git a/packages/website/src/components/config/ConfigEslint.tsx b/packages/website/src/components/config/ConfigEslint.tsx index cb64ae0a46ad..f72e02f342c1 100644 --- a/packages/website/src/components/config/ConfigEslint.tsx +++ b/packages/website/src/components/config/ConfigEslint.tsx @@ -1,8 +1,7 @@ import React, { useCallback, useEffect, useState } from 'react'; -import type { RulesRecord, RuleEntry } from '@typescript-eslint/website-eslint'; import ConfigEditor, { ConfigOptionsType } from './ConfigEditor'; -import type { RuleDetails } from '../types'; +import type { RuleDetails, RulesRecord, RuleEntry } from '../types'; import { shallowEqual } from '../lib/shallowEqual'; export interface ModalEslintProps { diff --git a/packages/website/src/components/editor/LoadedEditor.tsx b/packages/website/src/components/editor/LoadedEditor.tsx index 9e4baa04da5b..d6433bfaee50 100644 --- a/packages/website/src/components/editor/LoadedEditor.tsx +++ b/packages/website/src/components/editor/LoadedEditor.tsx @@ -1,12 +1,12 @@ import React, { useMemo } from 'react'; import type Monaco from 'monaco-editor'; import { useEffect, useRef, useState } from 'react'; -import type { WebLinter } from '@typescript-eslint/website-eslint'; import type { SandboxInstance } from './useSandboxServices'; import type { CommonEditorProps } from './types'; +import type { WebLinter } from '../linter/WebLinter'; import { debounce } from '../lib/debounce'; -import { lintCode, LintCodeAction } from './lintCode'; +import { lintCode, LintCodeAction } from '../linter/lintCode'; import { createProvideCodeActions } from './createProvideCodeActions'; export interface LoadedEditorProps extends CommonEditorProps { @@ -83,9 +83,9 @@ export const LoadedEditor: React.FC = ({ ); } - onEsASTChange(fatalMessage ?? webLinter.getAst()); - onTsASTChange(fatalMessage ?? webLinter.getTsAst()); - onScopeChange(fatalMessage ?? webLinter.getScope()); + onEsASTChange(fatalMessage ?? webLinter.storedAST ?? ''); + onTsASTChange(fatalMessage ?? webLinter.storedTsAST ?? ''); + onScopeChange(fatalMessage ?? webLinter.storedScope ?? ''); onSelect(sandboxInstance.editor.getPosition()); }, 500), [code, jsx, sandboxInstance, rules, sourceType, tsConfig, webLinter], diff --git a/packages/website/src/components/editor/createProvideCodeActions.ts b/packages/website/src/components/editor/createProvideCodeActions.ts index b0930c19b322..1e9dfaca3899 100644 --- a/packages/website/src/components/editor/createProvideCodeActions.ts +++ b/packages/website/src/components/editor/createProvideCodeActions.ts @@ -1,6 +1,6 @@ import type Monaco from 'monaco-editor'; -import { createURI } from './utils'; -import type { LintCodeAction } from './lintCode'; +import type { LintCodeAction } from '../linter/lintCode'; +import { createURI } from '../linter/utils'; export function createProvideCodeActions( fixes: Map, diff --git a/packages/website/src/components/editor/loadSandbox.ts b/packages/website/src/components/editor/loadSandbox.ts index d25d9248bb6b..ac342d8e290c 100644 --- a/packages/website/src/components/editor/loadSandbox.ts +++ b/packages/website/src/components/editor/loadSandbox.ts @@ -1,6 +1,6 @@ import type * as TsWorker from '../../vendor/tsWorker'; import type * as SandboxFactory from '../../vendor/sandbox'; -import type { LinterLoader } from '@typescript-eslint/website-eslint'; +import type { LintUtils } from '@typescript-eslint/website-eslint'; type Monaco = typeof import('monaco-editor'); type TS = typeof import('typescript'); @@ -10,7 +10,7 @@ declare global { main: Monaco, tsWorker: typeof TsWorker, sandboxFactory: typeof SandboxFactory, - linter: LinterLoader, + lintUtils: LintUtils, ) => void; interface WindowRequire { (files: string[], cb: WindowRequireCb): void; @@ -31,7 +31,7 @@ export interface SandboxModel { tsWorker: typeof TsWorker; sandboxFactory: typeof SandboxFactory; ts: TS; - linter: LinterLoader; + lintUtils: LintUtils; } function loadSandbox(tsVersion: string): Promise { @@ -61,7 +61,7 @@ function loadSandbox(tsVersion: string): Promise { 'sandbox/index', 'linter/index', ], - (main, tsWorker, sandboxFactory, linter) => { + (main, tsWorker, sandboxFactory, lintUtils) => { const isOK = main && window.ts && sandboxFactory; if (isOK) { resolve({ @@ -69,7 +69,7 @@ function loadSandbox(tsVersion: string): Promise { tsWorker, sandboxFactory, ts: window.ts, - linter, + lintUtils, }); } else { reject( diff --git a/packages/website/src/components/editor/types.ts b/packages/website/src/components/editor/types.ts index edcbcf842d38..894a4fde6053 100644 --- a/packages/website/src/components/editor/types.ts +++ b/packages/website/src/components/editor/types.ts @@ -1,6 +1,6 @@ import type Monaco from 'monaco-editor'; import type { ConfigModel, SelectedRange } from '../types'; -import type { TSESTree } from '@typescript-eslint/website-eslint'; +import type { TSESTree } from '@typescript-eslint/utils'; import type { SourceFile } from 'typescript'; export interface CommonEditorProps extends ConfigModel { diff --git a/packages/website/src/components/editor/useSandboxServices.ts b/packages/website/src/components/editor/useSandboxServices.ts index 3fe7d7e1ddcb..698f136a9c3b 100644 --- a/packages/website/src/components/editor/useSandboxServices.ts +++ b/packages/website/src/components/editor/useSandboxServices.ts @@ -1,13 +1,14 @@ import { useEffect, useState } from 'react'; import type Monaco from 'monaco-editor'; -import type { LintMessage, WebLinter } from '@typescript-eslint/website-eslint'; +import type { TSESLint } from '@typescript-eslint/utils'; import type { RuleDetails } from '../types'; import type { createTypeScriptSandbox, SandboxConfig, } from '../../vendor/sandbox'; +import { WebLinter } from '../linter/WebLinter'; import { sandboxSingleton } from './loadSandbox'; import { editorEmbedId } from './EditorEmbed'; import { useColorMode } from '@docusaurus/theme-common'; @@ -24,7 +25,7 @@ export interface SandboxServicesProps { export type SandboxInstance = ReturnType; export interface SandboxServices { - fixes: Map; + fixes: Map; main: typeof Monaco; sandboxInstance: SandboxInstance; webLinter: WebLinter; @@ -35,7 +36,7 @@ export const useSandboxServices = ( ): Error | SandboxServices | undefined => { const [services, setServices] = useState(); const [loadedTs, setLoadedTs] = useState(props.ts); - const { isDarkTheme } = useColorMode(); + const { colorMode } = useColorMode(); useEffect(() => { if (props.ts !== loadedTs) { @@ -44,17 +45,17 @@ export const useSandboxServices = ( }, [props.ts, loadedTs]); useEffect(() => { - const fixes = new Map(); + const fixes = new Map(); let sandboxInstance: SandboxInstance | undefined; setLoadedTs(props.ts); sandboxSingleton(props.ts) - .then(async ({ main, sandboxFactory, ts, linter }) => { + .then(async ({ main, sandboxFactory, ts, lintUtils }) => { const compilerOptions: Monaco.languages.typescript.CompilerOptions = { noResolve: true, target: main.languages.typescript.ScriptTarget.ESNext, jsx: props.jsx ? main.languages.typescript.JsxEmit.React : undefined, - lib: ['esnext'], + lib: ['es2021', 'esnext'], module: main.languages.typescript.ModuleKind.ESNext, }; @@ -77,7 +78,7 @@ export const useSandboxServices = ( ts, ); sandboxInstance.monaco.editor.setTheme( - isDarkTheme ? 'vs-dark' : 'vs-light', + colorMode === 'dark' ? 'vs-dark' : 'vs-light', ); const libMap = await sandboxInstance.tsvfs.createDefaultMapFromCDN( @@ -86,8 +87,9 @@ export const useSandboxServices = ( true, window.ts, ); + const system = sandboxInstance.tsvfs.createSystem(libMap); - const webLinter = linter.loadLinter(libMap, compilerOptions); + const webLinter = new WebLinter(system, compilerOptions, lintUtils); props.onLoaded(webLinter.ruleNames, sandboxInstance.supportedVersions); diff --git a/packages/website/src/components/linter/CompilerHost.ts b/packages/website/src/components/linter/CompilerHost.ts new file mode 100644 index 000000000000..a96b1ac2e0a2 --- /dev/null +++ b/packages/website/src/components/linter/CompilerHost.ts @@ -0,0 +1,63 @@ +import type { ScriptKind, System, SourceFile, CompilerHost } from 'typescript'; + +function getScriptKind( + ts: typeof import('typescript'), + filePath: string, +): ScriptKind { + const extension = (/(\.[a-z]+)$/.exec(filePath)?.[0] ?? '').toLowerCase(); + + switch (extension) { + case '.ts': + return ts.ScriptKind.TS; + case '.tsx': + return ts.ScriptKind.TSX; + + case '.js': + return ts.ScriptKind.JS; + case '.jsx': + return ts.ScriptKind.JSX; + + case '.json': + return ts.ScriptKind.JSON; + + default: + // unknown extension, force typescript to ignore the file extension, and respect the user's setting + return ts.ScriptKind.TS; + } +} + +/** + * Creates an in-memory CompilerHost -which is essentially an extra wrapper to System + * which works with TypeScript objects - returns both a compiler host, and a way to add new SourceFile + * instances to the in-memory file system. + * + * based on typescript-vfs + * @see https://github.com/microsoft/TypeScript-Website/blob/d2613c0e57ae1be2f3a76e94b006819a1fc73d5e/packages/typescript-vfs/src/index.ts#L480 + */ +export function createVirtualCompilerHost( + sys: System, + ts: typeof import('typescript'), +): CompilerHost { + return { + ...sys, + getCanonicalFileName: (fileName: string) => fileName, + getDefaultLibFileName: options => '/' + ts.getDefaultLibFileName(options), + getCurrentDirectory: () => '/', + getDirectories: () => [], + getNewLine: () => sys.newLine, + getSourceFile(fileName, languageVersionOrOptions): SourceFile | undefined { + if (this.fileExists(fileName)) { + const file = this.readFile(fileName)!; + return ts.createSourceFile( + fileName, + file, + languageVersionOrOptions, + true, + getScriptKind(ts, fileName), + ); + } + return undefined; + }, + useCaseSensitiveFileNames: () => sys.useCaseSensitiveFileNames, + }; +} diff --git a/packages/website/src/components/linter/WebLinter.ts b/packages/website/src/components/linter/WebLinter.ts new file mode 100644 index 000000000000..bf8061285e86 --- /dev/null +++ b/packages/website/src/components/linter/WebLinter.ts @@ -0,0 +1,121 @@ +import type { + CompilerOptions, + SourceFile, + CompilerHost, + System, +} from 'typescript'; +import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; +import type { ParserServices } from '@typescript-eslint/utils/dist/ts-estree'; +import type { ParserOptions } from '@typescript-eslint/types'; +import type { LintUtils } from '@typescript-eslint/website-eslint'; + +import { createVirtualCompilerHost } from '@site/src/components/linter/CompilerHost'; +import { extra } from '@site/src/components/linter/config'; + +const PARSER_NAME = '@typescript-eslint/parser'; + +export class WebLinter { + private readonly host: CompilerHost; + + public storedAST?: TSESTree.Program; + public storedTsAST?: SourceFile; + public storedScope?: Record; + + private compilerOptions: CompilerOptions; + private linter: TSESLint.Linter; + private lintUtils: LintUtils; + + public ruleNames: { name: string; description?: string }[]; + + constructor( + system: System, + compilerOptions: CompilerOptions, + lintUtils: LintUtils, + ) { + this.compilerOptions = compilerOptions; + this.lintUtils = lintUtils; + this.linter = lintUtils.createLinter(); + + this.host = createVirtualCompilerHost(system, window.ts); + + this.linter.defineParser(PARSER_NAME, { + parseForESLint: (text, options?: ParserOptions) => { + return this.eslintParse(text, compilerOptions, options); + }, + }); + + this.ruleNames = Array.from(this.linter.getRules()).map(value => { + return { + name: value[0], + description: value[1]?.meta?.docs?.description, + }; + }); + } + + lint( + code: string, + parserOptions: ParserOptions, + rules: TSESLint.Linter.RulesRecord, + ): TSESLint.Linter.LintMessage[] { + return this.linter.verify(code, { + parser: PARSER_NAME, + parserOptions, + rules, + }); + } + + updateOptions(options: CompilerOptions = {}): void { + this.compilerOptions = options; + } + + eslintParse( + code: string, + compilerOptions: CompilerOptions, + eslintOptions: ParserOptions = {}, + ): TSESLint.Linter.ESLintParseResult { + const isJsx = eslintOptions?.ecmaFeatures?.jsx ?? false; + const fileName = isJsx ? '/demo.tsx' : '/demo.ts'; + + this.host.writeFile(fileName, code, false); + + const program = window.ts.createProgram( + [fileName], + compilerOptions, + this.host, + ); + const tsAst = program.getSourceFile(fileName)!; + + const { estree: ast, astMaps } = this.lintUtils.astConverter( + tsAst, + { ...extra, code, jsx: isJsx }, + true, + ); + + const scopeManager = this.lintUtils.analyze(ast, { + ecmaVersion: + eslintOptions.ecmaVersion === 'latest' + ? 1e8 + : eslintOptions.ecmaVersion, + globalReturn: eslintOptions.ecmaFeatures?.globalReturn ?? false, + sourceType: eslintOptions.sourceType ?? 'script', + }); + + this.storedAST = ast; + this.storedTsAST = tsAst; + this.storedScope = scopeManager as unknown as Record; + + const services: ParserServices = { + hasFullTypeInformation: true, + program, + esTreeNodeToTSNodeMap: astMaps.esTreeNodeToTSNodeMap, + tsNodeToESTreeNodeMap: astMaps.tsNodeToESTreeNodeMap, + }; + + return { + ast, + services, + scopeManager, + visitorKeys: this.lintUtils.visitorKeys, + }; + } +} diff --git a/packages/website-eslint/src/linter/config.js b/packages/website/src/components/linter/config.ts similarity index 81% rename from packages/website-eslint/src/linter/config.js rename to packages/website/src/components/linter/config.ts index 06e9ec28b3e7..f4a33eda679d 100644 --- a/packages/website-eslint/src/linter/config.js +++ b/packages/website/src/components/linter/config.ts @@ -1,4 +1,6 @@ -export const extra = { +import type { Extra } from '@typescript-eslint/typescript-estree/dist/parser-options'; + +export const extra: Extra = { code: '', comment: true, comments: [], diff --git a/packages/website/src/components/editor/lintCode.ts b/packages/website/src/components/linter/lintCode.ts similarity index 91% rename from packages/website/src/components/editor/lintCode.ts rename to packages/website/src/components/linter/lintCode.ts index b601a6cdcb36..33f6c216bf35 100644 --- a/packages/website/src/components/editor/lintCode.ts +++ b/packages/website/src/components/linter/lintCode.ts @@ -1,5 +1,6 @@ -import type { RulesRecord, WebLinter } from '@typescript-eslint/website-eslint'; +import type { TSESLint } from '@typescript-eslint/utils'; import type Monaco from 'monaco-editor'; +import type { WebLinter } from './WebLinter'; import { createURI, ensurePositiveInt } from './utils'; export interface LintCodeAction { @@ -15,7 +16,7 @@ export type LintCodeActionGroup = [string, LintCodeAction]; export function lintCode( linter: WebLinter, code: string, - rules: RulesRecord | undefined, + rules: TSESLint.Linter.RulesRecord | undefined, jsx?: boolean, sourceType?: 'module' | 'script', ): [Monaco.editor.IMarkerData[], string | undefined, LintCodeActionGroup[]] { @@ -26,11 +27,11 @@ export function lintCode( jsx: jsx ?? false, globalReturn: false, }, - ecmaVersion: 2020, + ecmaVersion: 'latest', project: ['./tsconfig.json'], sourceType: sourceType ?? 'module', }, - rules, + rules ?? {}, ); const markers: Monaco.editor.IMarkerData[] = []; let fatalMessage: string | undefined = undefined; diff --git a/packages/website/src/components/editor/utils.ts b/packages/website/src/components/linter/utils.ts similarity index 100% rename from packages/website/src/components/editor/utils.ts rename to packages/website/src/components/linter/utils.ts diff --git a/packages/website/src/components/types.ts b/packages/website/src/components/types.ts index bb774a436f49..51cd4e98b40c 100644 --- a/packages/website/src/components/types.ts +++ b/packages/website/src/components/types.ts @@ -1,13 +1,11 @@ -import type { - ParserOptions, - RulesRecord, -} from '@typescript-eslint/website-eslint'; +import type { TSESLint } from '@typescript-eslint/utils'; export type CompilerFlags = Record; -export type SourceType = ParserOptions['sourceType']; +export type SourceType = TSESLint.SourceType; -export type { RulesRecord } from '@typescript-eslint/website-eslint'; +export type RulesRecord = TSESLint.Linter.RulesRecord; +export type RuleEntry = TSESLint.Linter.RuleEntry; export interface RuleDetails { name: string; From f7d33b47e6b1e77741f5d68d6833d7a6aefc5f34 Mon Sep 17 00:00:00 2001 From: armano2 Date: Sun, 22 May 2022 17:42:00 +0200 Subject: [PATCH 2/5] chore(website): correct issues with ts ^4.7-beta --- packages/website/src/components/ast/serializer/serializerTS.ts | 1 + packages/website/src/components/linter/CompilerHost.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/website/src/components/ast/serializer/serializerTS.ts b/packages/website/src/components/ast/serializer/serializerTS.ts index b89d6e0e1a44..66cffe82b92a 100644 --- a/packages/website/src/components/ast/serializer/serializerTS.ts +++ b/packages/website/src/components/ast/serializer/serializerTS.ts @@ -20,6 +20,7 @@ export const propsToFilter = [ 'jsDocComment', 'lineMap', 'externalModuleIndicator', + 'setExternalModuleIndicator', 'bindDiagnostics', 'transformFlags', 'resolvedModules', diff --git a/packages/website/src/components/linter/CompilerHost.ts b/packages/website/src/components/linter/CompilerHost.ts index a96b1ac2e0a2..0421df7c48c1 100644 --- a/packages/website/src/components/linter/CompilerHost.ts +++ b/packages/website/src/components/linter/CompilerHost.ts @@ -47,7 +47,7 @@ export function createVirtualCompilerHost( getNewLine: () => sys.newLine, getSourceFile(fileName, languageVersionOrOptions): SourceFile | undefined { if (this.fileExists(fileName)) { - const file = this.readFile(fileName)!; + const file = this.readFile(fileName) ?? ''; return ts.createSourceFile( fileName, file, From 298347d8d3e0da83b9627714dbc7cab923063036 Mon Sep 17 00:00:00 2001 From: armano2 Date: Sun, 22 May 2022 18:07:03 +0200 Subject: [PATCH 3/5] chore(website): linting issues --- packages/website/src/components/linter/CompilerHost.ts | 2 -- packages/website/src/components/linter/config.ts | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/website/src/components/linter/CompilerHost.ts b/packages/website/src/components/linter/CompilerHost.ts index 0421df7c48c1..8a882644ba91 100644 --- a/packages/website/src/components/linter/CompilerHost.ts +++ b/packages/website/src/components/linter/CompilerHost.ts @@ -42,8 +42,6 @@ export function createVirtualCompilerHost( ...sys, getCanonicalFileName: (fileName: string) => fileName, getDefaultLibFileName: options => '/' + ts.getDefaultLibFileName(options), - getCurrentDirectory: () => '/', - getDirectories: () => [], getNewLine: () => sys.newLine, getSourceFile(fileName, languageVersionOrOptions): SourceFile | undefined { if (this.fileExists(fileName)) { diff --git a/packages/website/src/components/linter/config.ts b/packages/website/src/components/linter/config.ts index f4a33eda679d..cad38a3e28fa 100644 --- a/packages/website/src/components/linter/config.ts +++ b/packages/website/src/components/linter/config.ts @@ -12,6 +12,7 @@ export const extra: Extra = { filePath: '', jsx: false, loc: true, + // eslint-disable-next-line no-console log: console.log, preserveNodeMaps: true, projects: [], From 863b054491c299b6a28216cfd38d36b295ffc1a8 Mon Sep 17 00:00:00 2001 From: Armano Date: Wed, 25 May 2022 00:43:49 +0200 Subject: [PATCH 4/5] chore(website): reuse getScriptKind from typescript-estree --- packages/website-eslint/src/linter/linter.js | 1 + packages/website-eslint/types/index.d.ts | 2 + .../src/components/linter/CompilerHost.ts | 38 ++++--------------- .../src/components/linter/WebLinter.ts | 2 +- 4 files changed, 12 insertions(+), 31 deletions(-) diff --git a/packages/website-eslint/src/linter/linter.js b/packages/website-eslint/src/linter/linter.js index 34f2c1900db8..2eb7878391c7 100644 --- a/packages/website-eslint/src/linter/linter.js +++ b/packages/website-eslint/src/linter/linter.js @@ -13,3 +13,4 @@ export function createLinter() { export { analyze } from '@typescript-eslint/scope-manager/dist/analyze'; export { visitorKeys } from '@typescript-eslint/visitor-keys/dist/visitor-keys'; export { astConverter } from '@typescript-eslint/typescript-estree/dist/ast-converter'; +export { getScriptKind } from '@typescript-eslint/typescript-estree/dist/create-program/shared'; diff --git a/packages/website-eslint/types/index.d.ts b/packages/website-eslint/types/index.d.ts index d0ee27a6280c..447019621503 100644 --- a/packages/website-eslint/types/index.d.ts +++ b/packages/website-eslint/types/index.d.ts @@ -2,10 +2,12 @@ import type { TSESLint } from '@typescript-eslint/utils'; import { analyze } from '@typescript-eslint/scope-manager/dist/analyze'; import { astConverter } from '@typescript-eslint/typescript-estree/dist/ast-converter'; +import { getScriptKind } from '@typescript-eslint/typescript-estree/dist/create-program/shared'; export interface LintUtils { createLinter: () => TSESLint.Linter; analyze: typeof analyze; visitorKeys: TSESLint.SourceCode.VisitorKeys; astConverter: typeof astConverter; + getScriptKind: typeof getScriptKind; } diff --git a/packages/website/src/components/linter/CompilerHost.ts b/packages/website/src/components/linter/CompilerHost.ts index 8a882644ba91..89a56c37ddc2 100644 --- a/packages/website/src/components/linter/CompilerHost.ts +++ b/packages/website/src/components/linter/CompilerHost.ts @@ -1,30 +1,7 @@ -import type { ScriptKind, System, SourceFile, CompilerHost } from 'typescript'; +import type { System, SourceFile, CompilerHost } from 'typescript'; +import type { LintUtils } from '@typescript-eslint/website-eslint'; -function getScriptKind( - ts: typeof import('typescript'), - filePath: string, -): ScriptKind { - const extension = (/(\.[a-z]+)$/.exec(filePath)?.[0] ?? '').toLowerCase(); - - switch (extension) { - case '.ts': - return ts.ScriptKind.TS; - case '.tsx': - return ts.ScriptKind.TSX; - - case '.js': - return ts.ScriptKind.JS; - case '.jsx': - return ts.ScriptKind.JSX; - - case '.json': - return ts.ScriptKind.JSON; - - default: - // unknown extension, force typescript to ignore the file extension, and respect the user's setting - return ts.ScriptKind.TS; - } -} +import { extra } from './config'; /** * Creates an in-memory CompilerHost -which is essentially an extra wrapper to System @@ -36,22 +13,23 @@ function getScriptKind( */ export function createVirtualCompilerHost( sys: System, - ts: typeof import('typescript'), + lintUtils: LintUtils, ): CompilerHost { return { ...sys, getCanonicalFileName: (fileName: string) => fileName, - getDefaultLibFileName: options => '/' + ts.getDefaultLibFileName(options), + getDefaultLibFileName: options => + '/' + window.ts.getDefaultLibFileName(options), getNewLine: () => sys.newLine, getSourceFile(fileName, languageVersionOrOptions): SourceFile | undefined { if (this.fileExists(fileName)) { const file = this.readFile(fileName) ?? ''; - return ts.createSourceFile( + return window.ts.createSourceFile( fileName, file, languageVersionOrOptions, true, - getScriptKind(ts, fileName), + lintUtils.getScriptKind(extra, fileName), ); } return undefined; diff --git a/packages/website/src/components/linter/WebLinter.ts b/packages/website/src/components/linter/WebLinter.ts index bf8061285e86..d3c045eaf427 100644 --- a/packages/website/src/components/linter/WebLinter.ts +++ b/packages/website/src/components/linter/WebLinter.ts @@ -36,7 +36,7 @@ export class WebLinter { this.lintUtils = lintUtils; this.linter = lintUtils.createLinter(); - this.host = createVirtualCompilerHost(system, window.ts); + this.host = createVirtualCompilerHost(system, lintUtils); this.linter.defineParser(PARSER_NAME, { parseForESLint: (text, options?: ParserOptions) => { From a46048a0efdee1c51608690af755723833258d14 Mon Sep 17 00:00:00 2001 From: Armano Date: Wed, 25 May 2022 01:28:31 +0200 Subject: [PATCH 5/5] chore(website): update link to getScriptKind base on changes from #5027 --- packages/website-eslint/src/linter/linter.js | 2 +- packages/website-eslint/types/index.d.ts | 2 +- packages/website/src/components/linter/CompilerHost.ts | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/website-eslint/src/linter/linter.js b/packages/website-eslint/src/linter/linter.js index 2eb7878391c7..fede1540f98c 100644 --- a/packages/website-eslint/src/linter/linter.js +++ b/packages/website-eslint/src/linter/linter.js @@ -13,4 +13,4 @@ export function createLinter() { export { analyze } from '@typescript-eslint/scope-manager/dist/analyze'; export { visitorKeys } from '@typescript-eslint/visitor-keys/dist/visitor-keys'; export { astConverter } from '@typescript-eslint/typescript-estree/dist/ast-converter'; -export { getScriptKind } from '@typescript-eslint/typescript-estree/dist/create-program/shared'; +export { getScriptKind } from '@typescript-eslint/typescript-estree/dist/create-program/getScriptKind'; diff --git a/packages/website-eslint/types/index.d.ts b/packages/website-eslint/types/index.d.ts index 447019621503..7673f6d10a27 100644 --- a/packages/website-eslint/types/index.d.ts +++ b/packages/website-eslint/types/index.d.ts @@ -2,7 +2,7 @@ import type { TSESLint } from '@typescript-eslint/utils'; import { analyze } from '@typescript-eslint/scope-manager/dist/analyze'; import { astConverter } from '@typescript-eslint/typescript-estree/dist/ast-converter'; -import { getScriptKind } from '@typescript-eslint/typescript-estree/dist/create-program/shared'; +import { getScriptKind } from '@typescript-eslint/typescript-estree/dist/create-program/getScriptKind'; export interface LintUtils { createLinter: () => TSESLint.Linter; diff --git a/packages/website/src/components/linter/CompilerHost.ts b/packages/website/src/components/linter/CompilerHost.ts index 89a56c37ddc2..ddf360a2a25d 100644 --- a/packages/website/src/components/linter/CompilerHost.ts +++ b/packages/website/src/components/linter/CompilerHost.ts @@ -1,8 +1,6 @@ import type { System, SourceFile, CompilerHost } from 'typescript'; import type { LintUtils } from '@typescript-eslint/website-eslint'; -import { extra } from './config'; - /** * Creates an in-memory CompilerHost -which is essentially an extra wrapper to System * which works with TypeScript objects - returns both a compiler host, and a way to add new SourceFile @@ -29,7 +27,7 @@ export function createVirtualCompilerHost( file, languageVersionOrOptions, true, - lintUtils.getScriptKind(extra, fileName), + lintUtils.getScriptKind(fileName, false), ); } return undefined; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy