Files
Stirling-PDF/frontend/oxlint.config.ts
T
James Brunton cfaf777f2b Fix more any type usages in frontend code (#7334)
# Description of Changes
Continued effort towards removing all uses of the any type in our
frontend code (last PR was #7326). This PR fixes 7 more folders and
removes them from the exclude list. All of them were localised within
the folder in the exclude list so again were pretty easy to fix.
2026-08-10 10:40:47 +00:00

417 lines
14 KiB
TypeScript

import { defineConfig, type OxlintGlobals } from "oxlint";
// Glob for all editor app source, and the two layers that need their own
// import scope. `no-restricted-imports` is repeated per scope on purpose:
// oxlint REPLACES (does not merge) a rule across matching overrides, so each
// scope must restate the full set of bans that apply to it.
const APP_SOURCE = "editor/src/**/*.{js,mjs,jsx,ts,tsx}";
const DESKTOP_SOURCE = "editor/src/desktop/**/*.{js,mjs,jsx,ts,tsx}";
const CLOUD_SOURCE = "editor/src/cloud/**/*.{js,mjs,jsx,ts,tsx}";
// Shared import-ban building blocks -----------------------------------------
const aliasOverRelative = {
regex: "^\\.",
message:
"Use a workspace alias (@app/* for editor, @portal/* for portal) instead of relative imports.",
};
const aliasOverSrc = {
regex: "^src/",
message: "Use a workspace alias instead of absolute src/ imports.",
};
const noTauriOutsideDesktop = {
regex: "^@tauri-apps/",
message:
"Tauri APIs are desktop-only. Review frontend/editor/DeveloperGuide.md for structure advice.",
};
const cloudNoTauri = {
regex: "^@tauri-apps/",
message:
"cloud/ must stay platform-portable. Tauri APIs are desktop-only — reach native features via an @app/* seam (e.g. @app/platform/openExternal).",
};
const cloudNoSupabase = {
regex: "^@supabase/",
message:
"cloud/ must stay platform-portable. Reach Supabase via an @app/* seam (e.g. @app/auth/supabase, @app/auth/session) provided per-platform in saas/ and desktop/.",
};
// Shared-DS Button/SegmentedControl/Chip family must come from @app/ui, not
// Mantine. (The raw-<button> ban from the ESLint config used
// no-restricted-syntax, which oxlint does not implement, so it is dropped.)
const mantineDsPaths = [
{
name: "@mantine/core",
importNames: [
"Button",
"ActionIcon",
"UnstyledButton",
"CloseButton",
"FileButton",
],
message:
'Use the shared Button (@app/ui/Button) instead of the Mantine button family. variant=primary|secondary|tertiary, accent=default|neutral|brand|ai|premium|danger|success|warning; an icon-only button is `<Button leftSection={…} aria-label="…" />`. If no variant fits, extend the shared Button rather than importing Mantine.',
},
{
name: "@mantine/core",
importNames: ["SegmentedControl"],
message:
"Use the shared SegmentedControl (@app/ui/SegmentedControl) instead of Mantine's.",
},
{
name: "@mantine/core",
importNames: ["Chip", "Pill"],
message:
"Use the shared Chip (@app/ui/Chip) instead of Mantine's Chip/Pill.",
},
];
// Modern JS globals not yet in oxlint's builtin env.
const modernGlobals: OxlintGlobals = {
AsyncDisposableStack: "readonly",
DisposableStack: "readonly",
SuppressedError: "readonly",
};
// Folders not yet conformant to the stricter no-explicit-any rule
const noExplicitAnyExcludes = [
"editor/src/core/components/shared/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/components/shared/config/configSections/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/components/tools/addStamp/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/components/tools/automate/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/components/viewer/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/contexts/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/contexts/viewer/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/hooks/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/services/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/tools/annotate/useAnnotationSelection.ts",
"editor/src/core/types/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/utils/*.{js,mjs,jsx,ts,tsx}",
];
export default defineConfig({
plugins: ["typescript", "import"],
categories: {
correctness: "off",
},
env: {
builtin: true,
},
ignorePatterns: [
"dist",
"dist-portal",
"node_modules",
"playwright-report",
"storybook-static",
"test-results",
"editor/dist",
"editor/public",
"editor/src-tauri",
"editor/playwright-report",
"editor/test-results",
],
rules: {
"constructor-super": "error",
"for-direction": "error",
"getter-return": "error",
"no-async-promise-executor": "error",
"no-case-declarations": "error",
"no-class-assign": "error",
"no-compare-neg-zero": "error",
"no-cond-assign": "error",
"no-const-assign": "error",
"no-constant-binary-expression": "error",
"no-constant-condition": "error",
"no-control-regex": "error",
"no-debugger": "error",
"no-delete-var": "error",
"no-dupe-class-members": "error",
"no-dupe-else-if": "error",
"no-dupe-keys": "error",
"no-duplicate-case": "error",
"no-empty": "error",
"no-empty-character-class": "error",
"no-empty-pattern": "error",
"no-empty-static-block": "error",
"no-ex-assign": "error",
"no-extra-boolean-cast": "error",
"no-fallthrough": "error",
"no-func-assign": "error",
"no-global-assign": "error",
"no-import-assign": "error",
"no-invalid-regexp": "error",
"no-irregular-whitespace": "error",
"no-loss-of-precision": "error",
"no-misleading-character-class": "error",
"no-new-native-nonconstructor": "error",
"no-nonoctal-decimal-escape": "error",
"no-obj-calls": "error",
"no-prototype-builtins": "error",
"no-redeclare": "error",
"no-regex-spaces": "error",
"no-self-assign": "error",
"no-setter-return": "error",
"no-shadow-restricted-names": "error",
"no-sparse-arrays": "error",
"no-this-before-super": "error",
"no-unassigned-vars": "error",
"no-unexpected-multiline": "error",
"no-unreachable": "error",
"no-unsafe-finally": "error",
"no-unsafe-negation": "error",
"no-unsafe-optional-chaining": "error",
"no-unused-labels": "error",
"no-unused-private-class-members": "error",
"no-unused-vars": [
"error",
{
args: "all",
argsIgnorePattern: "^_",
caughtErrors: "all",
caughtErrorsIgnorePattern: "^_",
destructuredArrayIgnorePattern: "^_",
varsIgnorePattern: "^_",
ignoreRestSiblings: true,
},
],
"no-useless-backreference": "error",
"no-useless-catch": "error",
"no-useless-escape": "error",
"no-with": "error",
"preserve-caught-error": "error",
"require-yield": "error",
"use-isnan": "error",
"valid-typeof": "error",
"no-array-constructor": "error",
"no-unused-expressions": "error",
"no-restricted-imports": [
"error",
{
patterns: [aliasOverRelative, aliasOverSrc],
},
],
"typescript/ban-ts-comment": "error",
"typescript/no-duplicate-enum-values": "error",
"typescript/no-empty-object-type": [
"error",
{
allowInterfaces: "with-single-extends",
},
],
"typescript/no-extra-non-null-assertion": "error",
"typescript/no-misused-new": "error",
"typescript/no-namespace": "error",
"typescript/no-non-null-asserted-optional-chain": "error",
"typescript/no-require-imports": "error",
"typescript/no-this-alias": "error",
"typescript/no-unnecessary-type-constraint": "error",
"typescript/no-unsafe-declaration-merging": "error",
"typescript/no-unsafe-function-type": "error",
"typescript/no-wrapper-object-types": "error",
"typescript/prefer-as-const": "error",
"typescript/prefer-namespace-keyword": "error",
"typescript/triple-slash-reference": "error",
},
overrides: [
{
// TS files: core JS rules superseded by the TypeScript compiler are turned
// off, and the TS-appropriate replacements are enabled. Mirrors
// typescript-eslint's recommended flat config.
files: ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"],
rules: {
"constructor-super": "off",
"getter-return": "off",
"no-class-assign": "off",
"no-const-assign": "off",
"no-dupe-class-members": "off",
"no-dupe-keys": "off",
"no-func-assign": "off",
"no-import-assign": "off",
"no-new-native-nonconstructor": "off",
"no-obj-calls": "off",
"no-redeclare": "off",
"no-setter-return": "off",
"no-this-before-super": "off",
"no-unreachable": "off",
"no-unsafe-negation": "off",
"no-var": "error",
"no-with": "off",
"prefer-const": "error",
"prefer-rest-params": "error",
"prefer-spread": "error",
},
},
{
// Browser globals for all editor app source.
files: [APP_SOURCE],
env: {
browser: true,
},
globals: modernGlobals,
},
{
// Node globals for build scripts, config files, and Storybook config.
files: [
"scripts/**/*.{js,ts,mjs,mts}",
"editor/scripts/**/*.{js,ts,mjs,mts}",
"editor/*.config.{js,ts,mjs}",
"*.config.{js,ts,mjs}",
".storybook/*.{js,ts,mjs,mts,tsx}",
],
globals: modernGlobals,
env: {
node: true,
},
},
{
// Editor app source (excluding desktop): ban relative/src imports, ban
// Tauri (desktop-only), and the shared-DS Mantine import ban.
files: [APP_SOURCE],
excludeFiles: [DESKTOP_SOURCE],
rules: {
"no-restricted-imports": [
"error",
{
patterns: [aliasOverRelative, aliasOverSrc, noTauriOutsideDesktop],
paths: mantineDsPaths,
},
],
},
},
{
// Desktop source: same DS import ban, but Tauri is allowed here (this is
// the only layer that may reach @tauri-apps/* directly).
files: [DESKTOP_SOURCE],
rules: {
"no-restricted-imports": [
"error",
{
patterns: [aliasOverRelative, aliasOverSrc],
paths: mantineDsPaths,
},
],
},
},
{
// The cloud/ layer is the SHARED hosted/SaaS experience consumed by BOTH
// the saas and desktop leaves, so it must stay platform-portable: no
// Supabase or Tauri directly, and no raw fetch/localStorage/sessionStorage
// - those arrive via @app/* seams that each leaf provides for its own
// platform. window.location and import.meta.env are banned below via the
// no-restricted-properties.
files: [CLOUD_SOURCE],
rules: {
"no-restricted-imports": [
"error",
{
patterns: [
aliasOverRelative,
aliasOverSrc,
cloudNoTauri,
cloudNoSupabase,
],
paths: mantineDsPaths,
},
],
"no-restricted-globals": [
"error",
{
name: "fetch",
message:
"cloud/ must not call raw fetch — use @app/services/apiClient so each platform supplies its own transport.",
},
{
name: "localStorage",
message:
"cloud/ must not touch localStorage — use an @app/* storage seam so desktop/web can differ.",
},
{
name: "sessionStorage",
message:
"cloud/ must not touch sessionStorage — use an @app/* storage seam so desktop/web can differ.",
},
],
"no-restricted-properties": [
"error",
{
object: "window",
property: "location",
message:
"cloud/ must not touch window.location - use an @app/* seam (e.g. @app/platform/openExternal) so desktop/web can differ.",
},
{
// Property-only: import.meta.env's object is a MetaProperty, which
// no-restricted-properties can't target, so this bans every `.env`
// read in cloud/ - which matches the intent (config comes via seams,
// never env). cloud/ has no other `.env` access today.
property: "env",
message:
"cloud/ must not read import.meta.env - config comes from @app/constants/app / @app/platform seams, not env.",
},
],
},
},
{
// Exempt from the shared-DS Mantine import ban (these layers may use
// Mantine directly): the shared DS itself wraps Mantine, stories/tests
// demo it, and prototypes are not shipped. Module-path bans still apply.
// The three named files are ARIA tablist/segmented controls that the
// ESLint config exempted from the (now-dropped) raw-<button> and Mantine
// rules. Comes after the scoped bans above so it wins for these files;
// desktop/cloud keep theirs.
files: [
"editor/src/core/ui/**/*.{js,mjs,jsx,ts,tsx}",
"editor/src/prototypes/**/*.{js,mjs,jsx,ts,tsx}",
"**/*.stories.{js,mjs,jsx,ts,tsx}",
"**/*.test.{js,mjs,jsx,ts,tsx}",
"editor/src/core/components/shared/FileSelectorPicker.tsx",
"editor/src/core/components/filesPage/FileManagerView.tsx",
"editor/src/core/pages/HomePage.tsx",
],
excludeFiles: [DESKTOP_SOURCE, CLOUD_SOURCE],
rules: {
"no-restricted-imports": [
"error",
{
patterns: [aliasOverRelative, aliasOverSrc, noTauriOutsideDesktop],
},
],
},
},
{
// Desktop test/story files: like every other *.test/*.stories file they
// are exempt from the shared-DS Mantine import ban; being desktop they also
// keep the Tauri allowance.
files: [
"editor/src/desktop/**/*.test.{js,mjs,jsx,ts,tsx}",
"editor/src/desktop/**/*.stories.{js,mjs,jsx,ts,tsx}",
],
rules: {
"no-restricted-imports": [
"error",
{
patterns: [aliasOverRelative, aliasOverSrc],
},
],
},
},
{
// Stricter no-explicit-any, enabled everywhere in the editor app EXCEPT
// the folders that are not yet conformant (migrated incrementally).
files: [APP_SOURCE],
excludeFiles: noExplicitAnyExcludes,
rules: {
"typescript/no-explicit-any": "error",
},
},
{
// Circular-import detection across the editor app source (the import
// plugin resolves @app/* and the other tsconfig path aliases). Replaces
// the previous dpdm pass.
files: ["editor/src/**/*.{ts,tsx}"],
rules: {
"import/no-cycle": "error",
},
},
],
});