Skip to content

Commit

Permalink
chore(website): validate rule options in editor (#6907)
Browse files Browse the repository at this point in the history
  • Loading branch information
armano2 committed Apr 16, 2023
1 parent 277fdb5 commit 5ee6180
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 21 deletions.
10 changes: 9 additions & 1 deletion packages/website/src/components/editor/LoadedEditor.tsx
Expand Up @@ -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';
Expand Down Expand Up @@ -147,16 +148,23 @@ export const LoadedEditor: React.FC<LoadedEditorProps> = ({
}, [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: getRuleJsonSchemaWithErrorLevel(rule.name, 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
Expand Down
Expand Up @@ -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;
Expand Down
105 changes: 89 additions & 16 deletions packages/website/src/components/lib/jsonSchema.ts
Expand Up @@ -3,33 +3,106 @@ 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],
};

/**
* 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(
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-comment
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: 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) {
// example: naming-convention rule
return {
...ruleSchema,
items: [defaultRuleSchema],
additionalItems: ruleSchema.items,
};
}
// example eqeqeq
if (Array.isArray(ruleSchema.anyOf)) {
return {
...ruleSchema,
anyOf: ruleSchema.anyOf.map(item =>
getRuleJsonSchemaWithErrorLevel(name, item),
),
};
}
// example logical-assignment-operators
if (Array.isArray(ruleSchema.oneOf)) {
return {
...ruleSchema,
oneOf: ruleSchema.oneOf.map(item =>
getRuleJsonSchemaWithErrorLevel(name, item),
),
};
}
if (typeof ruleSchema !== 'object' || Object.keys(ruleSchema).length) {
console.error('unsupported rule schema', name, ruleSchema);
}
return {
type: 'array',
items: [defaultRuleSchema],
minItems: 1,
additionalItems: false,
};
}

/**
* 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<string, JSONSchema4> = {};

for (const [, item] of linter.rules) {
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],
},
{
type: 'array',
items: [
{
type: ['string', 'number'],
enum: ['off', 'warn', 'error', 0, 1, 2],
},
],
},
],
oneOf: [defaultRuleSchema, { $ref: createRef(item.name) }],
};
}

Expand Down
13 changes: 11 additions & 2 deletions 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';
Expand All @@ -15,7 +15,15 @@ import type {
} from './types';

export interface CreateLinter {
rules: Map<string, { name: string; description?: string; url?: string }>;
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;
Expand Down Expand Up @@ -56,6 +64,7 @@ export function createLinter(
name: name,
description: item.meta?.docs?.description,
url: item.meta?.docs?.url,
schema: item.meta?.schema ?? [],
});
});

Expand Down
5 changes: 3 additions & 2 deletions packages/website/src/components/linter/utils.ts
Expand Up @@ -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),
Expand Down

0 comments on commit 5ee6180

Please sign in to comment.