diff --git a/packages/oxlint-config/README.md b/packages/oxlint-config/README.md new file mode 100644 index 0000000..e0b7895 --- /dev/null +++ b/packages/oxlint-config/README.md @@ -0,0 +1,205 @@ +# @qlik/oxlint-config + +Qlik's shared [oxlint](https://oxc.rs/docs/guide/usage/linter) configurations. + +This package mirrors the rules from `@qlik/eslint-config` for projects that have adopted (or are migrating to) oxlint. oxlint is dramatically faster than ESLint and covers the majority of rules used in our ESLint configs. + +## Requirements + +- `oxlint >= 1.0.0` + +## Configs + +| Config | Plugins | Use for | +| ---------------- | ------------------------------------------- | ----------------------- | +| `recommended.js` | `typescript`, `import` | Any JS/TS project | +| `react.js` | `typescript`, `import`, `react`, `jsx-a11y` | React projects | +| `node.js` | `typescript`, `import`, `node` | Node.js scripts/servers | +| `vitest.js` | `typescript`, `import`, `vitest` | Vitest test files | +| `jest.js` | `typescript`, `import`, `jest` | Jest test files | + +## Usage + +Configs are plain JS objects and are consumed via an `oxlint.config.ts` file in your project root. The `defineConfig` helper from `oxlint` provides typing and merging. + +### TypeScript / JavaScript project + +```ts +// oxlint.config.ts +import { defineConfig } from "oxlint"; +import recommended from "@qlik/oxlint-config/recommended.js"; + +export default defineConfig({ extends: [recommended] }); +``` + +### React project + +```ts +// oxlint.config.ts +import { defineConfig } from "oxlint"; +import react from "@qlik/oxlint-config/react.js"; + +export default defineConfig({ extends: [react] }); +``` + +### Node.js project + +```ts +// oxlint.config.ts +import { defineConfig } from "oxlint"; +import node from "@qlik/oxlint-config/node.js"; + +export default defineConfig({ extends: [node] }); +``` + +### With test file overrides + +Use `overrides` to apply test-specific rules only to test file globs: + +```ts +// oxlint.config.ts +import { defineConfig } from "oxlint"; +import recommended from "@qlik/oxlint-config/recommended.js"; +import vitest from "@qlik/oxlint-config/vitest.js"; + +export default defineConfig({ + extends: [recommended], + overrides: [ + { + files: ["**/__tests__/**", "**/*.test.*", "**/*.spec.*"], + extends: [vitest], + }, + ], +}); +``` + +## Running oxlint + +```sh +# oxlint.config.ts is picked up automatically +oxlint + +# Or point to it explicitly +oxlint --config oxlint.config.ts +``` + +## Testing + +Snapshot files in [`test/generated/`](./test/generated/) show the fully-resolved rule set for each config (as printed by `oxlint --print-config`). They make it easy to review the exact rules that will be enforced and to spot unintended changes in pull requests. + +```sh +# Verify committed snapshots match the current config (used in CI) +pnpm test + +# Regenerate snapshots after changing a config, then commit the result +pnpm test:update +``` + +--- + +## Local development (consuming without npm publish) + +You can build and test this package locally without publishing to the npm registry. + +### 1. Pack a tarball + +```sh +cd packages/oxlint-config +pnpm pack +# Creates: qlik-oxlint-config-0.1.0.tgz +``` + +### 2. Consume the tarball in another project + +```sh +# In your consumer project +pnpm add /absolute/path/to/dev-tools-js/packages/oxlint-config/qlik-oxlint-config-0.1.0.tgz +``` + +Or with npm / yarn: + +```sh +npm install /absolute/path/to/dev-tools-js/packages/oxlint-config/qlik-oxlint-config-0.1.0.tgz +yarn add /absolute/path/to/dev-tools-js/packages/oxlint-config/qlik-oxlint-config-0.1.0.tgz +``` + +### 3. Inside this monorepo (workspace protocol) + +If your consumer is also a package in this monorepo, use the workspace protocol instead: + +```json +// consumer/package.json +{ + "devDependencies": { + "@qlik/oxlint-config": "workspace:^" + } +} +``` + +Then run `pnpm install` from the monorepo root. + +### 4. Create your oxlint config + +```ts +// oxlint.config.ts +import { defineConfig } from "oxlint"; +import recommended from "@qlik/oxlint-config/recommended.js"; + +export default defineConfig({ extends: [recommended] }); +``` + +--- + +## Running oxlint alongside ESLint + +During migration you can run both linters in sequence. Use +[`eslint-plugin-oxlint`](https://github.com/oxc-project/eslint-plugin-oxlint) to +disable ESLint rules that are already covered by oxlint and avoid duplicate +diagnostics: + +```sh +npm install --save-dev eslint-plugin-oxlint +``` + +```js +// eslint.config.js +import oxlint from "eslint-plugin-oxlint"; +export default [ + // ... your existing eslint config + oxlint.configs["flat/recommended"], +]; +``` + +Then run both: + +```sh +oxlint && eslint +``` + +## Rule coverage notes + +The following rules from `@qlik/eslint-config` are **not yet implemented** in +oxlint and remain ESLint-only: + +| ESLint rule | Notes | +| ---------------------------------------------------- | ---------------------------------------------------------------- | +| `camelcase` / `@typescript-eslint/naming-convention` | Not implemented in oxlint; TypeScript types enforce PascalCase | +| `no-restricted-syntax` | Not implemented; `no-labels`/`no-with` cover the important cases | +| `no-restricted-exports` | Not implemented | +| `no-restricted-properties` | Not implemented; `prefer-object-has-own` etc. cover key cases | + +### Type-aware TypeScript rules + +The following rules from `@typescript-eslint` require type information (💭 in +the oxlint docs). They are available via oxlint's +[type-aware alpha](https://oxc.rs/blog/2025-12-08-type-aware-alpha) and will be +covered automatically once type-aware mode is stable: + +- `@typescript-eslint/no-floating-promises` +- `@typescript-eslint/no-misused-promises` +- `@typescript-eslint/no-unnecessary-condition` +- `@typescript-eslint/no-unnecessary-type-arguments` +- `@typescript-eslint/return-await` +- `@typescript-eslint/switch-exhaustiveness-check` +- `@typescript-eslint/use-unknown-in-catch-callback-variable` +- and more diff --git a/packages/oxlint-config/jest.js b/packages/oxlint-config/jest.js new file mode 100644 index 0000000..4b866c0 --- /dev/null +++ b/packages/oxlint-config/jest.js @@ -0,0 +1,54 @@ +// @ts-check +// Qlik Jest oxlint config +// Extends recommended with jest plugin rules for test files. +// +// Typical usage – add an override to your project's .oxlintrc.json: +// +// { +// "extends": ["@qlik/oxlint-config/recommended.json"], +// "overrides": [ +// { +// "files": ["**/__tests__/**", "**/*.test.*", "**/*.spec.*"], +// "extends": ["@qlik/oxlint-config/jest.json"] +// } +// ] +// } +// +// Or, in an oxlint.config.ts: +// +// import { defineConfig } from "oxlint"; +// import jest from "@qlik/oxlint-config/jest.js"; +// export default defineConfig({ extends: [jest] }); + +import recommended from "./recommended.js"; + +/** @type {import("oxlint").OxlintConfig} */ +const config = { + extends: [recommended], + + plugins: ["typescript", "import", "jest"], + + env: { + jest: true, + }, + + rules: { + // console output in tests is usually intentional (debugging assertions) + "no-console": "off", + + // test files don't export production code, so import restrictions are relaxed + "no-restricted-imports": "off", + + /* ------------------------------------------------------------------ */ + /* Jest – correctness (override to make explicit) */ + /* ------------------------------------------------------------------ */ + + // focused tests (test.only / describe.only) must never be committed + "jest/no-focused-tests": "error", + + // disabled tests (test.skip / xtest) should be reviewed and removed + "jest/no-disabled-tests": "error", + }, +}; + +export default config; diff --git a/packages/oxlint-config/node.js b/packages/oxlint-config/node.js new file mode 100644 index 0000000..535420f --- /dev/null +++ b/packages/oxlint-config/node.js @@ -0,0 +1,26 @@ +// @ts-check +// Qlik Node.js oxlint config +// Extends recommended with Node.js-friendly overrides and the node plugin. + +import recommended from "./recommended.js"; + +/** @type {import("oxlint").OxlintConfig} */ +const config = { + extends: [recommended], + + plugins: ["typescript", "import", "node"], + + rules: { + // console is perfectly fine in Node.js scripts and servers + "no-console": "off", + + /* ------------------------------------------------------------------ */ + /* node plugin – restriction category */ + /* ------------------------------------------------------------------ */ + + // string concatenation for paths is fragile; use path.join/path.resolve + "node/no-path-concat": "error", + }, +}; + +export default config; diff --git a/packages/oxlint-config/package.json b/packages/oxlint-config/package.json new file mode 100644 index 0000000..0b11005 --- /dev/null +++ b/packages/oxlint-config/package.json @@ -0,0 +1,34 @@ +{ + "name": "@qlik/oxlint-config", + "version": "0.1.0", + "description": "Qlik's oxlint configs", + "repository": "git@github.com:qlik-oss/dev-tools-js.git", + "license": "ISC", + "type": "module", + "exports": { + "./recommended.js": "./recommended.js", + "./react.js": "./react.js", + "./node.js": "./node.js", + "./vitest.js": "./vitest.js", + "./jest.js": "./jest.js" + }, + "files": [ + "recommended.js", + "react.js", + "node.js", + "vitest.js", + "jest.js" + ], + "scripts": { + "format:check": "cd ../.. && prettier --check 'packages/oxlint-config' --ignore-unknown", + "format:write": "cd ../.. && prettier --write 'packages/oxlint-config' --ignore-unknown", + "test": "./test/verify-configs.sh", + "test:update": "./test/update-configs.sh" + }, + "devDependencies": { + "oxlint": "^1.50.0" + }, + "peerDependencies": { + "oxlint": ">=1.0.0" + } +} diff --git a/packages/oxlint-config/react.js b/packages/oxlint-config/react.js new file mode 100644 index 0000000..528846a --- /dev/null +++ b/packages/oxlint-config/react.js @@ -0,0 +1,117 @@ +// @ts-check +// Qlik React oxlint config +// Extends recommended with React and jsx-a11y plugins. +// Most jsx-a11y rules are already caught by the `correctness` category; +// only rules that need non-default options are configured explicitly. + +import recommended from "./recommended.js"; + +/** @type {import("oxlint").OxlintConfig} */ +const config = { + extends: [recommended], + + // plugins must include all parent plugins plus any additions + plugins: ["typescript", "import", "react", "jsx-a11y"], + + rules: { + /* ------------------------------------------------------------------ */ + /* React – style category (not on by default) */ + /* ------------------------------------------------------------------ */ + + // boolean props should be written as not + "react/jsx-boolean-value": ["error", "never"], + + // user-defined JSX components must use PascalCase + "react/jsx-pascal-case": "error", + + // self-close components that have no children: not + "react/self-closing-comp": "error", + + /* ------------------------------------------------------------------ */ + /* React – pedantic category */ + /* ------------------------------------------------------------------ */ + + // rules of hooks must always be followed + "react/rules-of-hooks": "error", + + // useless fragments add noise and affect perf + "react/jsx-no-useless-fragment": "error", + + // target="_blank" without rel="noreferrer" allows tab-napping attacks + "react/jsx-no-target-blank": "error", + + /* ------------------------------------------------------------------ */ + /* React – restriction category */ + /* ------------------------------------------------------------------ */ + + // dangerouslySetInnerHTML is an XSS vector; warn rather than error + // because there are legitimate uses (e.g. server-rendered HTML) + "react/no-danger": "warn", + + // unknown DOM props are silently ignored by React but almost always typos + "react/no-unknown-property": "error", + + /* ------------------------------------------------------------------ */ + /* React – suspicious overrides */ + /* ------------------------------------------------------------------ */ + + // since React 17 the JSX transform handles the React import automatically + "react/react-in-jsx-scope": "off", + + /* ------------------------------------------------------------------ */ + /* jsx-a11y – option overrides */ + /* Most rules are already enforced via the correctness category; */ + /* only rules that need project-specific options are listed here. */ + /* ------------------------------------------------------------------ */ + + // autofocus steals focus unexpectedly – allow it on non-DOM components + // (e.g. a custom component that wraps a native input) + "jsx-a11y/no-autofocus": ["error", { ignoreNonDOM: true }], + + // support custom Link components that use `to` instead of `href` + "jsx-a11y/anchor-is-valid": [ + "error", + { + components: ["Link"], + specialLink: ["to"], + aspects: ["noHref", "invalidHref", "preferButton"], + }, + ], + + // every label must have both a for/htmlFor attribute and a matching control + "jsx-a11y/label-has-associated-control": [ + "error", + { + assert: "both", + depth: 25, + }, + ], + + // interactive elements turned non-interactive must use one of these roles + "jsx-a11y/no-interactive-element-to-noninteractive-role": [ + "error", + { + tr: ["none", "presentation"], + }, + ], + + // non-interactive elements that are given an interactive role must be + // limited to semantically appropriate roles for each element type + "jsx-a11y/no-noninteractive-element-to-interactive-role": [ + "error", + { + ul: ["listbox", "menu", "menubar", "radiogroup", "tablist", "tree", "treegrid"], + ol: ["listbox", "menu", "menubar", "radiogroup", "tablist", "tree", "treegrid"], + li: ["menuitem", "option", "row", "tab", "treeitem"], + table: ["grid"], + td: ["gridcell"], + }, + ], + + // oxlint's no-redundant-roles does not accept options; the default + // behaviour already catches the most common case (nav + navigation) + "jsx-a11y/no-redundant-roles": "error", + }, +}; + +export default config; diff --git a/packages/oxlint-config/recommended.js b/packages/oxlint-config/recommended.js new file mode 100644 index 0000000..54f29e4 --- /dev/null +++ b/packages/oxlint-config/recommended.js @@ -0,0 +1,293 @@ +// @ts-check +// Qlik Recommended oxlint config +// Covers ESLint core, TypeScript, and Import rules. +// +// Category strategy +// ───────────────── +// correctness → rules that catch definite bugs (oxlint default) +// suspicious → code that is likely wrong (oxlint default) +// pedantic → extra-strict, few false positives — Qlik standard +// enables: array-callback-return, no-case-declarations, +// no-constructor-return, no-else-return, no-fallthrough, +// no-loop-func, no-lonely-if, no-new-wrappers, no-redeclare, +// no-self-compare, no-throw-literal, no-useless-return, radix, +// symbol-description, no-array-constructor, +// typescript/ban-ts-comment, typescript/prefer-enum-initializers +// +// Rules listed explicitly are those that need custom options, a non-"error" +// severity, belong to an opt-in category (restriction / style / perf), or +// override a category-level default. +// +// Rules NOT yet implemented in oxlint (remain ESLint-only): +// - camelcase / @typescript-eslint/naming-convention +// - no-restricted-syntax +// - no-restricted-exports +// - no-restricted-properties + +/** @type {import("oxlint").OxlintConfig} */ +const config = { + plugins: ["typescript", "import"], + + categories: { + correctness: "error", + suspicious: "error", + pedantic: "error", + }, + + rules: { + /* ------------------------------------------------------------------ */ + /* Category overrides – custom options */ + /* ------------------------------------------------------------------ */ + + // allow implicit returns in array callbacks (e.g. `arr.map(x => x && x.y)`) + "array-callback-return": ["error", { allowImplicit: true }], + + // strict equality everywhere, but allow `== null` to catch both null/undefined + eqeqeq: ["error", "always", { null: "ignore" }], + + // else-after-return is redundant; also disallow else-if-after-return + "no-else-return": ["error", { allowElseIf: false }], + + // always reject Promises with Error objects; allow empty rejections + "prefer-promise-reject-errors": ["error", { allowEmptyReject: true }], + + // assignment-in-return is almost always a bug; "always" = no exception + "no-return-assign": ["error", "always"], + + // constructor/class names must be PascalCase; don't require cap for plain calls + "new-cap": ["error", { newIsCap: true, capIsNew: false }], + + // param mutation is confusing; allow common accumulator/context/request patterns + "no-param-reassign": [ + "error", + { + props: true, + ignorePropertyModificationsFor: [ + "prev", // reduce accumulators + "acc", // reduce accumulators + "accumulator", // reduce accumulators + "e", // event objects (e.returnValue etc.) + "ctx", // Koa routing + "context", // Koa routing + "req", // Express requests + "request", // Express requests + "res", // Express responses + "response", // Express responses + "$scope", // Angular scopes + "staticContext", // React Router + "sharedState", // shared reducers + "state", // shared reducers + ], + }, + ], + + // restrict confusing browser globals that shadow window properties + "no-restricted-globals": [ + "error", + { name: "isFinite", message: "Use Number.isFinite instead" }, + { name: "isNaN", message: "Use Number.isNaN instead" }, + { name: "addEventListener", message: "Use window.addEventListener instead" }, + { name: "blur", message: "Use window.blur instead" }, + { name: "close", message: "Use window.close instead" }, + { name: "closed", message: "Use window.closed instead" }, + { name: "confirm", message: "Use window.confirm instead" }, + { name: "defaultStatus", message: "Use window.defaultStatus instead" }, + { name: "defaultstatus", message: "Use window.defaultstatus instead" }, + { name: "error", message: "Use window.error instead" }, + { name: "event", message: "Use window.event instead" }, + { name: "find", message: "Use window.find instead" }, + { name: "focus", message: "Use window.focus instead" }, + { name: "frameElement", message: "Use window.frameElement instead" }, + { name: "frames", message: "Use window.frames instead" }, + { name: "history", message: "Use window.history instead" }, + { name: "innerHeight", message: "Use window.innerHeight instead" }, + { name: "innerWidth", message: "Use window.innerWidth instead" }, + { name: "length", message: "Use window.length instead" }, + { name: "localStorage", message: "Use window.localStorage instead" }, + { name: "location", message: "Use window.location instead" }, + { name: "menubar", message: "Use window.menubar instead" }, + { name: "moveBy", message: "Use window.moveBy instead" }, + { name: "moveTo", message: "Use window.moveTo instead" }, + { name: "name", message: "Use window.name instead" }, + { name: "onblur", message: "Use window.onblur instead" }, + { name: "onerror", message: "Use window.onerror instead" }, + { name: "onfocus", message: "Use window.onfocus instead" }, + { name: "onload", message: "Use window.onload instead" }, + { name: "onresize", message: "Use window.onresize instead" }, + { name: "onunload", message: "Use window.onunload instead" }, + { name: "open", message: "Use window.open instead" }, + { name: "opener", message: "Use window.opener instead" }, + { name: "opera", message: "Use window.opera instead" }, + { name: "outerHeight", message: "Use window.outerHeight instead" }, + { name: "outerWidth", message: "Use window.outerWidth instead" }, + { name: "pageXOffset", message: "Use window.pageXOffset instead" }, + { name: "pageYOffset", message: "Use window.pageYOffset instead" }, + { name: "parent", message: "Use window.parent instead" }, + { name: "print", message: "Use window.print instead" }, + { name: "removeEventListener", message: "Use window.removeEventListener instead" }, + { name: "resizeBy", message: "Use window.resizeBy instead" }, + { name: "resizeTo", message: "Use window.resizeTo instead" }, + { name: "screen", message: "Use window.screen instead" }, + { name: "screenLeft", message: "Use window.screenLeft instead" }, + { name: "screenTop", message: "Use window.screenTop instead" }, + { name: "screenX", message: "Use window.screenX instead" }, + { name: "screenY", message: "Use window.screenY instead" }, + { name: "scroll", message: "Use window.scroll instead" }, + { name: "scrollbars", message: "Use window.scrollbars instead" }, + { name: "scrollBy", message: "Use window.scrollBy instead" }, + { name: "scrollTo", message: "Use window.scrollTo instead" }, + { name: "scrollX", message: "Use window.scrollX instead" }, + { name: "scrollY", message: "Use window.scrollY instead" }, + { name: "self", message: "Use window.self instead" }, + { name: "sessionStorage", message: "Use window.sessionStorage instead" }, + { name: "status", message: "Use window.status instead" }, + { name: "statusbar", message: "Use window.statusbar instead" }, + { name: "stop", message: "Use window.stop instead" }, + { name: "toolbar", message: "Use window.toolbar instead" }, + { name: "top", message: "Use window.top instead" }, + ], + + // BOM causes encoding issues in source files + "unicode-bom": ["error", "never"], + + // circular imports cause subtle bugs; trust external packages + "import/no-cycle": ["error", { ignoreExternal: true }], + + /* ------------------------------------------------------------------ */ + /* Warn instead of error */ + /* ------------------------------------------------------------------ */ + + // console output should be intentional; warn so CI surfaces it without blocking + "no-console": "warn", + + // anonymous functions make stack traces harder to read + "func-names": "warn", + + // empty function bodies should be a deliberate and visible choice + "no-empty-function": "warn", + + /* ------------------------------------------------------------------ */ + /* Restriction – explicit opt-in */ + /* ------------------------------------------------------------------ */ + + // alert/confirm/prompt are debug tools, not production patterns + "no-alert": "error", + + // bitwise ops are almost always a typo for logical operators (|, &) + "no-bitwise": "error", + + // __proto__ is deprecated; use Object.getPrototypeOf instead + "no-proto": "error", + + // comma sequences produce surprising evaluated values + "no-sequences": "error", + + // var has confusing function scope; always use let/const + "no-var": "error", + + // switch without a default case silently ignores unhandled values + "default-case": "error", + + // teams configure their own restricted imports per project + "no-restricted-imports": "off", + + /* ------------------------------------------------------------------ */ + /* TypeScript – restriction */ + /* ------------------------------------------------------------------ */ + + // always use `import type` for type-only imports + "typescript/consistent-type-imports": "error", + + // delete on dynamic keys bypasses type safety + "typescript/no-dynamic-delete": "error", + + // side-effect imports of type-only modules defeat `import type` + "typescript/no-import-type-side-effects": "error", + + // void used as a type where it shouldn't be is confusing + "typescript/no-invalid-void-type": "error", + + // !foo ?? bar is always a logical bug + "typescript/no-non-null-asserted-nullish-coalescing": "error", + + // non-null assertions hide potential null bugs; use explicit checks + "typescript/no-non-null-assertion": "error", + + // enum members with explicit values are easier to audit + "typescript/prefer-literal-enum-member": "error", + + /* ------------------------------------------------------------------ */ + /* TypeScript – nursery / style (not covered by pedantic) */ + /* ------------------------------------------------------------------ */ + + // enforce consistent type-only exports (mirrors consistent-type-imports) + "typescript/consistent-type-exports": "error", + + // overloaded signatures that can be unified reduce API surface area + "typescript/unified-signatures": "error", + + /* ------------------------------------------------------------------ */ + /* Import – restriction */ + /* ------------------------------------------------------------------ */ + + // AMD require() is legacy and incompatible with ES modules + "import/no-amd": "error", + + // dynamic require() defeats static analysis and tree-shaking + "import/no-dynamic-require": "error", + + // webpack-specific loader syntax in import paths is non-standard + "import/no-webpack-loader-syntax": "error", + + /* ------------------------------------------------------------------ */ + /* Import – suspicious overrides (disable noisy defaults) */ + /* ------------------------------------------------------------------ */ + + // TypeScript already catches misused default imports + "import/no-named-as-default-member": "off", + + // empty named import blocks cause false positives with re-export patterns + "import/no-empty-named-blocks": "off", + + /* ------------------------------------------------------------------ */ + /* Style – explicit opt-in */ + /* (style category is intentionally not enabled globally; it includes */ + /* sort-imports, no-inline-comments, no-undefined, no-ternary, etc.) */ + /* ------------------------------------------------------------------ */ + + "default-case-last": "error", + "default-param-last": "error", + "no-continue": "error", + "no-extra-label": "error", + "no-labels": "error", + "no-label-var": "error", + "no-lone-blocks": "error", + "no-multi-str": "error", + "no-multi-assign": "error", + "no-nested-ternary": "error", + "no-new-func": "error", + "no-script-url": "error", + "operator-assignment": "error", + "prefer-const": "error", + "prefer-exponentiation-operator": "error", + "prefer-numeric-literals": "error", + "prefer-object-has-own": "error", + "prefer-object-spread": "error", + "prefer-rest-params": "error", + "prefer-spread": "error", + "prefer-template": "error", + yoda: "error", + + /* ------------------------------------------------------------------ */ + /* Perf – explicit opt-in */ + /* ------------------------------------------------------------------ */ + + // await in loops blocks parallelism – batch with Promise.all instead + "no-await-in-loop": "error", + + // unnecessary .call()/.apply() skip the optimised fast-path + "no-useless-call": "error", + }, +}; + +export default config; diff --git a/packages/oxlint-config/test/generate-configs.js b/packages/oxlint-config/test/generate-configs.js new file mode 100644 index 0000000..edc1363 --- /dev/null +++ b/packages/oxlint-config/test/generate-configs.js @@ -0,0 +1,99 @@ +/** + * Generates test/generated/*.json snapshot files for each oxlint config preset. + * + * Each snapshot is produced by writing a temporary oxlint.config.mjs that + * extends the preset, running `oxlint --print-config` against a dummy file, + * and capturing the fully-resolved JSON output. This lets reviewers see the + * exact rules oxlint will enforce at a glance, without running the linter. + * + * Usage: + * node test/generate-configs.js + */ + +import { execSync } from "node:child_process"; +import fs from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const root = path.resolve(__dirname, ".."); +const generatedDir = path.resolve(__dirname, "generated"); + +await fs.mkdir(generatedDir, { recursive: true }); + +/** + * Configs to snapshot. + * Each entry maps a name to the import path (relative to the package root). + */ +const configs = { + recommended: "../recommended.js", + react: "../react.js", + node: "../node.js", + vitest: "../vitest.js", + jest: "../jest.js", +}; + +// Reusable dummy file for --print-config (just needs to exist) +const dummyFile = path.resolve(root, "_print-config-dummy.js"); +await fs.writeFile(dummyFile, ""); + +for (const [name, importPath] of Object.entries(configs)) { + // Write the ephemeral config inside the package root so that Node can + // resolve both "oxlint" and the local preset via the workspace node_modules. + const configFile = path.resolve(root, `_print-config-${name}.mjs`); + + await fs.writeFile( + configFile, + [ + `import { defineConfig } from "oxlint";`, + `import preset from "${path.resolve(root, importPath)}";`, + `export default defineConfig(preset);`, + ].join("\n"), + ); + + let resolved; + try { + resolved = execSync(`node_modules/.bin/oxlint --config "${configFile}" --print-config "${dummyFile}"`, { + cwd: root, + encoding: "utf8", + }); + } catch (err) { + console.error(`Failed to resolve config for "${name}":`, err.message); + process.exitCode = 1; + continue; + } finally { + await fs.rm(configFile, { force: true }); + } + + // Pretty-print the JSON with sorted keys for stable diffs + const parsed = JSON.parse(resolved); + const sorted = sortKeys(parsed); + const pretty = JSON.stringify(sorted, null, 2); + + const outFile = path.resolve(generatedDir, `${name}-final-config.json`); + await fs.writeFile(outFile, pretty + "\n"); + console.log(` ✓ ${name} → test/generated/${name}-final-config.json`); +} + +// Clean up the shared dummy file +await fs.rm(dummyFile, { force: true }); + +/** + * Recursively sorts object keys for a stable representation. + * Arrays and primitives are left unchanged. + * @param {unknown} value + * @returns {unknown} + */ +function sortKeys(value) { + if (Array.isArray(value)) { + return value.map(sortKeys); + } + if (value !== null && typeof value === "object") { + return Object.fromEntries( + Object.entries(value) + .sort(([a], [b]) => a.localeCompare(b)) + .map(([k, v]) => [k, sortKeys(v)]), + ); + } + return value; +} diff --git a/packages/oxlint-config/test/generated/jest-final-config.json b/packages/oxlint-config/test/generated/jest-final-config.json new file mode 100644 index 0000000..a9699d1 --- /dev/null +++ b/packages/oxlint-config/test/generated/jest-final-config.json @@ -0,0 +1,233 @@ +{ + "categories": {}, + "env": { + "jest": true + }, + "globals": {}, + "ignorePatterns": [], + "plugins": [ + "typescript", + "import", + "jest" + ], + "rules": { + "array-callback-return": "deny", + "block-scoped-var": "deny", + "class-methods-use-this": "warn", + "constructor-super": "deny", + "default-case": "deny", + "default-case-last": "deny", + "default-param-last": "deny", + "eqeqeq": "deny", + "for-direction": "deny", + "func-names": "warn", + "getter-return": "deny", + "import/default": "deny", + "import/max-dependencies": "allow", + "import/namespace": "deny", + "import/no-absolute-path": "deny", + "import/no-amd": "deny", + "import/no-cycle": "deny", + "import/no-duplicates": "deny", + "import/no-dynamic-require": "deny", + "import/no-empty-named-blocks": "allow", + "import/no-mutable-exports": "deny", + "import/no-named-as-default": "deny", + "import/no-named-as-default-member": "allow", + "import/no-named-default": "deny", + "import/no-self-import": "deny", + "import/no-unassigned-import": "deny", + "import/no-webpack-loader-syntax": "deny", + "jest/expect-expect": "deny", + "jest/no-commented-out-tests": "deny", + "jest/no-conditional-expect": "deny", + "jest/no-disabled-tests": "deny", + "jest/no-export": "deny", + "jest/no-focused-tests": "deny", + "jest/no-standalone-expect": "deny", + "jest/require-to-throw-message": "deny", + "jest/valid-describe-callback": "deny", + "jest/valid-expect": "deny", + "jest/valid-title": "deny", + "new-cap": "deny", + "no-alert": "deny", + "no-async-promise-executor": "deny", + "no-await-in-loop": "deny", + "no-bitwise": "deny", + "no-caller": "deny", + "no-class-assign": "deny", + "no-compare-neg-zero": "deny", + "no-cond-assign": "deny", + "no-console": "allow", + "no-const-assign": "deny", + "no-constant-binary-expression": "deny", + "no-constant-condition": "deny", + "no-continue": "deny", + "no-control-regex": "deny", + "no-debugger": "deny", + "no-delete-var": "deny", + "no-dupe-class-members": "deny", + "no-dupe-else-if": "deny", + "no-dupe-keys": "deny", + "no-duplicate-case": "deny", + "no-else-return": "deny", + "no-empty-character-class": "deny", + "no-empty-function": "allow", + "no-empty-pattern": "deny", + "no-empty-static-block": "deny", + "no-eval": "deny", + "no-ex-assign": "deny", + "no-extend-native": "deny", + "no-extra-bind": "deny", + "no-extra-boolean-cast": "deny", + "no-extra-label": "deny", + "no-func-assign": "deny", + "no-global-assign": "deny", + "no-import-assign": "deny", + "no-invalid-regexp": "deny", + "no-irregular-whitespace": "deny", + "no-iterator": "deny", + "no-label-var": "deny", + "no-labels": "deny", + "no-lone-blocks": "deny", + "no-loss-of-precision": "deny", + "no-misleading-character-class": "deny", + "no-multi-assign": "deny", + "no-multi-str": "deny", + "no-nested-ternary": "deny", + "no-new": "deny", + "no-new-func": "deny", + "no-new-native-nonconstructor": "deny", + "no-nonoctal-decimal-escape": "deny", + "no-obj-calls": "deny", + "no-param-reassign": "deny", + "no-proto": "deny", + "no-regex-spaces": "deny", + "no-restricted-globals": "deny", + "no-restricted-imports": "allow", + "no-return-assign": "deny", + "no-script-url": "deny", + "no-self-assign": "deny", + "no-sequences": "deny", + "no-setter-return": "deny", + "no-shadow": "deny", + "no-shadow-restricted-names": "deny", + "no-sparse-arrays": "deny", + "no-template-curly-in-string": "deny", + "no-this-before-super": "deny", + "no-unassigned-vars": "deny", + "no-unexpected-multiline": "deny", + "no-unmodified-loop-condition": "deny", + "no-unneeded-ternary": "deny", + "no-unreachable": "deny", + "no-unsafe-finally": "deny", + "no-unsafe-negation": "deny", + "no-unsafe-optional-chaining": "deny", + "no-unused-expressions": "deny", + "no-unused-labels": "deny", + "no-unused-private-class-members": "deny", + "no-unused-vars": "deny", + "no-useless-backreference": "deny", + "no-useless-call": "deny", + "no-useless-catch": "deny", + "no-useless-concat": "deny", + "no-useless-constructor": "deny", + "no-useless-escape": "deny", + "no-useless-rename": "deny", + "no-var": "deny", + "no-with": "deny", + "operator-assignment": "deny", + "prefer-const": "deny", + "prefer-exponentiation-operator": "deny", + "prefer-numeric-literals": "deny", + "prefer-object-has-own": "deny", + "prefer-object-spread": "deny", + "prefer-promise-reject-errors": "deny", + "prefer-rest-params": "deny", + "prefer-spread": "deny", + "prefer-template": "deny", + "preserve-caught-error": "deny", + "require-await": "warn", + "require-yield": "deny", + "typescript/await-thenable": "deny", + "typescript/consistent-type-exports": "deny", + "typescript/consistent-type-imports": "deny", + "typescript/no-array-delete": "deny", + "typescript/no-base-to-string": "deny", + "typescript/no-confusing-non-null-assertion": "deny", + "typescript/no-duplicate-enum-values": "deny", + "typescript/no-duplicate-type-constituents": "deny", + "typescript/no-dynamic-delete": "deny", + "typescript/no-extra-non-null-assertion": "deny", + "typescript/no-extraneous-class": "deny", + "typescript/no-floating-promises": "deny", + "typescript/no-for-in-array": "deny", + "typescript/no-implied-eval": "deny", + "typescript/no-import-type-side-effects": "deny", + "typescript/no-invalid-void-type": "deny", + "typescript/no-meaningless-void-operator": "deny", + "typescript/no-misused-new": "deny", + "typescript/no-misused-spread": "deny", + "typescript/no-non-null-asserted-nullish-coalescing": "deny", + "typescript/no-non-null-asserted-optional-chain": "deny", + "typescript/no-non-null-assertion": "deny", + "typescript/no-redundant-type-constituents": "deny", + "typescript/no-this-alias": "deny", + "typescript/no-unnecessary-boolean-literal-compare": "deny", + "typescript/no-unnecessary-parameter-property-assignment": "deny", + "typescript/no-unnecessary-template-expression": "deny", + "typescript/no-unnecessary-type-arguments": "deny", + "typescript/no-unnecessary-type-assertion": "deny", + "typescript/no-unnecessary-type-constraint": "deny", + "typescript/no-unsafe-declaration-merging": "deny", + "typescript/no-unsafe-enum-comparison": "deny", + "typescript/no-unsafe-type-assertion": "deny", + "typescript/no-unsafe-unary-minus": "deny", + "typescript/no-useless-empty-export": "deny", + "typescript/no-wrapper-object-types": "deny", + "typescript/prefer-as-const": "deny", + "typescript/prefer-literal-enum-member": "deny", + "typescript/prefer-namespace-keyword": "deny", + "typescript/prefer-reduce-type-parameter": "deny", + "typescript/prefer-return-this-type": "deny", + "typescript/require-array-sort-compare": "deny", + "typescript/restrict-template-expressions": "deny", + "typescript/triple-slash-reference": "deny", + "typescript/unbound-method": "deny", + "typescript/unified-signatures": "deny", + "typescript/use-unknown-in-catch-callback-variable": "deny", + "unicode-bom": "deny", + "use-isnan": "deny", + "valid-typeof": "deny", + "yoda": "deny" + }, + "settings": { + "jsdoc": { + "augmentsExtendsReplacesDocs": false, + "exemptDestructuredRootsFromChecks": false, + "ignoreInternal": false, + "ignorePrivate": false, + "ignoreReplacesDocs": true, + "implementsReplacesDocs": false, + "overrideReplacesDocs": true, + "tagNamePreference": {} + }, + "jsx-a11y": { + "attributes": {}, + "components": {}, + "polymorphicPropName": null + }, + "next": { + "rootDir": [] + }, + "react": { + "componentWrapperFunctions": [], + "formComponents": [], + "linkComponents": [], + "version": null + }, + "vitest": { + "typecheck": false + } + } +} diff --git a/packages/oxlint-config/test/generated/node-final-config.json b/packages/oxlint-config/test/generated/node-final-config.json new file mode 100644 index 0000000..28552a3 --- /dev/null +++ b/packages/oxlint-config/test/generated/node-final-config.json @@ -0,0 +1,223 @@ +{ + "categories": {}, + "env": { + "builtin": true + }, + "globals": {}, + "ignorePatterns": [], + "plugins": [ + "typescript", + "import", + "node" + ], + "rules": { + "array-callback-return": "deny", + "block-scoped-var": "deny", + "class-methods-use-this": "warn", + "constructor-super": "deny", + "default-case": "deny", + "default-case-last": "deny", + "default-param-last": "deny", + "eqeqeq": "deny", + "for-direction": "deny", + "func-names": "warn", + "getter-return": "deny", + "import/default": "deny", + "import/max-dependencies": "allow", + "import/namespace": "deny", + "import/no-absolute-path": "deny", + "import/no-amd": "deny", + "import/no-cycle": "deny", + "import/no-duplicates": "deny", + "import/no-dynamic-require": "deny", + "import/no-empty-named-blocks": "allow", + "import/no-mutable-exports": "deny", + "import/no-named-as-default": "deny", + "import/no-named-as-default-member": "allow", + "import/no-named-default": "deny", + "import/no-self-import": "deny", + "import/no-unassigned-import": "deny", + "import/no-webpack-loader-syntax": "deny", + "new-cap": "deny", + "no-alert": "deny", + "no-async-promise-executor": "deny", + "no-await-in-loop": "deny", + "no-bitwise": "deny", + "no-caller": "deny", + "no-class-assign": "deny", + "no-compare-neg-zero": "deny", + "no-cond-assign": "deny", + "no-console": "allow", + "no-const-assign": "deny", + "no-constant-binary-expression": "deny", + "no-constant-condition": "deny", + "no-continue": "deny", + "no-control-regex": "deny", + "no-debugger": "deny", + "no-delete-var": "deny", + "no-dupe-class-members": "deny", + "no-dupe-else-if": "deny", + "no-dupe-keys": "deny", + "no-duplicate-case": "deny", + "no-else-return": "deny", + "no-empty-character-class": "deny", + "no-empty-function": "allow", + "no-empty-pattern": "deny", + "no-empty-static-block": "deny", + "no-eval": "deny", + "no-ex-assign": "deny", + "no-extend-native": "deny", + "no-extra-bind": "deny", + "no-extra-boolean-cast": "deny", + "no-extra-label": "deny", + "no-func-assign": "deny", + "no-global-assign": "deny", + "no-import-assign": "deny", + "no-invalid-regexp": "deny", + "no-irregular-whitespace": "deny", + "no-iterator": "deny", + "no-label-var": "deny", + "no-labels": "deny", + "no-lone-blocks": "deny", + "no-loss-of-precision": "deny", + "no-misleading-character-class": "deny", + "no-multi-assign": "deny", + "no-multi-str": "deny", + "no-nested-ternary": "deny", + "no-new": "deny", + "no-new-func": "deny", + "no-new-native-nonconstructor": "deny", + "no-nonoctal-decimal-escape": "deny", + "no-obj-calls": "deny", + "no-param-reassign": "deny", + "no-proto": "deny", + "no-regex-spaces": "deny", + "no-restricted-globals": "deny", + "no-restricted-imports": "allow", + "no-return-assign": "deny", + "no-script-url": "deny", + "no-self-assign": "deny", + "no-sequences": "deny", + "no-setter-return": "deny", + "no-shadow": "deny", + "no-shadow-restricted-names": "deny", + "no-sparse-arrays": "deny", + "no-template-curly-in-string": "deny", + "no-this-before-super": "deny", + "no-unassigned-vars": "deny", + "no-unexpected-multiline": "deny", + "no-unmodified-loop-condition": "deny", + "no-unneeded-ternary": "deny", + "no-unreachable": "deny", + "no-unsafe-finally": "deny", + "no-unsafe-negation": "deny", + "no-unsafe-optional-chaining": "deny", + "no-unused-expressions": "deny", + "no-unused-labels": "deny", + "no-unused-private-class-members": "deny", + "no-unused-vars": "deny", + "no-useless-backreference": "deny", + "no-useless-call": "deny", + "no-useless-catch": "deny", + "no-useless-concat": "deny", + "no-useless-constructor": "deny", + "no-useless-escape": "deny", + "no-useless-rename": "deny", + "no-var": "deny", + "no-with": "deny", + "node/no-path-concat": "deny", + "operator-assignment": "deny", + "prefer-const": "deny", + "prefer-exponentiation-operator": "deny", + "prefer-numeric-literals": "deny", + "prefer-object-has-own": "deny", + "prefer-object-spread": "deny", + "prefer-promise-reject-errors": "deny", + "prefer-rest-params": "deny", + "prefer-spread": "deny", + "prefer-template": "deny", + "preserve-caught-error": "deny", + "require-await": "warn", + "require-yield": "deny", + "typescript/await-thenable": "deny", + "typescript/consistent-type-exports": "deny", + "typescript/consistent-type-imports": "deny", + "typescript/no-array-delete": "deny", + "typescript/no-base-to-string": "deny", + "typescript/no-confusing-non-null-assertion": "deny", + "typescript/no-duplicate-enum-values": "deny", + "typescript/no-duplicate-type-constituents": "deny", + "typescript/no-dynamic-delete": "deny", + "typescript/no-extra-non-null-assertion": "deny", + "typescript/no-extraneous-class": "deny", + "typescript/no-floating-promises": "deny", + "typescript/no-for-in-array": "deny", + "typescript/no-implied-eval": "deny", + "typescript/no-import-type-side-effects": "deny", + "typescript/no-invalid-void-type": "deny", + "typescript/no-meaningless-void-operator": "deny", + "typescript/no-misused-new": "deny", + "typescript/no-misused-spread": "deny", + "typescript/no-non-null-asserted-nullish-coalescing": "deny", + "typescript/no-non-null-asserted-optional-chain": "deny", + "typescript/no-non-null-assertion": "deny", + "typescript/no-redundant-type-constituents": "deny", + "typescript/no-this-alias": "deny", + "typescript/no-unnecessary-boolean-literal-compare": "deny", + "typescript/no-unnecessary-parameter-property-assignment": "deny", + "typescript/no-unnecessary-template-expression": "deny", + "typescript/no-unnecessary-type-arguments": "deny", + "typescript/no-unnecessary-type-assertion": "deny", + "typescript/no-unnecessary-type-constraint": "deny", + "typescript/no-unsafe-declaration-merging": "deny", + "typescript/no-unsafe-enum-comparison": "deny", + "typescript/no-unsafe-type-assertion": "deny", + "typescript/no-unsafe-unary-minus": "deny", + "typescript/no-useless-empty-export": "deny", + "typescript/no-wrapper-object-types": "deny", + "typescript/prefer-as-const": "deny", + "typescript/prefer-literal-enum-member": "deny", + "typescript/prefer-namespace-keyword": "deny", + "typescript/prefer-reduce-type-parameter": "deny", + "typescript/prefer-return-this-type": "deny", + "typescript/require-array-sort-compare": "deny", + "typescript/restrict-template-expressions": "deny", + "typescript/triple-slash-reference": "deny", + "typescript/unbound-method": "deny", + "typescript/unified-signatures": "deny", + "typescript/use-unknown-in-catch-callback-variable": "deny", + "unicode-bom": "deny", + "use-isnan": "deny", + "valid-typeof": "deny", + "yoda": "deny" + }, + "settings": { + "jsdoc": { + "augmentsExtendsReplacesDocs": false, + "exemptDestructuredRootsFromChecks": false, + "ignoreInternal": false, + "ignorePrivate": false, + "ignoreReplacesDocs": true, + "implementsReplacesDocs": false, + "overrideReplacesDocs": true, + "tagNamePreference": {} + }, + "jsx-a11y": { + "attributes": {}, + "components": {}, + "polymorphicPropName": null + }, + "next": { + "rootDir": [] + }, + "react": { + "componentWrapperFunctions": [], + "formComponents": [], + "linkComponents": [], + "version": null + }, + "vitest": { + "typecheck": false + } + } +} diff --git a/packages/oxlint-config/test/generated/react-final-config.json b/packages/oxlint-config/test/generated/react-final-config.json new file mode 100644 index 0000000..5d575d9 --- /dev/null +++ b/packages/oxlint-config/test/generated/react-final-config.json @@ -0,0 +1,322 @@ +{ + "categories": {}, + "env": { + "builtin": true + }, + "globals": {}, + "ignorePatterns": [], + "plugins": [ + "react", + "typescript", + "import", + "jsx-a11y" + ], + "rules": { + "array-callback-return": "deny", + "block-scoped-var": "deny", + "class-methods-use-this": "warn", + "constructor-super": "deny", + "default-case": "deny", + "default-case-last": "deny", + "default-param-last": "deny", + "eqeqeq": "deny", + "for-direction": "deny", + "func-names": "warn", + "getter-return": "deny", + "import/default": "deny", + "import/max-dependencies": "allow", + "import/namespace": "deny", + "import/no-absolute-path": "deny", + "import/no-amd": "deny", + "import/no-cycle": "deny", + "import/no-duplicates": "deny", + "import/no-dynamic-require": "deny", + "import/no-empty-named-blocks": "allow", + "import/no-mutable-exports": "deny", + "import/no-named-as-default": "deny", + "import/no-named-as-default-member": "allow", + "import/no-named-default": "deny", + "import/no-self-import": "deny", + "import/no-unassigned-import": "deny", + "import/no-webpack-loader-syntax": "deny", + "jsx_a11y/alt-text": "deny", + "jsx_a11y/anchor-has-content": "deny", + "jsx_a11y/anchor-is-valid": [ + "deny", + [ + { + "aspects": [ + "noHref", + "invalidHref", + "preferButton" + ], + "components": [ + "Link" + ], + "specialLink": [ + "to" + ] + } + ] + ], + "jsx_a11y/aria-activedescendant-has-tabindex": "deny", + "jsx_a11y/aria-props": "deny", + "jsx_a11y/aria-proptypes": "deny", + "jsx_a11y/aria-role": "deny", + "jsx_a11y/aria-unsupported-elements": "deny", + "jsx_a11y/autocomplete-valid": "deny", + "jsx_a11y/click-events-have-key-events": "deny", + "jsx_a11y/heading-has-content": "deny", + "jsx_a11y/html-has-lang": "deny", + "jsx_a11y/iframe-has-title": "deny", + "jsx_a11y/img-redundant-alt": "deny", + "jsx_a11y/label-has-associated-control": [ + "deny", + [ + { + "assert": "both", + "depth": 25 + } + ] + ], + "jsx_a11y/lang": "deny", + "jsx_a11y/media-has-caption": "deny", + "jsx_a11y/mouse-events-have-key-events": "deny", + "jsx_a11y/no-access-key": "deny", + "jsx_a11y/no-aria-hidden-on-focusable": "deny", + "jsx_a11y/no-autofocus": [ + "deny", + [ + { + "ignoreNonDOM": true + } + ] + ], + "jsx_a11y/no-distracting-elements": "deny", + "jsx_a11y/no-noninteractive-tabindex": "deny", + "jsx_a11y/no-redundant-roles": "deny", + "jsx_a11y/no-static-element-interactions": "deny", + "jsx_a11y/prefer-tag-over-role": "deny", + "jsx_a11y/role-has-required-aria-props": "deny", + "jsx_a11y/role-supports-aria-props": "deny", + "jsx_a11y/scope": "deny", + "jsx_a11y/tabindex-no-positive": "deny", + "new-cap": "deny", + "no-alert": "deny", + "no-async-promise-executor": "deny", + "no-await-in-loop": "deny", + "no-bitwise": "deny", + "no-caller": "deny", + "no-class-assign": "deny", + "no-compare-neg-zero": "deny", + "no-cond-assign": "deny", + "no-console": "warn", + "no-const-assign": "deny", + "no-constant-binary-expression": "deny", + "no-constant-condition": "deny", + "no-continue": "deny", + "no-control-regex": "deny", + "no-debugger": "deny", + "no-delete-var": "deny", + "no-dupe-class-members": "deny", + "no-dupe-else-if": "deny", + "no-dupe-keys": "deny", + "no-duplicate-case": "deny", + "no-else-return": "deny", + "no-empty-character-class": "deny", + "no-empty-function": "allow", + "no-empty-pattern": "deny", + "no-empty-static-block": "deny", + "no-eval": "deny", + "no-ex-assign": "deny", + "no-extend-native": "deny", + "no-extra-bind": "deny", + "no-extra-boolean-cast": "deny", + "no-extra-label": "deny", + "no-func-assign": "deny", + "no-global-assign": "deny", + "no-import-assign": "deny", + "no-invalid-regexp": "deny", + "no-irregular-whitespace": "deny", + "no-iterator": "deny", + "no-label-var": "deny", + "no-labels": "deny", + "no-lone-blocks": "deny", + "no-loss-of-precision": "deny", + "no-misleading-character-class": "deny", + "no-multi-assign": "deny", + "no-multi-str": "deny", + "no-nested-ternary": "deny", + "no-new": "deny", + "no-new-func": "deny", + "no-new-native-nonconstructor": "deny", + "no-nonoctal-decimal-escape": "deny", + "no-obj-calls": "deny", + "no-param-reassign": "deny", + "no-proto": "deny", + "no-regex-spaces": "deny", + "no-restricted-globals": "deny", + "no-restricted-imports": "allow", + "no-return-assign": "deny", + "no-script-url": "deny", + "no-self-assign": "deny", + "no-sequences": "deny", + "no-setter-return": "deny", + "no-shadow": "deny", + "no-shadow-restricted-names": "deny", + "no-sparse-arrays": "deny", + "no-template-curly-in-string": "deny", + "no-this-before-super": "deny", + "no-unassigned-vars": "deny", + "no-unexpected-multiline": "deny", + "no-unmodified-loop-condition": "deny", + "no-unneeded-ternary": "deny", + "no-unreachable": "deny", + "no-unsafe-finally": "deny", + "no-unsafe-negation": "deny", + "no-unsafe-optional-chaining": "deny", + "no-unused-expressions": "deny", + "no-unused-labels": "deny", + "no-unused-private-class-members": "deny", + "no-unused-vars": "deny", + "no-useless-backreference": "deny", + "no-useless-call": "deny", + "no-useless-catch": "deny", + "no-useless-concat": "deny", + "no-useless-constructor": "deny", + "no-useless-escape": "deny", + "no-useless-rename": "deny", + "no-var": "deny", + "no-with": "deny", + "operator-assignment": "deny", + "prefer-const": "deny", + "prefer-exponentiation-operator": "deny", + "prefer-numeric-literals": "deny", + "prefer-object-has-own": "deny", + "prefer-object-spread": "deny", + "prefer-promise-reject-errors": "deny", + "prefer-rest-params": "deny", + "prefer-spread": "deny", + "prefer-template": "deny", + "preserve-caught-error": "deny", + "react/exhaustive-deps": "deny", + "react/forward-ref-uses-ref": "deny", + "react/iframe-missing-sandbox": "deny", + "react/jsx-boolean-value": [ + "deny", + [ + "never" + ] + ], + "react/jsx-key": "deny", + "react/jsx-no-comment-textnodes": "deny", + "react/jsx-no-duplicate-props": "deny", + "react/jsx-no-script-url": "deny", + "react/jsx-no-target-blank": "deny", + "react/jsx-no-undef": "deny", + "react/jsx-no-useless-fragment": "deny", + "react/jsx-pascal-case": "deny", + "react/jsx-props-no-spread-multi": "deny", + "react/no-children-prop": "deny", + "react/no-danger": "warn", + "react/no-danger-with-children": "deny", + "react/no-did-mount-set-state": "deny", + "react/no-direct-mutation-state": "deny", + "react/no-find-dom-node": "deny", + "react/no-is-mounted": "deny", + "react/no-namespace": "deny", + "react/no-render-return-value": "deny", + "react/no-string-refs": "deny", + "react/no-this-in-sfc": "deny", + "react/no-unknown-property": "deny", + "react/no-unsafe": "deny", + "react/no-will-update-set-state": "deny", + "react/react-in-jsx-scope": "allow", + "react/rules-of-hooks": "deny", + "react/self-closing-comp": "deny", + "react/style-prop-object": "deny", + "react/void-dom-elements-no-children": "deny", + "require-await": "warn", + "require-yield": "deny", + "typescript/await-thenable": "deny", + "typescript/consistent-type-exports": "deny", + "typescript/consistent-type-imports": "deny", + "typescript/no-array-delete": "deny", + "typescript/no-base-to-string": "deny", + "typescript/no-confusing-non-null-assertion": "deny", + "typescript/no-duplicate-enum-values": "deny", + "typescript/no-duplicate-type-constituents": "deny", + "typescript/no-dynamic-delete": "deny", + "typescript/no-extra-non-null-assertion": "deny", + "typescript/no-extraneous-class": "deny", + "typescript/no-floating-promises": "deny", + "typescript/no-for-in-array": "deny", + "typescript/no-implied-eval": "deny", + "typescript/no-import-type-side-effects": "deny", + "typescript/no-invalid-void-type": "deny", + "typescript/no-meaningless-void-operator": "deny", + "typescript/no-misused-new": "deny", + "typescript/no-misused-spread": "deny", + "typescript/no-non-null-asserted-nullish-coalescing": "deny", + "typescript/no-non-null-asserted-optional-chain": "deny", + "typescript/no-non-null-assertion": "deny", + "typescript/no-redundant-type-constituents": "deny", + "typescript/no-this-alias": "deny", + "typescript/no-unnecessary-boolean-literal-compare": "deny", + "typescript/no-unnecessary-parameter-property-assignment": "deny", + "typescript/no-unnecessary-template-expression": "deny", + "typescript/no-unnecessary-type-arguments": "deny", + "typescript/no-unnecessary-type-assertion": "deny", + "typescript/no-unnecessary-type-constraint": "deny", + "typescript/no-unsafe-declaration-merging": "deny", + "typescript/no-unsafe-enum-comparison": "deny", + "typescript/no-unsafe-type-assertion": "deny", + "typescript/no-unsafe-unary-minus": "deny", + "typescript/no-useless-empty-export": "deny", + "typescript/no-wrapper-object-types": "deny", + "typescript/prefer-as-const": "deny", + "typescript/prefer-literal-enum-member": "deny", + "typescript/prefer-namespace-keyword": "deny", + "typescript/prefer-reduce-type-parameter": "deny", + "typescript/prefer-return-this-type": "deny", + "typescript/require-array-sort-compare": "deny", + "typescript/restrict-template-expressions": "deny", + "typescript/triple-slash-reference": "deny", + "typescript/unbound-method": "deny", + "typescript/unified-signatures": "deny", + "typescript/use-unknown-in-catch-callback-variable": "deny", + "unicode-bom": "deny", + "use-isnan": "deny", + "valid-typeof": "deny", + "yoda": "deny" + }, + "settings": { + "jsdoc": { + "augmentsExtendsReplacesDocs": false, + "exemptDestructuredRootsFromChecks": false, + "ignoreInternal": false, + "ignorePrivate": false, + "ignoreReplacesDocs": true, + "implementsReplacesDocs": false, + "overrideReplacesDocs": true, + "tagNamePreference": {} + }, + "jsx-a11y": { + "attributes": {}, + "components": {}, + "polymorphicPropName": null + }, + "next": { + "rootDir": [] + }, + "react": { + "componentWrapperFunctions": [], + "formComponents": [], + "linkComponents": [], + "version": null + }, + "vitest": { + "typecheck": false + } + } +} diff --git a/packages/oxlint-config/test/generated/recommended-final-config.json b/packages/oxlint-config/test/generated/recommended-final-config.json new file mode 100644 index 0000000..b51395f --- /dev/null +++ b/packages/oxlint-config/test/generated/recommended-final-config.json @@ -0,0 +1,556 @@ +{ + "categories": { + "correctness": "deny", + "suspicious": "deny" + }, + "env": { + "builtin": true + }, + "globals": {}, + "ignorePatterns": [], + "plugins": [ + "typescript", + "import" + ], + "rules": { + "array-callback-return": [ + "deny", + [ + { + "allowImplicit": true + } + ] + ], + "block-scoped-var": "deny", + "class-methods-use-this": "warn", + "constructor-super": "deny", + "default-case": "deny", + "default-case-last": "deny", + "default-param-last": "deny", + "eqeqeq": [ + "deny", + [ + "always", + { + "null": "ignore" + } + ] + ], + "for-direction": "deny", + "func-names": "warn", + "getter-return": [ + "deny", + [ + { + "allowImplicit": true + } + ] + ], + "import/default": "deny", + "import/max-dependencies": "allow", + "import/namespace": "deny", + "import/no-absolute-path": "deny", + "import/no-amd": "deny", + "import/no-cycle": [ + "deny", + [ + { + "ignoreExternal": true + } + ] + ], + "import/no-duplicates": "deny", + "import/no-dynamic-require": "deny", + "import/no-empty-named-blocks": "allow", + "import/no-mutable-exports": "deny", + "import/no-named-as-default": "deny", + "import/no-named-as-default-member": "allow", + "import/no-named-default": "deny", + "import/no-self-import": "deny", + "import/no-unassigned-import": "deny", + "import/no-webpack-loader-syntax": "deny", + "new-cap": [ + "deny", + [ + { + "capIsNew": false, + "newIsCap": true + } + ] + ], + "no-alert": "deny", + "no-async-promise-executor": "deny", + "no-await-in-loop": "deny", + "no-bitwise": "deny", + "no-caller": "deny", + "no-class-assign": "deny", + "no-compare-neg-zero": "deny", + "no-cond-assign": "deny", + "no-console": "warn", + "no-const-assign": "deny", + "no-constant-binary-expression": "deny", + "no-constant-condition": "deny", + "no-continue": "deny", + "no-control-regex": "deny", + "no-debugger": "deny", + "no-delete-var": "deny", + "no-dupe-class-members": "deny", + "no-dupe-else-if": "deny", + "no-dupe-keys": "deny", + "no-duplicate-case": "deny", + "no-else-return": [ + "deny", + [ + { + "allowElseIf": false + } + ] + ], + "no-empty-character-class": "deny", + "no-empty-function": "allow", + "no-empty-pattern": "deny", + "no-empty-static-block": "deny", + "no-eval": "deny", + "no-ex-assign": "deny", + "no-extend-native": "deny", + "no-extra-bind": "deny", + "no-extra-boolean-cast": "deny", + "no-extra-label": "deny", + "no-func-assign": "deny", + "no-global-assign": "deny", + "no-import-assign": "deny", + "no-invalid-regexp": "deny", + "no-irregular-whitespace": "deny", + "no-iterator": "deny", + "no-label-var": "deny", + "no-labels": "deny", + "no-lone-blocks": "deny", + "no-loss-of-precision": "deny", + "no-misleading-character-class": "deny", + "no-multi-assign": "deny", + "no-multi-str": "deny", + "no-nested-ternary": "deny", + "no-new": "deny", + "no-new-func": "deny", + "no-new-native-nonconstructor": "deny", + "no-nonoctal-decimal-escape": "deny", + "no-obj-calls": "deny", + "no-param-reassign": [ + "deny", + [ + { + "ignorePropertyModificationsFor": [ + "prev", + "acc", + "accumulator", + "e", + "ctx", + "context", + "req", + "request", + "res", + "response", + "$scope", + "staticContext", + "sharedState", + "state" + ], + "props": true + } + ] + ], + "no-proto": "deny", + "no-regex-spaces": "deny", + "no-restricted-globals": [ + "deny", + [ + { + "message": "Use Number.isFinite instead", + "name": "isFinite" + }, + { + "message": "Use Number.isNaN instead", + "name": "isNaN" + }, + { + "message": "Use window.addEventListener instead", + "name": "addEventListener" + }, + { + "message": "Use window.blur instead", + "name": "blur" + }, + { + "message": "Use window.close instead", + "name": "close" + }, + { + "message": "Use window.closed instead", + "name": "closed" + }, + { + "message": "Use window.confirm instead", + "name": "confirm" + }, + { + "message": "Use window.defaultStatus instead", + "name": "defaultStatus" + }, + { + "message": "Use window.defaultstatus instead", + "name": "defaultstatus" + }, + { + "message": "Use window.error instead", + "name": "error" + }, + { + "message": "Use window.event instead", + "name": "event" + }, + { + "message": "Use window.find instead", + "name": "find" + }, + { + "message": "Use window.focus instead", + "name": "focus" + }, + { + "message": "Use window.frameElement instead", + "name": "frameElement" + }, + { + "message": "Use window.frames instead", + "name": "frames" + }, + { + "message": "Use window.history instead", + "name": "history" + }, + { + "message": "Use window.innerHeight instead", + "name": "innerHeight" + }, + { + "message": "Use window.innerWidth instead", + "name": "innerWidth" + }, + { + "message": "Use window.length instead", + "name": "length" + }, + { + "message": "Use window.localStorage instead", + "name": "localStorage" + }, + { + "message": "Use window.location instead", + "name": "location" + }, + { + "message": "Use window.menubar instead", + "name": "menubar" + }, + { + "message": "Use window.moveBy instead", + "name": "moveBy" + }, + { + "message": "Use window.moveTo instead", + "name": "moveTo" + }, + { + "message": "Use window.name instead", + "name": "name" + }, + { + "message": "Use window.onblur instead", + "name": "onblur" + }, + { + "message": "Use window.onerror instead", + "name": "onerror" + }, + { + "message": "Use window.onfocus instead", + "name": "onfocus" + }, + { + "message": "Use window.onload instead", + "name": "onload" + }, + { + "message": "Use window.onresize instead", + "name": "onresize" + }, + { + "message": "Use window.onunload instead", + "name": "onunload" + }, + { + "message": "Use window.open instead", + "name": "open" + }, + { + "message": "Use window.opener instead", + "name": "opener" + }, + { + "message": "Use window.opera instead", + "name": "opera" + }, + { + "message": "Use window.outerHeight instead", + "name": "outerHeight" + }, + { + "message": "Use window.outerWidth instead", + "name": "outerWidth" + }, + { + "message": "Use window.pageXOffset instead", + "name": "pageXOffset" + }, + { + "message": "Use window.pageYOffset instead", + "name": "pageYOffset" + }, + { + "message": "Use window.parent instead", + "name": "parent" + }, + { + "message": "Use window.print instead", + "name": "print" + }, + { + "message": "Use window.removeEventListener instead", + "name": "removeEventListener" + }, + { + "message": "Use window.resizeBy instead", + "name": "resizeBy" + }, + { + "message": "Use window.resizeTo instead", + "name": "resizeTo" + }, + { + "message": "Use window.screen instead", + "name": "screen" + }, + { + "message": "Use window.screenLeft instead", + "name": "screenLeft" + }, + { + "message": "Use window.screenTop instead", + "name": "screenTop" + }, + { + "message": "Use window.screenX instead", + "name": "screenX" + }, + { + "message": "Use window.screenY instead", + "name": "screenY" + }, + { + "message": "Use window.scroll instead", + "name": "scroll" + }, + { + "message": "Use window.scrollbars instead", + "name": "scrollbars" + }, + { + "message": "Use window.scrollBy instead", + "name": "scrollBy" + }, + { + "message": "Use window.scrollTo instead", + "name": "scrollTo" + }, + { + "message": "Use window.scrollX instead", + "name": "scrollX" + }, + { + "message": "Use window.scrollY instead", + "name": "scrollY" + }, + { + "message": "Use window.self instead", + "name": "self" + }, + { + "message": "Use window.sessionStorage instead", + "name": "sessionStorage" + }, + { + "message": "Use window.status instead", + "name": "status" + }, + { + "message": "Use window.statusbar instead", + "name": "statusbar" + }, + { + "message": "Use window.stop instead", + "name": "stop" + }, + { + "message": "Use window.toolbar instead", + "name": "toolbar" + }, + { + "message": "Use window.top instead", + "name": "top" + } + ] + ], + "no-restricted-imports": "allow", + "no-return-assign": [ + "deny", + [ + "always" + ] + ], + "no-script-url": "deny", + "no-self-assign": "deny", + "no-sequences": "deny", + "no-setter-return": "deny", + "no-shadow": "deny", + "no-shadow-restricted-names": "deny", + "no-sparse-arrays": "deny", + "no-template-curly-in-string": "deny", + "no-this-before-super": "deny", + "no-unassigned-vars": "deny", + "no-unexpected-multiline": "deny", + "no-unmodified-loop-condition": "deny", + "no-unneeded-ternary": "deny", + "no-unreachable": "deny", + "no-unsafe-finally": "deny", + "no-unsafe-negation": "deny", + "no-unsafe-optional-chaining": "deny", + "no-unused-expressions": "deny", + "no-unused-labels": "deny", + "no-unused-private-class-members": "deny", + "no-unused-vars": "deny", + "no-useless-backreference": "deny", + "no-useless-call": "deny", + "no-useless-catch": "deny", + "no-useless-concat": "deny", + "no-useless-constructor": "deny", + "no-useless-escape": "deny", + "no-useless-rename": "deny", + "no-var": "deny", + "no-with": "deny", + "operator-assignment": "deny", + "prefer-const": "deny", + "prefer-exponentiation-operator": "deny", + "prefer-numeric-literals": "deny", + "prefer-object-has-own": "deny", + "prefer-object-spread": "deny", + "prefer-promise-reject-errors": [ + "deny", + [ + { + "allowEmptyReject": true + } + ] + ], + "prefer-rest-params": "deny", + "prefer-spread": "deny", + "prefer-template": "deny", + "preserve-caught-error": "deny", + "require-await": "warn", + "require-yield": "deny", + "typescript/await-thenable": "deny", + "typescript/consistent-type-exports": "deny", + "typescript/consistent-type-imports": "deny", + "typescript/no-array-delete": "deny", + "typescript/no-base-to-string": "deny", + "typescript/no-confusing-non-null-assertion": "deny", + "typescript/no-duplicate-enum-values": "deny", + "typescript/no-duplicate-type-constituents": "deny", + "typescript/no-dynamic-delete": "deny", + "typescript/no-extra-non-null-assertion": "deny", + "typescript/no-extraneous-class": "deny", + "typescript/no-floating-promises": "deny", + "typescript/no-for-in-array": "deny", + "typescript/no-implied-eval": "deny", + "typescript/no-import-type-side-effects": "deny", + "typescript/no-invalid-void-type": "deny", + "typescript/no-meaningless-void-operator": "deny", + "typescript/no-misused-new": "deny", + "typescript/no-misused-spread": "deny", + "typescript/no-non-null-asserted-nullish-coalescing": "deny", + "typescript/no-non-null-asserted-optional-chain": "deny", + "typescript/no-non-null-assertion": "deny", + "typescript/no-redundant-type-constituents": "deny", + "typescript/no-this-alias": "deny", + "typescript/no-unnecessary-boolean-literal-compare": "deny", + "typescript/no-unnecessary-parameter-property-assignment": "deny", + "typescript/no-unnecessary-template-expression": "deny", + "typescript/no-unnecessary-type-arguments": "deny", + "typescript/no-unnecessary-type-assertion": "deny", + "typescript/no-unnecessary-type-constraint": "deny", + "typescript/no-unsafe-declaration-merging": "deny", + "typescript/no-unsafe-enum-comparison": "deny", + "typescript/no-unsafe-type-assertion": "deny", + "typescript/no-unsafe-unary-minus": "deny", + "typescript/no-useless-empty-export": "deny", + "typescript/no-wrapper-object-types": "deny", + "typescript/prefer-as-const": "deny", + "typescript/prefer-literal-enum-member": "deny", + "typescript/prefer-namespace-keyword": "deny", + "typescript/prefer-reduce-type-parameter": "deny", + "typescript/prefer-return-this-type": "deny", + "typescript/require-array-sort-compare": "deny", + "typescript/restrict-template-expressions": "deny", + "typescript/triple-slash-reference": "deny", + "typescript/unbound-method": "deny", + "typescript/unified-signatures": "deny", + "typescript/use-unknown-in-catch-callback-variable": "deny", + "unicode-bom": [ + "deny", + [ + "never" + ] + ], + "use-isnan": "deny", + "valid-typeof": "deny", + "yoda": "deny" + }, + "settings": { + "jsdoc": { + "augmentsExtendsReplacesDocs": false, + "exemptDestructuredRootsFromChecks": false, + "ignoreInternal": false, + "ignorePrivate": false, + "ignoreReplacesDocs": true, + "implementsReplacesDocs": false, + "overrideReplacesDocs": true, + "tagNamePreference": {} + }, + "jsx-a11y": { + "attributes": {}, + "components": {}, + "polymorphicPropName": null + }, + "next": { + "rootDir": [] + }, + "react": { + "componentWrapperFunctions": [], + "formComponents": [], + "linkComponents": [], + "version": null + }, + "vitest": { + "typecheck": false + } + } +} diff --git a/packages/oxlint-config/test/generated/vitest-final-config.json b/packages/oxlint-config/test/generated/vitest-final-config.json new file mode 100644 index 0000000..3cf8dcd --- /dev/null +++ b/packages/oxlint-config/test/generated/vitest-final-config.json @@ -0,0 +1,238 @@ +{ + "categories": {}, + "env": { + "builtin": true + }, + "globals": {}, + "ignorePatterns": [], + "plugins": [ + "typescript", + "import", + "vitest" + ], + "rules": { + "array-callback-return": "deny", + "block-scoped-var": "deny", + "class-methods-use-this": "warn", + "constructor-super": "deny", + "default-case": "deny", + "default-case-last": "deny", + "default-param-last": "deny", + "eqeqeq": "deny", + "for-direction": "deny", + "func-names": "warn", + "getter-return": "deny", + "import/default": "deny", + "import/max-dependencies": "allow", + "import/namespace": "deny", + "import/no-absolute-path": "deny", + "import/no-amd": "deny", + "import/no-cycle": "deny", + "import/no-duplicates": "deny", + "import/no-dynamic-require": "deny", + "import/no-empty-named-blocks": "allow", + "import/no-mutable-exports": "deny", + "import/no-named-as-default": "deny", + "import/no-named-as-default-member": "allow", + "import/no-named-default": "deny", + "import/no-self-import": "deny", + "import/no-unassigned-import": "deny", + "import/no-webpack-loader-syntax": "deny", + "jest/expect-expect": "deny", + "jest/no-commented-out-tests": "deny", + "jest/no-conditional-expect": "deny", + "jest/no-disabled-tests": "deny", + "jest/no-export": "deny", + "jest/no-focused-tests": "deny", + "jest/no-standalone-expect": "deny", + "jest/require-to-throw-message": "deny", + "jest/valid-describe-callback": "deny", + "jest/valid-expect": "deny", + "jest/valid-title": "deny", + "new-cap": "deny", + "no-alert": "deny", + "no-async-promise-executor": "deny", + "no-await-in-loop": "deny", + "no-bitwise": "deny", + "no-caller": "deny", + "no-class-assign": "deny", + "no-compare-neg-zero": "deny", + "no-cond-assign": "deny", + "no-console": "allow", + "no-const-assign": "deny", + "no-constant-binary-expression": "deny", + "no-constant-condition": "deny", + "no-continue": "deny", + "no-control-regex": "deny", + "no-debugger": "deny", + "no-delete-var": "deny", + "no-dupe-class-members": "deny", + "no-dupe-else-if": "deny", + "no-dupe-keys": "deny", + "no-duplicate-case": "deny", + "no-else-return": "deny", + "no-empty-character-class": "deny", + "no-empty-function": "allow", + "no-empty-pattern": "deny", + "no-empty-static-block": "deny", + "no-eval": "deny", + "no-ex-assign": "deny", + "no-extend-native": "deny", + "no-extra-bind": "deny", + "no-extra-boolean-cast": "deny", + "no-extra-label": "deny", + "no-func-assign": "deny", + "no-global-assign": "deny", + "no-import-assign": "deny", + "no-invalid-regexp": "deny", + "no-irregular-whitespace": "deny", + "no-iterator": "deny", + "no-label-var": "deny", + "no-labels": "deny", + "no-lone-blocks": "deny", + "no-loss-of-precision": "deny", + "no-misleading-character-class": "deny", + "no-multi-assign": "deny", + "no-multi-str": "deny", + "no-nested-ternary": "deny", + "no-new": "deny", + "no-new-func": "deny", + "no-new-native-nonconstructor": "deny", + "no-nonoctal-decimal-escape": "deny", + "no-obj-calls": "deny", + "no-param-reassign": "deny", + "no-proto": "deny", + "no-regex-spaces": "deny", + "no-restricted-globals": "deny", + "no-restricted-imports": "allow", + "no-return-assign": "deny", + "no-script-url": "deny", + "no-self-assign": "deny", + "no-sequences": "deny", + "no-setter-return": "deny", + "no-shadow": "deny", + "no-shadow-restricted-names": "deny", + "no-sparse-arrays": "deny", + "no-template-curly-in-string": "deny", + "no-this-before-super": "deny", + "no-unassigned-vars": "deny", + "no-unexpected-multiline": "deny", + "no-unmodified-loop-condition": "deny", + "no-unneeded-ternary": "deny", + "no-unreachable": "deny", + "no-unsafe-finally": "deny", + "no-unsafe-negation": "deny", + "no-unsafe-optional-chaining": "deny", + "no-unused-expressions": "deny", + "no-unused-labels": "deny", + "no-unused-private-class-members": "deny", + "no-unused-vars": "deny", + "no-useless-backreference": "deny", + "no-useless-call": "deny", + "no-useless-catch": "deny", + "no-useless-concat": "deny", + "no-useless-constructor": "deny", + "no-useless-escape": "deny", + "no-useless-rename": "deny", + "no-var": "deny", + "no-with": "deny", + "operator-assignment": "deny", + "prefer-const": "deny", + "prefer-exponentiation-operator": "deny", + "prefer-numeric-literals": "deny", + "prefer-object-has-own": "deny", + "prefer-object-spread": "deny", + "prefer-promise-reject-errors": "deny", + "prefer-rest-params": "deny", + "prefer-spread": "deny", + "prefer-template": "deny", + "preserve-caught-error": "deny", + "require-await": "warn", + "require-yield": "deny", + "typescript/await-thenable": "deny", + "typescript/consistent-type-exports": "deny", + "typescript/consistent-type-imports": "deny", + "typescript/no-array-delete": "deny", + "typescript/no-base-to-string": "deny", + "typescript/no-confusing-non-null-assertion": "deny", + "typescript/no-duplicate-enum-values": "deny", + "typescript/no-duplicate-type-constituents": "deny", + "typescript/no-dynamic-delete": "deny", + "typescript/no-extra-non-null-assertion": "deny", + "typescript/no-extraneous-class": "deny", + "typescript/no-floating-promises": "deny", + "typescript/no-for-in-array": "deny", + "typescript/no-implied-eval": "deny", + "typescript/no-import-type-side-effects": "deny", + "typescript/no-invalid-void-type": "deny", + "typescript/no-meaningless-void-operator": "deny", + "typescript/no-misused-new": "deny", + "typescript/no-misused-spread": "deny", + "typescript/no-non-null-asserted-nullish-coalescing": "deny", + "typescript/no-non-null-asserted-optional-chain": "deny", + "typescript/no-non-null-assertion": "deny", + "typescript/no-redundant-type-constituents": "deny", + "typescript/no-this-alias": "deny", + "typescript/no-unnecessary-boolean-literal-compare": "deny", + "typescript/no-unnecessary-parameter-property-assignment": "deny", + "typescript/no-unnecessary-template-expression": "deny", + "typescript/no-unnecessary-type-arguments": "deny", + "typescript/no-unnecessary-type-assertion": "deny", + "typescript/no-unnecessary-type-constraint": "deny", + "typescript/no-unsafe-declaration-merging": "deny", + "typescript/no-unsafe-enum-comparison": "deny", + "typescript/no-unsafe-type-assertion": "deny", + "typescript/no-unsafe-unary-minus": "deny", + "typescript/no-useless-empty-export": "deny", + "typescript/no-wrapper-object-types": "deny", + "typescript/prefer-as-const": "deny", + "typescript/prefer-literal-enum-member": "deny", + "typescript/prefer-namespace-keyword": "deny", + "typescript/prefer-reduce-type-parameter": "deny", + "typescript/prefer-return-this-type": "deny", + "typescript/require-array-sort-compare": "deny", + "typescript/restrict-template-expressions": "deny", + "typescript/triple-slash-reference": "deny", + "typescript/unbound-method": "deny", + "typescript/unified-signatures": "deny", + "typescript/use-unknown-in-catch-callback-variable": "deny", + "unicode-bom": "deny", + "use-isnan": "deny", + "valid-typeof": "deny", + "vitest/consistent-each-for": "deny", + "vitest/hoisted-apis-on-top": "deny", + "vitest/no-conditional-tests": "deny", + "vitest/require-local-test-context-for-concurrent-snapshots": "deny", + "vitest/warn-todo": "deny", + "yoda": "deny" + }, + "settings": { + "jsdoc": { + "augmentsExtendsReplacesDocs": false, + "exemptDestructuredRootsFromChecks": false, + "ignoreInternal": false, + "ignorePrivate": false, + "ignoreReplacesDocs": true, + "implementsReplacesDocs": false, + "overrideReplacesDocs": true, + "tagNamePreference": {} + }, + "jsx-a11y": { + "attributes": {}, + "components": {}, + "polymorphicPropName": null + }, + "next": { + "rootDir": [] + }, + "react": { + "componentWrapperFunctions": [], + "formComponents": [], + "linkComponents": [], + "version": null + }, + "vitest": { + "typecheck": false + } + } +} diff --git a/packages/oxlint-config/test/update-configs.sh b/packages/oxlint-config/test/update-configs.sh new file mode 100755 index 0000000..1379db3 --- /dev/null +++ b/packages/oxlint-config/test/update-configs.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# update-configs.sh +# Regenerates the test/generated snapshot files and commits them if they changed. +# +# Run this after any change to a config preset: +# ./test/update-configs.sh + +set -euo pipefail + +cd "$(dirname "$0")/.." || exit 1 + +echo "Regenerating oxlint config snapshots..." +node test/generate-configs.js + +cd test || exit 1 + +if ! git diff --exit-code generated/; then + git add generated/ + git commit -m "chore(oxlint-config): update oxlint config snapshots" + git push + echo -e "\033[32mBranch has been updated with new config snapshots\033[0m" +else + echo -e "\033[32mNothing changed, snapshots are already up to date\033[0m" +fi diff --git a/packages/oxlint-config/test/verify-configs.sh b/packages/oxlint-config/test/verify-configs.sh new file mode 100755 index 0000000..13f5302 --- /dev/null +++ b/packages/oxlint-config/test/verify-configs.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# verify-configs.sh +# Regenerates snapshots in a temp copy and fails if they differ from committed. +# Intended for CI. +# +# Usage: ./test/verify-configs.sh + +set -euo pipefail + +cd "$(dirname "$0")/.." || exit 1 + +echo "Verifying oxlint config snapshots..." +node test/generate-configs.js + +cd test || exit 1 + +if ! git diff --exit-code generated/; then + echo -e "\033[31mSnapshot mismatch: the committed snapshots are out of date.\033[0m" + echo -e "\033[31mRun './test/update-configs.sh' locally and commit the result.\033[0m" + exit 1 +fi + +echo -e "\033[32mAll snapshots match, no diff found!\033[0m" diff --git a/packages/oxlint-config/vitest.js b/packages/oxlint-config/vitest.js new file mode 100644 index 0000000..f1df8d6 --- /dev/null +++ b/packages/oxlint-config/vitest.js @@ -0,0 +1,50 @@ +// @ts-check +// Qlik Vitest oxlint config +// Extends recommended with vitest plugin rules for test files. +// +// Typical usage – add an override to your project's .oxlintrc.json: +// +// { +// "extends": ["@qlik/oxlint-config/recommended.json"], +// "overrides": [ +// { +// "files": ["**/__tests__/**", "**/*.test.*", "**/*.spec.*"], +// "extends": ["@qlik/oxlint-config/vitest.json"] +// } +// ] +// } +// +// Or, in an oxlint.config.ts: +// +// import { defineConfig } from "oxlint"; +// import vitest from "@qlik/oxlint-config/vitest.js"; +// export default defineConfig({ extends: [vitest] }); + +import recommended from "./recommended.js"; + +/** @type {import("oxlint").OxlintConfig} */ +const config = { + extends: [recommended], + + plugins: ["typescript", "import", "vitest"], + + rules: { + // console output in tests is usually intentional (debugging assertions) + "no-console": "off", + + // test files don't export production code, so import restrictions are relaxed + "no-restricted-imports": "off", + + /* ------------------------------------------------------------------ */ + /* Vitest – correctness (override to make explicit) */ + /* ------------------------------------------------------------------ */ + + // conditional test logic masks bugs that should be fixed unconditionally + "vitest/no-conditional-tests": "error", + + // TODO-marked tests should surface as warnings so they aren't forgotten + "vitest/warn-todo": "error", + }, +}; + +export default config; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d6edeed..35996f2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -111,6 +111,12 @@ importers: specifier: ^4.0.18 version: 4.0.18(@types/node@25.3.0) + packages/oxlint-config: + devDependencies: + oxlint: + specifier: ^1.50.0 + version: 1.50.0 + packages/prettier-config: dependencies: prettier: @@ -583,6 +589,128 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@oxlint/binding-android-arm-eabi@1.50.0': + resolution: {integrity: sha512-G7MRGk/6NCe+L8ntonRdZP7IkBfEpiZ/he3buLK6JkLgMHgJShXZ+BeOwADmspXez7U7F7L1Anf4xLSkLHiGTg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxlint/binding-android-arm64@1.50.0': + resolution: {integrity: sha512-GeSuMoJWCVpovJi/e3xDSNgjeR8WEZ6MCXL6EtPiCIM2NTzv7LbflARINTXTJy2oFBYyvdf/l2PwHzYo6EdXvg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxlint/binding-darwin-arm64@1.50.0': + resolution: {integrity: sha512-w3SY5YtxGnxCHPJ8Twl3KmS9oja1gERYk3AMoZ7Hv8P43ZtB6HVfs02TxvarxfL214Tm3uzvc2vn+DhtUNeKnw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxlint/binding-darwin-x64@1.50.0': + resolution: {integrity: sha512-hNfogDqy7tvmllXKBSlHo6k5x7dhTUVOHbMSE15CCAcXzmqf5883aPvBYPOq9AE7DpDUQUZ1kVE22YbiGW+tuw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxlint/binding-freebsd-x64@1.50.0': + resolution: {integrity: sha512-ykZevOWEyu0nsxolA911ucxpEv0ahw8jfEeGWOwwb/VPoE4xoexuTOAiPNlWZNJqANlJl7yp8OyzCtXTUAxotw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxlint/binding-linux-arm-gnueabihf@1.50.0': + resolution: {integrity: sha512-hif3iDk7vo5GGJ4OLCCZAf2vjnU9FztGw4L0MbQL0M2iY9LKFtDMMiQAHmkF0PQGQMVbTYtPdXCLKVgdkiqWXQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm-musleabihf@1.50.0': + resolution: {integrity: sha512-dVp9iSssiGAnTNey2Ruf6xUaQhdnvcFOJyRWd/mu5o2jVbFK15E5fbWGeFRfmuobu5QXuROtFga44+7DOS3PLg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm64-gnu@1.50.0': + resolution: {integrity: sha512-1cT7yz2HA910CKA9NkH1ZJo50vTtmND2fkoW1oyiSb0j6WvNtJ0Wx2zoySfXWc/c+7HFoqRK5AbEoL41LOn9oA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-arm64-musl@1.50.0': + resolution: {integrity: sha512-++B3k/HEPFVlj89cOz8kWfQccMZB/aWL9AhsW7jPIkG++63Mpwb2cE9XOEsd0PATbIan78k2Gky+09uWM1d/gQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-ppc64-gnu@1.50.0': + resolution: {integrity: sha512-Z9b/KpFMkx66w3gVBqjIC1AJBTZAGoI9+U+K5L4QM0CB/G0JSNC1es9b3Y0Vcrlvtdn8A+IQTkYjd/Q0uCSaZw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-gnu@1.50.0': + resolution: {integrity: sha512-jvmuIw8wRSohsQlFNIST5uUwkEtEJmOQYr33bf/K2FrFPXHhM4KqGekI3ShYJemFS/gARVacQFgBzzJKCAyJjg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-musl@1.50.0': + resolution: {integrity: sha512-x+UrN47oYNh90nmAAyql8eQaaRpHbDPu5guasDg10+OpszUQ3/1+1J6zFMmV4xfIEgTcUXG/oI5fxJhF4eWCNA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-s390x-gnu@1.50.0': + resolution: {integrity: sha512-i/JLi2ljLUIVfekMj4ISmdt+Hn11wzYUdRRrkVUYsCWw7zAy5xV7X9iA+KMyM156LTFympa7s3oKBjuCLoTAUQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-gnu@1.50.0': + resolution: {integrity: sha512-/C7brhn6c6UUPccgSPCcpLQXcp+xKIW/3sji/5VZ8/OItL3tQ2U7KalHz887UxxSQeEOmd1kY6lrpuwFnmNqOA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-musl@1.50.0': + resolution: {integrity: sha512-oDR1f+bGOYU8LfgtEW8XtotWGB63ghtcxk5Jm6IDTCk++rTA/IRMsjOid2iMd+1bW+nP9Mdsmcdc7VbPD3+iyQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxlint/binding-openharmony-arm64@1.50.0': + resolution: {integrity: sha512-4CmRGPp5UpvXyu4jjP9Tey/SrXDQLRvZXm4pb4vdZBxAzbFZkCyh0KyRy4txld/kZKTJlW4TO8N1JKrNEk+mWw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxlint/binding-win32-arm64-msvc@1.50.0': + resolution: {integrity: sha512-Fq0M6vsGcFsSfeuWAACDhd5KJrO85ckbEfe1EGuBj+KPyJz7KeWte2fSFrFGmNKNXyhEMyx4tbgxiWRujBM2KQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxlint/binding-win32-ia32-msvc@1.50.0': + resolution: {integrity: sha512-qTdWR9KwY/vxJGhHVIZG2eBOhidOQvOwzDxnX+jhW/zIVacal1nAhR8GLkiywW8BIFDkQKXo/zOfT+/DY+ns/w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxlint/binding-win32-x64-msvc@1.50.0': + resolution: {integrity: sha512-682t7npLC4G2Ca+iNlI9fhAKTcFPYYXJjwoa88H4q+u5HHHlsnL/gHULapX3iqp+A8FIJbgdylL5KMYo2LaluQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@reteps/dockerfmt@0.3.6': resolution: {integrity: sha512-Tb5wIMvBf/nLejTQ61krK644/CEMB/cpiaIFXqGApfGqO3GwcR3qnI0DbmkFVCl2OyEp8LnLX3EkucoL0+tbFg==} engines: {node: ^v12.20.0 || ^14.13.0 || >=16.0.0} @@ -1951,6 +2079,16 @@ packages: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} + oxlint@1.50.0: + resolution: {integrity: sha512-iSJ4IZEICBma8cZX7kxIIz9PzsYLF2FaLAYN6RKu7VwRVKdu7RIgpP99bTZaGl//Yao7fsaGZLSEo5xBrI5ReQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + oxlint-tsgolint: '>=0.14.1' + peerDependenciesMeta: + oxlint-tsgolint: + optional: true + p-filter@2.1.0: resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} engines: {node: '>=8'} @@ -3033,6 +3171,63 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 + '@oxlint/binding-android-arm-eabi@1.50.0': + optional: true + + '@oxlint/binding-android-arm64@1.50.0': + optional: true + + '@oxlint/binding-darwin-arm64@1.50.0': + optional: true + + '@oxlint/binding-darwin-x64@1.50.0': + optional: true + + '@oxlint/binding-freebsd-x64@1.50.0': + optional: true + + '@oxlint/binding-linux-arm-gnueabihf@1.50.0': + optional: true + + '@oxlint/binding-linux-arm-musleabihf@1.50.0': + optional: true + + '@oxlint/binding-linux-arm64-gnu@1.50.0': + optional: true + + '@oxlint/binding-linux-arm64-musl@1.50.0': + optional: true + + '@oxlint/binding-linux-ppc64-gnu@1.50.0': + optional: true + + '@oxlint/binding-linux-riscv64-gnu@1.50.0': + optional: true + + '@oxlint/binding-linux-riscv64-musl@1.50.0': + optional: true + + '@oxlint/binding-linux-s390x-gnu@1.50.0': + optional: true + + '@oxlint/binding-linux-x64-gnu@1.50.0': + optional: true + + '@oxlint/binding-linux-x64-musl@1.50.0': + optional: true + + '@oxlint/binding-openharmony-arm64@1.50.0': + optional: true + + '@oxlint/binding-win32-arm64-msvc@1.50.0': + optional: true + + '@oxlint/binding-win32-ia32-msvc@1.50.0': + optional: true + + '@oxlint/binding-win32-x64-msvc@1.50.0': + optional: true + '@reteps/dockerfmt@0.3.6': {} '@rollup/rollup-android-arm-eabi@4.59.0': @@ -4477,6 +4672,28 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 + oxlint@1.50.0: + optionalDependencies: + '@oxlint/binding-android-arm-eabi': 1.50.0 + '@oxlint/binding-android-arm64': 1.50.0 + '@oxlint/binding-darwin-arm64': 1.50.0 + '@oxlint/binding-darwin-x64': 1.50.0 + '@oxlint/binding-freebsd-x64': 1.50.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.50.0 + '@oxlint/binding-linux-arm-musleabihf': 1.50.0 + '@oxlint/binding-linux-arm64-gnu': 1.50.0 + '@oxlint/binding-linux-arm64-musl': 1.50.0 + '@oxlint/binding-linux-ppc64-gnu': 1.50.0 + '@oxlint/binding-linux-riscv64-gnu': 1.50.0 + '@oxlint/binding-linux-riscv64-musl': 1.50.0 + '@oxlint/binding-linux-s390x-gnu': 1.50.0 + '@oxlint/binding-linux-x64-gnu': 1.50.0 + '@oxlint/binding-linux-x64-musl': 1.50.0 + '@oxlint/binding-openharmony-arm64': 1.50.0 + '@oxlint/binding-win32-arm64-msvc': 1.50.0 + '@oxlint/binding-win32-ia32-msvc': 1.50.0 + '@oxlint/binding-win32-x64-msvc': 1.50.0 + p-filter@2.1.0: dependencies: p-map: 2.1.0