From fda2c704763b2aa681e6285cf7e0a68c7ed99d81 Mon Sep 17 00:00:00 2001 From: Armano Date: Fri, 14 Apr 2023 15:20:49 +0200 Subject: [PATCH 1/6] chore(website): validate rule options in editor --- .../src/components/editor/LoadedEditor.tsx | 9 ++- .../website/src/components/lib/jsonSchema.ts | 61 +++++++++++++++---- .../src/components/linter/createLinter.ts | 13 +++- 3 files changed, 69 insertions(+), 14 deletions(-) diff --git a/packages/website/src/components/editor/LoadedEditor.tsx b/packages/website/src/components/editor/LoadedEditor.tsx index 994561e7c318..9c728648a17f 100644 --- a/packages/website/src/components/editor/LoadedEditor.tsx +++ b/packages/website/src/components/editor/LoadedEditor.tsx @@ -147,16 +147,23 @@ export const LoadedEditor: React.FC = ({ }, [webLinter, onEsASTChange, onScopeChange, onTsASTChange]); useEffect(() => { + const createRuleUri = (name: string): string => + monaco.Uri.parse(`/rules/${name.replace('@', '')}.json`).toString(); + // configure the JSON language support with schemas and schema associations monaco.languages.json.jsonDefaults.setDiagnosticsOptions({ validate: true, enableSchemaRequest: false, allowComments: true, schemas: [ + ...Array.from(webLinter.rules.values()).map(rule => ({ + uri: createRuleUri(rule.name), + schema: rule.schema, + })), { uri: monaco.Uri.file('eslint-schema.json').toString(), // id of the first schema fileMatch: ['/.eslintrc'], // associate with our model - schema: getEslintJsonSchema(webLinter), + schema: getEslintJsonSchema(webLinter, createRuleUri), }, { uri: monaco.Uri.file('ts-schema.json').toString(), // id of the first schema diff --git a/packages/website/src/components/lib/jsonSchema.ts b/packages/website/src/components/lib/jsonSchema.ts index b43b6ad72cb9..d54f92900dfb 100644 --- a/packages/website/src/components/lib/jsonSchema.ts +++ b/packages/website/src/components/lib/jsonSchema.ts @@ -3,31 +3,70 @@ import type * as ts from 'typescript'; import type { CreateLinter } from '../linter/createLinter'; +const defaultRuleSchema: JSONSchema4 = { + type: ['string', 'number'], + enum: ['off', 'warn', 'error', 0, 1, 2], +}; + /** * Get the JSON schema for the eslint config * Currently we only support the rules and extends */ -export function getEslintJsonSchema(linter: CreateLinter): JSONSchema4 { +export function getEslintJsonSchema( + linter: CreateLinter, + createRef: (name: string) => string, +): JSONSchema4 { const properties: Record = {}; for (const [, item] of linter.rules) { + const ruleRefName = createRef(item.name); + const schemaItemOptions: JSONSchema4[] = [defaultRuleSchema]; + let additionalItems: JSONSchema4 | false = false; + + // if the rule has options, add them to the schema + // if you encounter issues with rule schema validation you can check the schema by using the following code in the console: + // monaco.languages.json.jsonDefaults.diagnosticsOptions.schemas.find(item => item.uri === 'file:///rules/typescript-eslint/consistent-type-imports.json') + // monaco.languages.json.jsonDefaults.diagnosticsOptions.schemas.filter(item => item.schema.type === 'array') + if (Array.isArray(item.schema)) { + // example @typescript-eslint/consistent-type-imports + for (let index = 0; index < item.schema?.length; ++index) { + schemaItemOptions.push({ + $ref: `${ruleRefName}#/${index}`, + }); + } + } else { + const name = item.schema.items ? 'items' : 'prefixItems'; + const items = item.schema[name] as + | JSONSchema4[] + | JSONSchema4 + | undefined; + if (items) { + if (Array.isArray(items)) { + // example array-element-newline + for (let index = 0; index < items.length; ++index) { + schemaItemOptions.push({ + $ref: `${ruleRefName}#/${name}/${index}`, + }); + } + } else { + // example @typescript-eslint/naming-convention + additionalItems = { + $ref: `${ruleRefName}#/${name}`, + }; + } + } + } + properties[item.name] = { description: `${item.description}\n ${item.url}`, title: item.name.startsWith('@typescript') ? 'Rules' : 'Core rules', default: 'off', oneOf: [ - { - type: ['string', 'number'], - enum: ['off', 'warn', 'error', 0, 1, 2], - }, + defaultRuleSchema, { type: 'array', - items: [ - { - type: ['string', 'number'], - enum: ['off', 'warn', 'error', 0, 1, 2], - }, - ], + items: schemaItemOptions, + additionalItems: additionalItems, }, ], }; diff --git a/packages/website/src/components/linter/createLinter.ts b/packages/website/src/components/linter/createLinter.ts index 813fd10eaf24..30ba20fe1a43 100644 --- a/packages/website/src/components/linter/createLinter.ts +++ b/packages/website/src/components/linter/createLinter.ts @@ -1,5 +1,5 @@ import type * as tsvfs from '@site/src/vendor/typescript-vfs'; -import type { TSESLint } from '@typescript-eslint/utils'; +import type { JSONSchema, TSESLint } from '@typescript-eslint/utils'; import type * as ts from 'typescript'; import { createCompilerOptions } from '../lib/createCompilerOptions'; @@ -15,7 +15,15 @@ import type { } from './types'; export interface CreateLinter { - rules: Map; + rules: Map< + string, + { + name: string; + description?: string; + url?: string; + schema: JSONSchema.JSONSchema4; + } + >; configs: string[]; triggerFix(filename: string): TSESLint.Linter.FixReport | undefined; triggerLint(filename: string): void; @@ -56,6 +64,7 @@ export function createLinter( name: name, description: item.meta?.docs?.description, url: item.meta?.docs?.url, + schema: item.meta?.schema ?? [], }); }); From 01bd2a861a0a9de0b747a991d5d0171870b12093 Mon Sep 17 00:00:00 2001 From: Armano Date: Fri, 14 Apr 2023 17:54:57 +0200 Subject: [PATCH 2/6] chore(website): add support for oneOf and anyOf --- .../src/components/editor/LoadedEditor.tsx | 3 +- .../editor/createProvideCodeActions.ts | 4 +- .../website/src/components/lib/jsonSchema.ts | 124 +++++++++++------- 3 files changed, 82 insertions(+), 49 deletions(-) diff --git a/packages/website/src/components/editor/LoadedEditor.tsx b/packages/website/src/components/editor/LoadedEditor.tsx index 9c728648a17f..4d852ab19698 100644 --- a/packages/website/src/components/editor/LoadedEditor.tsx +++ b/packages/website/src/components/editor/LoadedEditor.tsx @@ -8,6 +8,7 @@ import { createCompilerOptions } from '../lib/createCompilerOptions'; import { debounce } from '../lib/debounce'; import { getEslintJsonSchema, + getRuleJsonSchemaWithErrorLevel, getTypescriptJsonSchema, } from '../lib/jsonSchema'; import { parseTSConfig, tryParseEslintModule } from '../lib/parseConfig'; @@ -158,7 +159,7 @@ export const LoadedEditor: React.FC = ({ schemas: [ ...Array.from(webLinter.rules.values()).map(rule => ({ uri: createRuleUri(rule.name), - schema: rule.schema, + schema: getRuleJsonSchemaWithErrorLevel(rule.schema), })), { uri: monaco.Uri.file('eslint-schema.json').toString(), // id of the first schema diff --git a/packages/website/src/components/editor/createProvideCodeActions.ts b/packages/website/src/components/editor/createProvideCodeActions.ts index 7f26faec7044..a5eae9f849a9 100644 --- a/packages/website/src/components/editor/createProvideCodeActions.ts +++ b/packages/website/src/components/editor/createProvideCodeActions.ts @@ -34,9 +34,9 @@ export function createProvideCodeActions( edits: [ { resource: model.uri, - // @ts-expect-error monaco for ts >= 4.8 + // monaco for ts >= 4.8 textEdit: editOperation, - // monaco for ts < 4.8 + // @ts-expect-error monaco for ts < 4.8 edit: editOperation, }, ], diff --git a/packages/website/src/components/lib/jsonSchema.ts b/packages/website/src/components/lib/jsonSchema.ts index d54f92900dfb..96d62666cf2b 100644 --- a/packages/website/src/components/lib/jsonSchema.ts +++ b/packages/website/src/components/lib/jsonSchema.ts @@ -8,6 +8,83 @@ const defaultRuleSchema: JSONSchema4 = { enum: ['off', 'warn', 'error', 0, 1, 2], }; +/** + * Add the error level to the rule schema items + * + * if you encounter issues with rule schema validation you can check the schema by using the following code in the console: + * monaco.languages.json.jsonDefaults.diagnosticsOptions.schemas.find(item => item.uri.includes('typescript-eslint/consistent-type-imports')) + * monaco.languages.json.jsonDefaults.diagnosticsOptions.schemas.find(item => item.uri.includes('no-unused-labels')) + * monaco.languages.json.jsonDefaults.diagnosticsOptions.schemas.filter(item => item.schema.type === 'array') + */ +export function getRuleJsonSchemaWithErrorLevel( + ruleSchema: JSONSchema4 | JSONSchema4[], +): JSONSchema4 { + if (Array.isArray(ruleSchema)) { + return { + type: 'array', + items: [defaultRuleSchema, ...ruleSchema], + additionalItems: false, + }; + } + // TODO: delete this once we update schemas + // example: ban-ts-comments + if (Array.isArray(ruleSchema.prefixItems)) { + const { prefixItems, ...rest } = ruleSchema; + return { + ...rest, + items: [defaultRuleSchema, ...(prefixItems as JSONSchema4[])], + maxItems: ruleSchema.maxItems ? ruleSchema.maxItems + 1 : undefined, + minItems: ruleSchema.minItems ? ruleSchema.minItems + 1 : 1, + additionalItems: false, + }; + } + // example: @typescript-eslint/explicit-member-accessibility + if (Array.isArray(ruleSchema.items)) { + return { + ...ruleSchema, + items: [defaultRuleSchema, ...ruleSchema.items], + maxItems: ruleSchema.maxItems ? ruleSchema.maxItems + 1 : undefined, + minItems: ruleSchema.minItems ? ruleSchema.minItems + 1 : 1, + additionalItems: false, + }; + } + if (typeof ruleSchema.items === 'object' && ruleSchema.items) { + // this is a workaround for @typescript-eslint/naming-convention rule + if (ruleSchema.items.oneOf) { + return { + ...ruleSchema, + items: [defaultRuleSchema], + additionalItems: ruleSchema.items, + }; + } + // example: @typescript-eslint/padding-line-between-statements + return { + ...ruleSchema, + items: [defaultRuleSchema, ruleSchema.items], + additionalItems: false, + }; + } + // example eqeqeq + if (Array.isArray(ruleSchema.anyOf)) { + return { + ...ruleSchema, + anyOf: ruleSchema.anyOf.map(item => + getRuleJsonSchemaWithErrorLevel(item), + ), + }; + } + // example logical-assignment-operators + if (Array.isArray(ruleSchema.oneOf)) { + return { + ...ruleSchema, + oneOf: ruleSchema.oneOf.map(item => + getRuleJsonSchemaWithErrorLevel(item), + ), + }; + } + return ruleSchema; +} + /** * Get the JSON schema for the eslint config * Currently we only support the rules and extends @@ -19,56 +96,11 @@ export function getEslintJsonSchema( const properties: Record = {}; for (const [, item] of linter.rules) { - const ruleRefName = createRef(item.name); - const schemaItemOptions: JSONSchema4[] = [defaultRuleSchema]; - let additionalItems: JSONSchema4 | false = false; - - // if the rule has options, add them to the schema - // if you encounter issues with rule schema validation you can check the schema by using the following code in the console: - // monaco.languages.json.jsonDefaults.diagnosticsOptions.schemas.find(item => item.uri === 'file:///rules/typescript-eslint/consistent-type-imports.json') - // monaco.languages.json.jsonDefaults.diagnosticsOptions.schemas.filter(item => item.schema.type === 'array') - if (Array.isArray(item.schema)) { - // example @typescript-eslint/consistent-type-imports - for (let index = 0; index < item.schema?.length; ++index) { - schemaItemOptions.push({ - $ref: `${ruleRefName}#/${index}`, - }); - } - } else { - const name = item.schema.items ? 'items' : 'prefixItems'; - const items = item.schema[name] as - | JSONSchema4[] - | JSONSchema4 - | undefined; - if (items) { - if (Array.isArray(items)) { - // example array-element-newline - for (let index = 0; index < items.length; ++index) { - schemaItemOptions.push({ - $ref: `${ruleRefName}#/${name}/${index}`, - }); - } - } else { - // example @typescript-eslint/naming-convention - additionalItems = { - $ref: `${ruleRefName}#/${name}`, - }; - } - } - } - properties[item.name] = { description: `${item.description}\n ${item.url}`, title: item.name.startsWith('@typescript') ? 'Rules' : 'Core rules', default: 'off', - oneOf: [ - defaultRuleSchema, - { - type: 'array', - items: schemaItemOptions, - additionalItems: additionalItems, - }, - ], + oneOf: [defaultRuleSchema, { $ref: createRef(item.name) }], }; } From b830f93baef4613cb8d543758abf58a9bebe342f Mon Sep 17 00:00:00 2001 From: Armano Date: Fri, 14 Apr 2023 19:45:01 +0200 Subject: [PATCH 3/6] chore: add logging for unsupported schemas / invalid, and add fallback --- .../src/components/editor/LoadedEditor.tsx | 2 +- .../website/src/components/lib/jsonSchema.ts | 22 +++++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/website/src/components/editor/LoadedEditor.tsx b/packages/website/src/components/editor/LoadedEditor.tsx index 4d852ab19698..f8c88d3d5f0a 100644 --- a/packages/website/src/components/editor/LoadedEditor.tsx +++ b/packages/website/src/components/editor/LoadedEditor.tsx @@ -159,7 +159,7 @@ export const LoadedEditor: React.FC = ({ schemas: [ ...Array.from(webLinter.rules.values()).map(rule => ({ uri: createRuleUri(rule.name), - schema: getRuleJsonSchemaWithErrorLevel(rule.schema), + schema: getRuleJsonSchemaWithErrorLevel(rule.name, rule.schema), })), { uri: monaco.Uri.file('eslint-schema.json').toString(), // id of the first schema diff --git a/packages/website/src/components/lib/jsonSchema.ts b/packages/website/src/components/lib/jsonSchema.ts index 96d62666cf2b..bab51924e991 100644 --- a/packages/website/src/components/lib/jsonSchema.ts +++ b/packages/website/src/components/lib/jsonSchema.ts @@ -17,17 +17,19 @@ const defaultRuleSchema: JSONSchema4 = { * monaco.languages.json.jsonDefaults.diagnosticsOptions.schemas.filter(item => item.schema.type === 'array') */ export function getRuleJsonSchemaWithErrorLevel( + name: string, ruleSchema: JSONSchema4 | JSONSchema4[], ): JSONSchema4 { if (Array.isArray(ruleSchema)) { return { type: 'array', items: [defaultRuleSchema, ...ruleSchema], + minItems: 1, additionalItems: false, }; } // TODO: delete this once we update schemas - // example: ban-ts-comments + // example: ban-ts-comment if (Array.isArray(ruleSchema.prefixItems)) { const { prefixItems, ...rest } = ruleSchema; return { @@ -38,7 +40,7 @@ export function getRuleJsonSchemaWithErrorLevel( additionalItems: false, }; } - // example: @typescript-eslint/explicit-member-accessibility + // example: explicit-member-accessibility if (Array.isArray(ruleSchema.items)) { return { ...ruleSchema, @@ -49,7 +51,7 @@ export function getRuleJsonSchemaWithErrorLevel( }; } if (typeof ruleSchema.items === 'object' && ruleSchema.items) { - // this is a workaround for @typescript-eslint/naming-convention rule + // this is a workaround for naming-convention rule if (ruleSchema.items.oneOf) { return { ...ruleSchema, @@ -57,7 +59,7 @@ export function getRuleJsonSchemaWithErrorLevel( additionalItems: ruleSchema.items, }; } - // example: @typescript-eslint/padding-line-between-statements + // example: padding-line-between-statements return { ...ruleSchema, items: [defaultRuleSchema, ruleSchema.items], @@ -69,7 +71,7 @@ export function getRuleJsonSchemaWithErrorLevel( return { ...ruleSchema, anyOf: ruleSchema.anyOf.map(item => - getRuleJsonSchemaWithErrorLevel(item), + getRuleJsonSchemaWithErrorLevel(name, item), ), }; } @@ -78,11 +80,17 @@ export function getRuleJsonSchemaWithErrorLevel( return { ...ruleSchema, oneOf: ruleSchema.oneOf.map(item => - getRuleJsonSchemaWithErrorLevel(item), + getRuleJsonSchemaWithErrorLevel(name, item), ), }; } - return ruleSchema; + console.log('unsupported rule schema', name, ruleSchema); + return { + type: 'array', + items: [defaultRuleSchema], + minItems: 1, + additionalItems: false, + }; } /** From 7c699d373879f209364fa08ee2fce68b153ad9c9 Mon Sep 17 00:00:00 2001 From: Armano Date: Fri, 14 Apr 2023 20:12:32 +0200 Subject: [PATCH 4/6] chore: do not show messages for empty objects --- packages/website/src/components/lib/jsonSchema.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/website/src/components/lib/jsonSchema.ts b/packages/website/src/components/lib/jsonSchema.ts index bab51924e991..7dc094a51fda 100644 --- a/packages/website/src/components/lib/jsonSchema.ts +++ b/packages/website/src/components/lib/jsonSchema.ts @@ -84,7 +84,9 @@ export function getRuleJsonSchemaWithErrorLevel( ), }; } - console.log('unsupported rule schema', name, ruleSchema); + if (typeof ruleSchema !== 'object' || Object.keys(ruleSchema).length) { + console.error('unsupported rule schema', name, ruleSchema); + } return { type: 'array', items: [defaultRuleSchema], From 664cc78f3c0b9ef39b39f0556b14befebfbe5551 Mon Sep 17 00:00:00 2001 From: Armano Date: Fri, 14 Apr 2023 23:09:23 +0200 Subject: [PATCH 5/6] fix: remove invalid condition --- packages/website/src/components/lib/jsonSchema.ts | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/packages/website/src/components/lib/jsonSchema.ts b/packages/website/src/components/lib/jsonSchema.ts index 7dc094a51fda..db11292c0847 100644 --- a/packages/website/src/components/lib/jsonSchema.ts +++ b/packages/website/src/components/lib/jsonSchema.ts @@ -51,19 +51,11 @@ export function getRuleJsonSchemaWithErrorLevel( }; } if (typeof ruleSchema.items === 'object' && ruleSchema.items) { - // this is a workaround for naming-convention rule - if (ruleSchema.items.oneOf) { - return { - ...ruleSchema, - items: [defaultRuleSchema], - additionalItems: ruleSchema.items, - }; - } - // example: padding-line-between-statements + // example: naming-convention rule return { ...ruleSchema, - items: [defaultRuleSchema, ruleSchema.items], - additionalItems: false, + items: [defaultRuleSchema], + additionalItems: ruleSchema.items, }; } // example eqeqeq From 3a5b13a531e9e34725559d806237e8aeadff666b Mon Sep 17 00:00:00 2001 From: Armano Date: Sun, 16 Apr 2023 19:50:33 +0200 Subject: [PATCH 6/6] fix: filter out invalid error codes --- packages/website/src/components/editor/useSandboxServices.ts | 1 + packages/website/src/components/linter/utils.ts | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/website/src/components/editor/useSandboxServices.ts b/packages/website/src/components/editor/useSandboxServices.ts index 21a7f51ea471..027e12d918cf 100644 --- a/packages/website/src/components/editor/useSandboxServices.ts +++ b/packages/website/src/components/editor/useSandboxServices.ts @@ -125,6 +125,7 @@ export const useSandboxServices = ( }; // colorMode and jsx can't be reactive here because we don't want to force a recreation // updating of colorMode and jsx is handled in LoadedEditor + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return services; diff --git a/packages/website/src/components/linter/utils.ts b/packages/website/src/components/linter/utils.ts index e2a4b3ed1c72..474e28cca8fb 100644 --- a/packages/website/src/components/linter/utils.ts +++ b/packages/website/src/components/linter/utils.ts @@ -102,8 +102,9 @@ export function parseMarkers( result[group].items.push({ message: - (marker.owner !== 'eslint' && code ? `${code.value}: ` : '') + - marker.message, + (marker.owner !== 'eslint' && marker.owner !== 'json' && code.value + ? `${code.value}: ` + : '') + marker.message, location: `${marker.startLineNumber}:${marker.startColumn} - ${marker.endLineNumber}:${marker.endColumn}`, severity: marker.severity, fixer: fixers.find(item => item.isPreferred), 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