@sister.software/oxlint-config 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +650 -0
- package/README.md +64 -0
- package/out/headers-plugin.d.ts +56 -0
- package/out/headers-plugin.d.ts.map +1 -0
- package/out/headers-plugin.js +85 -0
- package/out/headers-plugin.js.map +1 -0
- package/out/index.d.ts +46 -0
- package/out/index.d.ts.map +1 -0
- package/out/index.js +81 -0
- package/out/index.js.map +1 -0
- package/out/restrictions.d.ts +58 -0
- package/out/restrictions.d.ts.map +1 -0
- package/out/restrictions.js +194 -0
- package/out/restrictions.js.map +1 -0
- package/package.json +44 -0
- package/src/headers-plugin.ts +140 -0
- package/src/index.ts +122 -0
- package/src/restrictions.ts +253 -0
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sister.software/oxlint-config",
|
|
3
|
+
"version": "9.0.0",
|
|
4
|
+
"description": "Sister Software's oxlint config",
|
|
5
|
+
"license": "AGPL-3.0",
|
|
6
|
+
"author": "teffen@sister.software",
|
|
7
|
+
"files": [
|
|
8
|
+
"out/**/*.js",
|
|
9
|
+
"out/**/*.d.ts",
|
|
10
|
+
"out/**/*.map",
|
|
11
|
+
"src"
|
|
12
|
+
],
|
|
13
|
+
"type": "module",
|
|
14
|
+
"types": "./out/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
"./package.json": "./package.json",
|
|
17
|
+
".": {
|
|
18
|
+
"import": "./out/index.js",
|
|
19
|
+
"types": "./out/index.d.ts"
|
|
20
|
+
},
|
|
21
|
+
"./headers-plugin": {
|
|
22
|
+
"import": "./out/headers-plugin.js",
|
|
23
|
+
"types": "./out/headers-plugin.d.ts"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsc -p .",
|
|
31
|
+
"verify": "node scripts/verify-fixtures.mjs"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@sister.software/tsconfig": "workspace:*",
|
|
35
|
+
"@types/node": "^25.6.0",
|
|
36
|
+
"typescript": "^6.0.3"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"oxlint": "^1.71.0"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=24.0"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
* @file A JSDoc-aware license-header rule, authored as an oxlint JS plugin (ESLint v9-compatible
|
|
6
|
+
* API). It reports files missing the `@copyright`/`@license`/`@author` header and autofixes them
|
|
7
|
+
* while preserving any other JSDoc tags already present in the leading comment block.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
//#region Minimal ESLint-compatible plugin types (kept local to avoid coupling to oxlint's alpha
|
|
11
|
+
// internal type surface, which is not subject to semver).
|
|
12
|
+
|
|
13
|
+
interface Comment {
|
|
14
|
+
type: string
|
|
15
|
+
value: string
|
|
16
|
+
range: [number, number]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface SourceCode {
|
|
20
|
+
getText(): string
|
|
21
|
+
getAllComments(): Comment[]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface Fixer {
|
|
25
|
+
insertTextBeforeRange(range: [number, number], text: string): unknown
|
|
26
|
+
replaceTextRange(range: [number, number], text: string): unknown
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface RuleContext {
|
|
30
|
+
options: unknown[]
|
|
31
|
+
sourceCode?: SourceCode
|
|
32
|
+
getSourceCode?(): SourceCode
|
|
33
|
+
report(descriptor: { node: unknown; message: string; fix?(fixer: Fixer): unknown }): void
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface Rule {
|
|
37
|
+
meta: { name: string; type: string; fixable: "code"; schema: unknown[] }
|
|
38
|
+
create(context: RuleContext): Record<string, (node: unknown) => void>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface Plugin {
|
|
42
|
+
meta: { name: string }
|
|
43
|
+
rules: Record<string, Rule>
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
//#endregion
|
|
47
|
+
|
|
48
|
+
/** Options accepted by the header rule. */
|
|
49
|
+
export interface HeaderRuleOptions {
|
|
50
|
+
copyrightHolder?: string
|
|
51
|
+
spdxLicenseIdentifier?: string
|
|
52
|
+
author?: string
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const HEADER_TAG_PATTERN = /^@(copyright|license|author)\b/
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Splits a block comment's inner text into trimmed, non-empty content lines (one per JSDoc line), stripping the leading
|
|
59
|
+
* ` * ` decoration.
|
|
60
|
+
*/
|
|
61
|
+
function parseInnerLines(comment: Comment): string[] {
|
|
62
|
+
return comment.value
|
|
63
|
+
.split("\n")
|
|
64
|
+
.map((line) => line.replace(/^\s*\*?\s?/, "").replace(/\s+$/, ""))
|
|
65
|
+
.filter((line) => line.length > 0)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const rule: Rule = {
|
|
69
|
+
meta: {
|
|
70
|
+
name: "require-file-header",
|
|
71
|
+
type: "layout",
|
|
72
|
+
fixable: "code",
|
|
73
|
+
// Permissive schema: oxlint's alpha JS-plugin layer mangles complex option schemas, so we
|
|
74
|
+
// accept any object and read options defensively below.
|
|
75
|
+
schema: [{ type: "object", additionalProperties: true }],
|
|
76
|
+
},
|
|
77
|
+
create(context) {
|
|
78
|
+
const options = (context.options[0] ?? {}) as HeaderRuleOptions
|
|
79
|
+
const copyrightHolder = options.copyrightHolder ?? "Sister Software"
|
|
80
|
+
const spdxLicenseIdentifier = options.spdxLicenseIdentifier ?? "UNLICENSED"
|
|
81
|
+
const author = options.author ?? "Teffen Ellis, et al."
|
|
82
|
+
const headerLines = [`@copyright ${copyrightHolder}`, `@license ${spdxLicenseIdentifier}`, `@author ${author}`]
|
|
83
|
+
|
|
84
|
+
const sourceCode = context.sourceCode ?? context.getSourceCode!()
|
|
85
|
+
|
|
86
|
+
function buildBlock(preservedLines: string[]): string {
|
|
87
|
+
return "/**\n" + [...headerLines, ...preservedLines].map((line) => ` * ${line}`).join("\n") + "\n */"
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
Program(node) {
|
|
92
|
+
const text = sourceCode.getText()
|
|
93
|
+
// A leading shebang (`#!...`) must stay on line 1, so the header goes *after* it.
|
|
94
|
+
const shebang = /^#![^\n]*\n?/.exec(text)?.[0] ?? ""
|
|
95
|
+
|
|
96
|
+
const comments = sourceCode.getAllComments()
|
|
97
|
+
// The first *block* comment, skipping any shebang (which some ASTs surface as a leading
|
|
98
|
+
// comment). It counts as the header block only if nothing but the shebang precedes it.
|
|
99
|
+
const firstBlock = comments.find((comment) => comment.type === "Block")
|
|
100
|
+
const leading = firstBlock && text.slice(shebang.length, firstBlock.range[0]).trim() === "" ? firstBlock : null
|
|
101
|
+
|
|
102
|
+
if (leading) {
|
|
103
|
+
const existing = parseInnerLines(leading)
|
|
104
|
+
const missing = headerLines.filter((line) => !existing.includes(line))
|
|
105
|
+
if (missing.length === 0) return
|
|
106
|
+
|
|
107
|
+
const preserved = existing.filter((line) => !HEADER_TAG_PATTERN.test(line))
|
|
108
|
+
const block = buildBlock(preserved)
|
|
109
|
+
|
|
110
|
+
context.report({
|
|
111
|
+
node,
|
|
112
|
+
message: `File header missing or incorrect: ${missing.join(", ")}`,
|
|
113
|
+
fix(fixer) {
|
|
114
|
+
return fixer.replaceTextRange(leading.range, block)
|
|
115
|
+
},
|
|
116
|
+
})
|
|
117
|
+
} else {
|
|
118
|
+
const block = buildBlock([])
|
|
119
|
+
const insertAt = shebang.length
|
|
120
|
+
|
|
121
|
+
context.report({
|
|
122
|
+
node,
|
|
123
|
+
message: "File header missing (@copyright/@license/@author).",
|
|
124
|
+
fix(fixer) {
|
|
125
|
+
return fixer.insertTextBeforeRange([insertAt, insertAt], block + "\n\n")
|
|
126
|
+
},
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** The Sister Software oxlint header plugin. Registers the `sister-software/require-file-header` rule. */
|
|
135
|
+
const headerPlugin: Plugin = {
|
|
136
|
+
meta: { name: "sister-software" },
|
|
137
|
+
rules: { "require-file-header": rule },
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export default headerPlugin
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
* @file oxlint configuration factory for Sister Software projects.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createRuntimeOverrides } from "./restrictions.js"
|
|
9
|
+
|
|
10
|
+
export * from "./restrictions.js"
|
|
11
|
+
|
|
12
|
+
/** An oxlint configuration object, as consumed by `oxlint.config.ts` / `.oxlintrc.json`. */
|
|
13
|
+
export type OxlintConfig = Record<string, unknown>
|
|
14
|
+
|
|
15
|
+
/** Options for {@link createOxlintConfig}. */
|
|
16
|
+
export interface OxlintConfigOptions {
|
|
17
|
+
/** The package namespace whose runtime boundaries are enforced, e.g. `@sister.software`. */
|
|
18
|
+
packageNamespace?: string
|
|
19
|
+
/** The copyright holder stamped into file headers. */
|
|
20
|
+
copyrightHolder?: string
|
|
21
|
+
/** The SPDX license identifier stamped into file headers. */
|
|
22
|
+
spdxLicenseIdentifier?: string
|
|
23
|
+
/** The author stamped into file headers. */
|
|
24
|
+
author?: string
|
|
25
|
+
/** Enable oxlint's React plugin (off by default). */
|
|
26
|
+
react?: boolean
|
|
27
|
+
/** Enforce file headers via the bundled JS plugin (on by default). */
|
|
28
|
+
headers?: boolean
|
|
29
|
+
/** Override the default ignore patterns. */
|
|
30
|
+
ignorePatterns?: string[]
|
|
31
|
+
/** Extra config deep-merged last; an escape hatch for per-repo tweaks. */
|
|
32
|
+
overrides?: OxlintConfig
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Default ignore patterns for generated/build output. */
|
|
36
|
+
export const DefaultIgnorePatterns = [
|
|
37
|
+
"**/out",
|
|
38
|
+
"**/dist",
|
|
39
|
+
"**/.docusaurus/**",
|
|
40
|
+
"**/node_modules",
|
|
41
|
+
"**/coverage",
|
|
42
|
+
"**/storybook-static",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Builds the complete oxlint configuration for a Sister Software package.
|
|
47
|
+
*
|
|
48
|
+
* Consumers use it directly from an `oxlint.config.ts`:
|
|
49
|
+
*
|
|
50
|
+
* ```ts
|
|
51
|
+
* import { createOxlintConfig } from "@sister.software/oxlint-config"
|
|
52
|
+
* export default createOxlintConfig({ spdxLicenseIdentifier: "AGPL-3.0" })
|
|
53
|
+
* ```
|
|
54
|
+
*
|
|
55
|
+
* @param options Configuration options.
|
|
56
|
+
* @returns A complete oxlint config object (no `extends` required).
|
|
57
|
+
*/
|
|
58
|
+
export function createOxlintConfig(options: OxlintConfigOptions = {}): OxlintConfig {
|
|
59
|
+
const {
|
|
60
|
+
packageNamespace = "@sister.software",
|
|
61
|
+
copyrightHolder = "Sister Software",
|
|
62
|
+
spdxLicenseIdentifier = "UNLICENSED",
|
|
63
|
+
author = "Teffen Ellis, et al.",
|
|
64
|
+
react = false,
|
|
65
|
+
headers = true,
|
|
66
|
+
ignorePatterns = DefaultIgnorePatterns,
|
|
67
|
+
overrides = {},
|
|
68
|
+
} = options
|
|
69
|
+
|
|
70
|
+
const plugins = ["typescript", "unicorn", "oxc", ...(react ? ["react"] : [])]
|
|
71
|
+
|
|
72
|
+
const rules: Record<string, unknown> = {
|
|
73
|
+
// JavaScript
|
|
74
|
+
eqeqeq: ["error", "always", { null: "ignore" }],
|
|
75
|
+
"prefer-const": "warn",
|
|
76
|
+
"object-shorthand": ["warn", "always"],
|
|
77
|
+
"no-shadow": "off",
|
|
78
|
+
"no-undef": "off",
|
|
79
|
+
"no-unused-vars": [
|
|
80
|
+
"warn",
|
|
81
|
+
{
|
|
82
|
+
args: "all",
|
|
83
|
+
argsIgnorePattern: "^_",
|
|
84
|
+
caughtErrors: "all",
|
|
85
|
+
caughtErrorsIgnorePattern: "^_",
|
|
86
|
+
destructuredArrayIgnorePattern: "^_",
|
|
87
|
+
// Matches the prior ESLint config: unused vars are not reported (Prettier/oxfmt and TS
|
|
88
|
+
// already cover most cases); only unused args without a `_` prefix are flagged.
|
|
89
|
+
varsIgnorePattern: "^\\w",
|
|
90
|
+
ignoreRestSiblings: true,
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
|
|
94
|
+
// TypeScript — intentionally permissive (matches the prior ESLint config).
|
|
95
|
+
"typescript/ban-ts-comment": ["warn", { "ts-ignore": "allow-with-description" }],
|
|
96
|
+
"typescript/ban-types": "off",
|
|
97
|
+
"typescript/no-empty-interface": "off",
|
|
98
|
+
"typescript/no-explicit-any": "off",
|
|
99
|
+
"typescript/no-misused-new": "off",
|
|
100
|
+
"typescript/no-non-null-assertion": "off",
|
|
101
|
+
"typescript/no-var-requires": "off",
|
|
102
|
+
"typescript/no-require-imports": "off",
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (headers) {
|
|
106
|
+
// Error severity: missing headers should fail `oxlint` (a real CI gate) and surface as an
|
|
107
|
+
// editor error. The rule is auto-fixable, so `oxlint --fix` keeps the friction low.
|
|
108
|
+
rules["sister-software/require-file-header"] = ["error", { copyrightHolder, spdxLicenseIdentifier, author }]
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
plugins,
|
|
113
|
+
...(headers ? { jsPlugins: ["@sister.software/oxlint-config/headers-plugin"] } : {}),
|
|
114
|
+
categories: { correctness: "error" },
|
|
115
|
+
ignorePatterns,
|
|
116
|
+
rules,
|
|
117
|
+
overrides: createRuntimeOverrides(packageNamespace),
|
|
118
|
+
...overrides,
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export default createOxlintConfig
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
* @file Platform-layering import restrictions, ported to oxlint's `no-restricted-imports`.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { builtinModules } from "node:module"
|
|
9
|
+
|
|
10
|
+
//#region Runtime data
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Reserved package-name suffixes mapped to their target runtime.
|
|
14
|
+
*
|
|
15
|
+
* A package whose final name segment matches one of these signals that runtime, and imports across incompatible runtime
|
|
16
|
+
* boundaries become lint errors.
|
|
17
|
+
*/
|
|
18
|
+
export const RuntimePackageNamesRecord = {
|
|
19
|
+
browser: ["client", "browser"],
|
|
20
|
+
node: ["server", "node", "sdk"],
|
|
21
|
+
agnostic: ["shared", "common"],
|
|
22
|
+
worker: ["worker"],
|
|
23
|
+
} as const
|
|
24
|
+
|
|
25
|
+
export type RuntimePackageNamesRecord = typeof RuntimePackageNamesRecord
|
|
26
|
+
|
|
27
|
+
/** Valid runtime names. */
|
|
28
|
+
export type RuntimeName = keyof RuntimePackageNamesRecord
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Given a package name, produces the glob matcher for files belonging to that package.
|
|
32
|
+
*
|
|
33
|
+
* @param packageName The final package-name segment, e.g. `client`.
|
|
34
|
+
* @returns A single-element glob array, e.g. `["**\/client.{js,...}"]`.
|
|
35
|
+
*/
|
|
36
|
+
export function createPackageFileMatcher(packageName: string): string[] {
|
|
37
|
+
return [`**/${packageName}.{js,mjs,cjs,ts,d.ts,mts,tsx}`]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Joins a namespace and package name into a specifier, e.g. `@sister.software/client`. */
|
|
41
|
+
function namespaced(packageNamespace: string, packageName: string): string {
|
|
42
|
+
return [packageNamespace, packageName].filter(Boolean).join("/")
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
|
|
47
|
+
//#region Node built-in helpers
|
|
48
|
+
|
|
49
|
+
const NODE_BUILTINS_NO_PREFIX = builtinModules.filter(
|
|
50
|
+
(moduleName) => !moduleName.startsWith("_") && !moduleName.startsWith("node:")
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
const NODE_BUILTINS_PREFIXED = NODE_BUILTINS_NO_PREFIX.map((moduleName) => `node:${moduleName}`)
|
|
54
|
+
|
|
55
|
+
/** A restricted-import entry: an exact module name plus the message shown when it is imported. */
|
|
56
|
+
export interface RestrictedPath {
|
|
57
|
+
name: string
|
|
58
|
+
message: string
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Unprefixed Node built-ins (e.g. `fs`), each nudging toward the `node:` prefix. Used by browser- and node-runtime
|
|
63
|
+
* files where the bare form is ambiguous.
|
|
64
|
+
*/
|
|
65
|
+
export function ambiguousNodeBuiltinPaths(): RestrictedPath[] {
|
|
66
|
+
return NODE_BUILTINS_NO_PREFIX.map((name) => ({
|
|
67
|
+
name,
|
|
68
|
+
message: `Ambiguous module: did you mean \`node:${name}\`?`,
|
|
69
|
+
}))
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Every Node built-in (prefixed and unprefixed), each carrying the given message. Used by agnostic-runtime files, which
|
|
74
|
+
* must not assume a Node runtime at all.
|
|
75
|
+
*/
|
|
76
|
+
export function allNodeBuiltinPaths(message: string): RestrictedPath[] {
|
|
77
|
+
return [...NODE_BUILTINS_NO_PREFIX, ...NODE_BUILTINS_PREFIXED].map((name) => ({ name, message }))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
//#endregion
|
|
81
|
+
|
|
82
|
+
//#region Browser globals
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Browser globals whose bare use is ambiguous (they collide with common identifiers). Browser- and node-runtime files
|
|
86
|
+
* warn on these, nudging toward an explicit `window.` access.
|
|
87
|
+
*/
|
|
88
|
+
const BROWSER_GLOBALS = [
|
|
89
|
+
"addEventListener",
|
|
90
|
+
"blur",
|
|
91
|
+
"close",
|
|
92
|
+
"closed",
|
|
93
|
+
"confirm",
|
|
94
|
+
"defaultStatus",
|
|
95
|
+
"defaultstatus",
|
|
96
|
+
"event",
|
|
97
|
+
"external",
|
|
98
|
+
"find",
|
|
99
|
+
"focus",
|
|
100
|
+
"frameElement",
|
|
101
|
+
"frames",
|
|
102
|
+
"history",
|
|
103
|
+
"innerHeight",
|
|
104
|
+
"innerWidth",
|
|
105
|
+
"length",
|
|
106
|
+
"location",
|
|
107
|
+
"locationbar",
|
|
108
|
+
"menubar",
|
|
109
|
+
"moveBy",
|
|
110
|
+
"moveTo",
|
|
111
|
+
"name",
|
|
112
|
+
"onblur",
|
|
113
|
+
"onerror",
|
|
114
|
+
"onfocus",
|
|
115
|
+
"onload",
|
|
116
|
+
"onresize",
|
|
117
|
+
"onunload",
|
|
118
|
+
"open",
|
|
119
|
+
"opener",
|
|
120
|
+
"opera",
|
|
121
|
+
"outerHeight",
|
|
122
|
+
"outerWidth",
|
|
123
|
+
"pageXOffset",
|
|
124
|
+
"pageYOffset",
|
|
125
|
+
"parent",
|
|
126
|
+
"print",
|
|
127
|
+
"removeEventListener",
|
|
128
|
+
"resizeBy",
|
|
129
|
+
"resizeTo",
|
|
130
|
+
"screen",
|
|
131
|
+
"screenLeft",
|
|
132
|
+
"screenTop",
|
|
133
|
+
"screenX",
|
|
134
|
+
"screenY",
|
|
135
|
+
"scroll",
|
|
136
|
+
"scrollbars",
|
|
137
|
+
"scrollBy",
|
|
138
|
+
"scrollTo",
|
|
139
|
+
"scrollX",
|
|
140
|
+
"scrollY",
|
|
141
|
+
"self",
|
|
142
|
+
"status",
|
|
143
|
+
"statusbar",
|
|
144
|
+
"stop",
|
|
145
|
+
"toolbar",
|
|
146
|
+
"top",
|
|
147
|
+
] as const
|
|
148
|
+
|
|
149
|
+
function restrictedBrowserGlobalsRule(): unknown {
|
|
150
|
+
return ["warn", ...BROWSER_GLOBALS.map((name) => ({ name, message: `Ambiguous: did you mean \`window.${name}\`?` }))]
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
//#endregion
|
|
154
|
+
|
|
155
|
+
//#region Override generation
|
|
156
|
+
|
|
157
|
+
/** A per-file-glob oxlint override: applies `rules` only to files matching `files`. */
|
|
158
|
+
export interface OxlintOverride {
|
|
159
|
+
files: string[]
|
|
160
|
+
rules: Record<string, unknown>
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** A restricted-import pattern: a set of gitignore-style globs plus a message. */
|
|
164
|
+
interface RestrictedPattern {
|
|
165
|
+
group: string[]
|
|
166
|
+
message: string
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Builds the `patterns` entries forbidding a runtime from importing packages of incompatible runtimes, each with a
|
|
171
|
+
* tailored message.
|
|
172
|
+
*/
|
|
173
|
+
function crossRuntimePatterns(
|
|
174
|
+
packageNamespace: string,
|
|
175
|
+
self: RuntimeName,
|
|
176
|
+
incompatible: readonly RuntimeName[]
|
|
177
|
+
): RestrictedPattern[] {
|
|
178
|
+
return incompatible.flatMap((targetRuntime) =>
|
|
179
|
+
RuntimePackageNamesRecord[targetRuntime].map((packageName) => {
|
|
180
|
+
const specifier = namespaced(packageNamespace, packageName)
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
group: [specifier, `${specifier}/**`],
|
|
184
|
+
message: `A "${self}"-runtime module must not import "${specifier}", which targets the "${targetRuntime}" runtime. Move shared code into a "shared"/"common" package and import that from both.`,
|
|
185
|
+
}
|
|
186
|
+
})
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Generates the per-runtime overrides enforcing platform layering. One override is emitted for each browser-, node-,
|
|
192
|
+
* and agnostic-runtime package name (matching the original ESLint config — worker files are a restricted _target_ but
|
|
193
|
+
* have no override of their own).
|
|
194
|
+
*
|
|
195
|
+
* @param packageNamespace The namespace whose packages are subject to the rules, e.g. `@sister.software`.
|
|
196
|
+
* @returns An array of oxlint `overrides` entries.
|
|
197
|
+
*/
|
|
198
|
+
export function createRuntimeOverrides(packageNamespace: string): OxlintOverride[] {
|
|
199
|
+
const overrides: OxlintOverride[] = []
|
|
200
|
+
|
|
201
|
+
for (const packageName of RuntimePackageNamesRecord.browser) {
|
|
202
|
+
overrides.push({
|
|
203
|
+
files: createPackageFileMatcher(packageName),
|
|
204
|
+
rules: {
|
|
205
|
+
"no-restricted-imports": [
|
|
206
|
+
"warn",
|
|
207
|
+
{
|
|
208
|
+
paths: ambiguousNodeBuiltinPaths(),
|
|
209
|
+
patterns: crossRuntimePatterns(packageNamespace, "browser", ["node", "worker"]),
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
"no-restricted-globals": restrictedBrowserGlobalsRule(),
|
|
213
|
+
},
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
for (const packageName of RuntimePackageNamesRecord.node) {
|
|
218
|
+
overrides.push({
|
|
219
|
+
files: createPackageFileMatcher(packageName),
|
|
220
|
+
rules: {
|
|
221
|
+
"no-restricted-imports": [
|
|
222
|
+
"warn",
|
|
223
|
+
{
|
|
224
|
+
paths: ambiguousNodeBuiltinPaths(),
|
|
225
|
+
patterns: crossRuntimePatterns(packageNamespace, "node", ["browser", "worker"]),
|
|
226
|
+
},
|
|
227
|
+
],
|
|
228
|
+
"no-restricted-globals": restrictedBrowserGlobalsRule(),
|
|
229
|
+
},
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
for (const packageName of RuntimePackageNamesRecord.agnostic) {
|
|
234
|
+
overrides.push({
|
|
235
|
+
files: createPackageFileMatcher(packageName),
|
|
236
|
+
rules: {
|
|
237
|
+
"no-restricted-imports": [
|
|
238
|
+
"warn",
|
|
239
|
+
{
|
|
240
|
+
paths: allNodeBuiltinPaths(
|
|
241
|
+
`A "shared"/"common" module assumes no runtime and must not import Node built-ins.`
|
|
242
|
+
),
|
|
243
|
+
patterns: crossRuntimePatterns(packageNamespace, "agnostic", ["browser", "node", "worker"]),
|
|
244
|
+
},
|
|
245
|
+
],
|
|
246
|
+
},
|
|
247
|
+
})
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return overrides
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
//#endregion
|