diff --git a/Cargo.lock b/Cargo.lock index afdcf852906ed..e5e6964e52edb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2986,6 +2986,7 @@ dependencies = [ "bpaf", "ckb_schemars", "handlebars", + "insta", "oxc_cli", "oxc_linter", "pico-args", diff --git a/tasks/website/Cargo.toml b/tasks/website/Cargo.toml index 151a5e9250a9a..4bdf3a2b5b820 100644 --- a/tasks/website/Cargo.toml +++ b/tasks/website/Cargo.toml @@ -12,6 +12,9 @@ workspace = true name = "website" test = false +[lib] +doctest = false + [dependencies] oxc_linter = { workspace = true } oxc_cli = { path = "../../crates/oxc_cli" } @@ -22,3 +25,6 @@ serde_json = { workspace = true } schemars = { workspace = true } handlebars = { workspace = true } serde = { workspace = true } + +[dev-dependencies] +insta = { workspace = true } diff --git a/tasks/website/src/linter/cli.rs b/tasks/website/src/linter/cli.rs index 7ea132398c507..50ac5bc0eb0b4 100644 --- a/tasks/website/src/linter/cli.rs +++ b/tasks/website/src/linter/cli.rs @@ -1,8 +1,20 @@ use bpaf::Parser; use oxc_cli::lint_options; +#[test] +fn test_cli() { + let snapshot = generate_cli(); + insta::with_settings!({ prepend_module_to_snapshot => false }, { + insta::assert_snapshot!(snapshot); + }); +} + // -pub fn generate_cli() { +pub fn print_cli() { + println!("{}", generate_cli()); +} + +fn generate_cli() -> String { let markdown = lint_options().to_options().render_markdown("oxlint"); // Remove the extra header let markdown = markdown.trim_start_matches("# oxlint\n"); @@ -32,7 +44,7 @@ pub fn generate_cli() { }) .collect::>() .join("\n"); - println!( + format!( " @@ -40,5 +52,5 @@ pub fn generate_cli() { " - ); + ) } diff --git a/tasks/website/src/linter/json_schema.rs b/tasks/website/src/linter/json_schema.rs index f88fa0bb2c50e..52ab71ea692b0 100644 --- a/tasks/website/src/linter/json_schema.rs +++ b/tasks/website/src/linter/json_schema.rs @@ -7,15 +7,38 @@ use serde::Serialize; use oxc_linter::ESLintConfig; -pub fn generate_schema_json() { +#[test] +fn test_schema_json() { + let snapshot = generate_schema_json(); + insta::with_settings!({ prepend_module_to_snapshot => false }, { + insta::assert_snapshot!(snapshot); + }); +} + +pub fn print_schema_json() { + println!("{}", generate_schema_json()); +} + +fn generate_schema_json() -> String { let schema = schema_for!(ESLintConfig); - println!("{}", serde_json::to_string_pretty(&schema).unwrap()); + serde_json::to_string_pretty(&schema).unwrap() +} + +#[test] +fn test_schema_markdown() { + let snapshot = generate_schema_markdown(); + insta::with_settings!({ prepend_module_to_snapshot => false }, { + insta::assert_snapshot!(snapshot); + }); +} + +pub fn print_schema_markdown() { + println!("{}", generate_schema_markdown()); } -pub fn generate_schema_markdown() { +fn generate_schema_markdown() -> String { let root_schema = schema_for!(ESLintConfig); - let rendered = Renderer::new(root_schema).render(); - println!("{rendered}"); + Renderer::new(root_schema).render() } const ROOT: &str = " diff --git a/tasks/website/src/linter/mod.rs b/tasks/website/src/linter/mod.rs index b247669662786..7b9c04fbfe651 100644 --- a/tasks/website/src/linter/mod.rs +++ b/tasks/website/src/linter/mod.rs @@ -3,7 +3,7 @@ mod json_schema; mod rules; pub use self::{ - cli::generate_cli, - json_schema::{generate_schema_json, generate_schema_markdown}, - rules::generate_rules, + cli::print_cli, + json_schema::{print_schema_json, print_schema_markdown}, + rules::print_rules, }; diff --git a/tasks/website/src/linter/rules.rs b/tasks/website/src/linter/rules.rs index abaa561eb5711..08e194ead4e3a 100644 --- a/tasks/website/src/linter/rules.rs +++ b/tasks/website/src/linter/rules.rs @@ -2,7 +2,7 @@ use oxc_linter::table::RuleTable; // `cargo run -p website linter-rules > /path/to/oxc/oxc-project.github.io/src/docs/guide/usage/linter/generated-rules.md` // -pub fn generate_rules() { +pub fn print_rules() { let table = RuleTable::new(); let total = table.total; diff --git a/tasks/website/src/linter/snapshots/cli.snap b/tasks/website/src/linter/snapshots/cli.snap new file mode 100644 index 0000000000000..1961604ba90d8 --- /dev/null +++ b/tasks/website/src/linter/snapshots/cli.snap @@ -0,0 +1,129 @@ +--- +source: tasks/website/src/linter/cli.rs +expression: snapshot +--- + + + +## Usage + **`oxlint`** \[**`-c`**=_`<./eslintrc.json>`_\] \[**`--fix`**\] \[_`PATH`_\]... + +## Basic Configuration +- **`-c`**, **`--config`**=_`<./eslintrc.json>`_ — + ESLint configuration file (experimental) + + * only `.json` extension is supported +- **` --tsconfig`**=_`<./tsconfig.json>`_ — + TypeScript `tsconfig.json` path for reading path alias and project references for import plugin + + + +## Allowing / Denying Multiple Lints +Accumulate rules and categories from left to right on the command-line. + + + For example `-D correctness -A no-debugger` or `-A all -D no-debugger`. + The categories are: + * `correctness` - code that is outright wrong or useless (default) + * `suspicious` - code that is most likely wrong or useless + * `pedantic` - lints which are rather strict or have occasional false positives + * `style` - code that should be written in a more idiomatic way + * `nursery` - new lints that are still under development + * `restriction` - lints which prevent the use of language and library features + * `all` - all the categories listed above except nursery + +Arguments: + +- **`-A`**, **`--allow`**=_`NAME`_ — + Allow the rule or category (suppress the lint) +- **`-W`**, **`--warn`**=_`NAME`_ — + Deny the rule or category (emit a warning) +- **`-D`**, **`--deny`**=_`NAME`_ — + Deny the rule or category (emit an error) + + + +## Enable Plugins +- **` --disable-react-plugin`** — + Disable react plugin, which is turned on by default +- **` --disable-unicorn-plugin`** — + Disable unicorn plugin, which is turned on by default +- **` --disable-oxc-plugin`** — + Disable oxc unique rules, which is turned on by default +- **` --disable-typescript-plugin`** — + Disable TypeScript plugin, which is turned on by default +- **` --import-plugin`** — + Enable the experimental import plugin and detect ESM problems. It is recommended to use along side with the `--tsconfig` option. +- **` --jsdoc-plugin`** — + Enable the experimental jsdoc plugin and detect JSDoc problems +- **` --jest-plugin`** — + Enable the Jest plugin and detect test problems +- **` --jsx-a11y-plugin`** — + Enable the JSX-a11y plugin and detect accessibility problems +- **` --nextjs-plugin`** — + Enable the Next.js plugin and detect Next.js problems +- **` --react-perf-plugin`** — + Enable the React performance plugin and detect rendering performance problems + + + +## Fix Problems +- **` --fix`** — + Fix as many issues as possible. Only unfixed issues are reported in the output + + + +## Ignore Files +- **` --ignore-path`**=_`PATH`_ — + Specify the file to use as your .eslintignore +- **` --ignore-pattern`**=_`PAT`_ — + Specify patterns of files to ignore (in addition to those in .eslintignore) + + The supported syntax is the same as for .eslintignore and .gitignore files You should quote your patterns in order to avoid shell interpretation of glob patterns +- **` --no-ignore`** — + Disables excluding of files from .eslintignore files, **`--ignore-path`** flags and **`--ignore-pattern`** flags +- **` --symlinks`** — + Follow symbolic links. Oxlint ignores symbolic links by default. + + + +## Handle Warnings +- **` --quiet`** — + Disable reporting on warnings, only errors are reported +- **` --deny-warnings`** — + Ensure warnings produce a non-zero exit code +- **` --max-warnings`**=_`INT`_ — + Specify a warning threshold, which can be used to force exit with an error status if there are too many warning-level rule violations in your project + + + +## Output +- **`-f`**, **`--format`**=_`ARG`_ — + Use a specific output format (default, json, unix, checkstyle, github) + + + +## Miscellaneous +- **` --silent`** — + Do not display any diagnostics +- **` --threads`**=_`INT`_ — + Number of threads to use. Set to 1 for using only 1 CPU core + + + +## Available positional items: +- _`PATH`_ — + Single file, single path or list of paths + + + +## Available options: +- **` --rules`** — + list all the rules that are currently registered +- **`-h`**, **`--help`** — + Prints help information + + + + + diff --git a/tasks/website/src/linter/snapshots/schema_json.snap b/tasks/website/src/linter/snapshots/schema_json.snap new file mode 100644 index 0000000000000..5ecfb67748ea1 --- /dev/null +++ b/tasks/website/src/linter/snapshots/schema_json.snap @@ -0,0 +1,271 @@ +--- +source: tasks/website/src/linter/json_schema.rs +expression: snapshot +--- +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ESLintConfig", + "description": "Oxlint Configuration File\n\nThis configuration is aligned with ESLint v8's configuration schema (`eslintrc.json`).\n\nUsage: `oxlint -c oxlintrc.json`\n\n::: danger NOTE\n\nOnly the `.json` format is supported.\n\n:::\n\nExample\n\n```json\n // oxlintrc.json\n {\n // Comments are supported.\n \"env\": {\n \"browser\": true\n },\n \"globals\": {\n \"foo\": \"readonly\",\n },\n \"settings\": {\n },\n \"rules\": {\n \"eqeqeq\": \"warn\",\n },\n }\n```", + "type": "object", + "properties": { + "env": { + "$ref": "#/definitions/ESLintEnv" + }, + "globals": { + "$ref": "#/definitions/ESLintGlobals" + }, + "rules": { + "description": "See [Oxlint Rules](./rules)", + "allOf": [ + { + "$ref": "#/definitions/ESLintRules" + } + ] + }, + "settings": { + "$ref": "#/definitions/ESLintSettings" + } + }, + "definitions": { + "CustomComponent": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "required": [ + "attribute", + "name" + ], + "properties": { + "attribute": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "attributes", + "name" + ], + "properties": { + "attributes": { + "type": "array", + "items": { + "type": "string" + } + }, + "name": { + "type": "string" + } + } + } + ] + }, + "DummyRule": { + "anyOf": [ + { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + { + "type": "string" + }, + { + "type": "array", + "items": true + } + ] + }, + "ESLintEnv": { + "description": "Predefine global variables.", + "type": "object", + "additionalProperties": { + "type": "boolean" + } + }, + "ESLintGlobals": { + "description": "Add or remove global variables.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/GlobalValue" + } + }, + "ESLintRules": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/DummyRule" + } + }, + "ESLintSettings": { + "description": "Shared settings for plugins", + "type": "object", + "properties": { + "jsdoc": { + "$ref": "#/definitions/JSDocPluginSettings" + }, + "jsx-a11y": { + "$ref": "#/definitions/JSXA11yPluginSettings" + }, + "next": { + "$ref": "#/definitions/NextPluginSettings" + }, + "react": { + "$ref": "#/definitions/ReactPluginSettings" + } + } + }, + "GlobalValue": { + "type": "string", + "enum": [ + "readonly", + "writeable", + "off" + ] + }, + "JSDocPluginSettings": { + "type": "object", + "properties": { + "augmentsExtendsReplacesDocs": { + "description": "Only for `require-(yields|returns|description|example|param|throws)` rule", + "default": false, + "type": "boolean" + }, + "exemptDestructuredRootsFromChecks": { + "description": "Only for `require-param-type` and `require-param-description` rule", + "default": false, + "type": "boolean" + }, + "ignoreInternal": { + "description": "For all rules but NOT apply to `empty-tags` rule", + "default": false, + "type": "boolean" + }, + "ignorePrivate": { + "description": "For all rules but NOT apply to `check-access` and `empty-tags` rule", + "default": false, + "type": "boolean" + }, + "ignoreReplacesDocs": { + "description": "Only for `require-(yields|returns|description|example|param|throws)` rule", + "default": true, + "type": "boolean" + }, + "implementsReplacesDocs": { + "description": "Only for `require-(yields|returns|description|example|param|throws)` rule", + "default": false, + "type": "boolean" + }, + "overrideReplacesDocs": { + "description": "Only for `require-(yields|returns|description|example|param|throws)` rule", + "default": true, + "type": "boolean" + }, + "tagNamePreference": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/TagNamePreference" + } + } + } + }, + "JSXA11yPluginSettings": { + "type": "object", + "properties": { + "components": { + "default": {}, + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "polymorphicPropName": { + "type": [ + "string", + "null" + ] + } + } + }, + "NextPluginSettings": { + "type": "object", + "properties": { + "rootDir": { + "$ref": "#/definitions/OneOrMany_for_String" + } + } + }, + "OneOrMany_for_String": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "ReactPluginSettings": { + "type": "object", + "properties": { + "formComponents": { + "type": "array", + "items": { + "$ref": "#/definitions/CustomComponent" + } + }, + "linkComponents": { + "type": "array", + "items": { + "$ref": "#/definitions/CustomComponent" + } + } + } + }, + "TagNamePreference": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "required": [ + "message", + "replacement" + ], + "properties": { + "message": { + "type": "string" + }, + "replacement": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + }, + { + "type": "boolean" + } + ] + } + } +} diff --git a/tasks/website/src/linter/snapshots/schema_markdown.snap b/tasks/website/src/linter/snapshots/schema_markdown.snap new file mode 100644 index 0000000000000..0d3c070e8ccf9 --- /dev/null +++ b/tasks/website/src/linter/snapshots/schema_markdown.snap @@ -0,0 +1,211 @@ +--- +source: tasks/website/src/linter/json_schema.rs +expression: snapshot +--- +# Oxlint Configuration File + +This configuration is aligned with ESLint v8's configuration schema (`eslintrc.json`). + +Usage: `oxlint -c oxlintrc.json` + +::: danger NOTE + +Only the `.json` format is supported. + +::: + +Example + +```json + // oxlintrc.json + { + // Comments are supported. + "env": { + "browser": true + }, + "globals": { + "foo": "readonly", + }, + "settings": { + }, + "rules": { + "eqeqeq": "warn", + }, + } +``` + + +## env + +type: `object` + +Predefine global variables. + + + +## globals + +type: `object` + +Add or remove global variables. + + + +## rules + + +See [Oxlint Rules](./rules) + + + +## settings + +type: `object` + +Shared settings for plugins + + +### settings.jsdoc + +type: `object` + + + + +#### settings.jsdoc.augmentsExtendsReplacesDocs + +type: `boolean` + +Only for `require-(yields|returns|description|example|param|throws)` rule + + + +#### settings.jsdoc.exemptDestructuredRootsFromChecks + +type: `boolean` + +Only for `require-param-type` and `require-param-description` rule + + + +#### settings.jsdoc.ignoreInternal + +type: `boolean` + +For all rules but NOT apply to `empty-tags` rule + + + +#### settings.jsdoc.ignorePrivate + +type: `boolean` + +For all rules but NOT apply to `check-access` and `empty-tags` rule + + + +#### settings.jsdoc.ignoreReplacesDocs + +type: `boolean` + +Only for `require-(yields|returns|description|example|param|throws)` rule + + + +#### settings.jsdoc.implementsReplacesDocs + +type: `boolean` + +Only for `require-(yields|returns|description|example|param|throws)` rule + + + +#### settings.jsdoc.overrideReplacesDocs + +type: `boolean` + +Only for `require-(yields|returns|description|example|param|throws)` rule + + + +#### settings.jsdoc.tagNamePreference + +type: `object` + + + + + + +### settings.jsx-a11y + +type: `object` + + + + +#### settings.jsx-a11y.components + +type: `object` + + + + + +#### settings.jsx-a11y.polymorphicPropName + +type: `[ + string, + null +]` + + + + + + +### settings.next + +type: `object` + + + + +#### settings.next.rootDir + + + + + + + +### settings.react + +type: `object` + + + + +#### settings.react.formComponents + +type: `array` + + + + +##### settings.react.formComponents[n] + + + + + + + +#### settings.react.linkComponents + +type: `array` + + + + +##### settings.react.linkComponents[n] diff --git a/tasks/website/src/main.rs b/tasks/website/src/main.rs index 8f7987cc3ee54..eb85082add0b2 100644 --- a/tasks/website/src/main.rs +++ b/tasks/website/src/main.rs @@ -9,10 +9,10 @@ fn main() { let task = command.as_deref().unwrap_or("default"); match task { - "linter-schema-json" => linter::generate_schema_json(), - "linter-schema-markdown" => linter::generate_schema_markdown(), - "linter-cli" => linter::generate_cli(), - "linter-rules" => linter::generate_rules(), + "linter-schema-json" => linter::print_schema_json(), + "linter-schema-markdown" => linter::print_schema_markdown(), + "linter-cli" => linter::print_cli(), + "linter-rules" => linter::print_rules(), _ => println!("Missing task command."), } }