@sister.software/oxlint-config 9.3.0 → 11.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.
Files changed (78) hide show
  1. package/README.md +198 -10
  2. package/out/browser-globals.d.ts +15 -0
  3. package/out/browser-globals.d.ts.map +1 -0
  4. package/out/browser-globals.js +76 -0
  5. package/out/browser-globals.js.map +1 -0
  6. package/out/console-padding-plugin.d.ts +16 -0
  7. package/out/console-padding-plugin.d.ts.map +1 -0
  8. package/out/console-padding-plugin.js +68 -0
  9. package/out/console-padding-plugin.js.map +1 -0
  10. package/out/constant-doc-plugin.d.ts +32 -0
  11. package/out/constant-doc-plugin.d.ts.map +1 -0
  12. package/out/constant-doc-plugin.js +91 -0
  13. package/out/constant-doc-plugin.js.map +1 -0
  14. package/out/headers-plugin.d.ts +6 -2
  15. package/out/headers-plugin.d.ts.map +1 -1
  16. package/out/headers-plugin.js +4 -2
  17. package/out/headers-plugin.js.map +1 -1
  18. package/out/index.d.ts +166 -13
  19. package/out/index.d.ts.map +1 -1
  20. package/out/index.js +332 -6
  21. package/out/index.js.map +1 -1
  22. package/out/jsdoc-plugin.d.ts +16 -0
  23. package/out/jsdoc-plugin.d.ts.map +1 -0
  24. package/out/jsdoc-plugin.js +68 -0
  25. package/out/jsdoc-plugin.js.map +1 -0
  26. package/out/length-truthiness-plugin.d.ts +18 -0
  27. package/out/length-truthiness-plugin.d.ts.map +1 -0
  28. package/out/length-truthiness-plugin.js +123 -0
  29. package/out/length-truthiness-plugin.js.map +1 -0
  30. package/out/multiline-statement-plugin.d.ts +18 -0
  31. package/out/multiline-statement-plugin.d.ts.map +1 -0
  32. package/out/multiline-statement-plugin.js +93 -0
  33. package/out/multiline-statement-plugin.js.map +1 -0
  34. package/out/padding-plugin.d.ts +4 -3
  35. package/out/padding-plugin.d.ts.map +1 -1
  36. package/out/padding-plugin.js +44 -27
  37. package/out/padding-plugin.js.map +1 -1
  38. package/out/padding-utils.d.ts +34 -0
  39. package/out/padding-utils.d.ts.map +1 -0
  40. package/out/padding-utils.js +50 -0
  41. package/out/padding-utils.js.map +1 -0
  42. package/out/plugin-types.d.ts +68 -3
  43. package/out/plugin-types.d.ts.map +1 -1
  44. package/out/plugin.d.ts.map +1 -1
  45. package/out/plugin.js +17 -0
  46. package/out/plugin.js.map +1 -1
  47. package/out/process-globals-plugin.d.ts.map +1 -1
  48. package/out/process-globals-plugin.js +6 -2
  49. package/out/process-globals-plugin.js.map +1 -1
  50. package/out/restrictions.d.ts +9 -3
  51. package/out/restrictions.d.ts.map +1 -1
  52. package/out/restrictions.js +4 -70
  53. package/out/restrictions.js.map +1 -1
  54. package/out/section-marker-plugin.d.ts +25 -0
  55. package/out/section-marker-plugin.d.ts.map +1 -0
  56. package/out/section-marker-plugin.js +234 -0
  57. package/out/section-marker-plugin.js.map +1 -0
  58. package/out/threshold-plugin.d.ts +27 -0
  59. package/out/threshold-plugin.d.ts.map +1 -0
  60. package/out/threshold-plugin.js +78 -0
  61. package/out/threshold-plugin.js.map +1 -0
  62. package/package.json +2 -2
  63. package/src/browser-globals.ts +77 -0
  64. package/src/console-padding-plugin.ts +76 -0
  65. package/src/constant-doc-plugin.ts +124 -0
  66. package/src/headers-plugin.ts +7 -3
  67. package/src/index.ts +490 -17
  68. package/src/jsdoc-plugin.ts +75 -0
  69. package/src/length-truthiness-plugin.ts +142 -0
  70. package/src/multiline-statement-plugin.ts +104 -0
  71. package/src/padding-plugin.ts +44 -29
  72. package/src/padding-utils.ts +70 -0
  73. package/src/plugin-types.ts +68 -3
  74. package/src/plugin.ts +22 -0
  75. package/src/process-globals-plugin.ts +6 -2
  76. package/src/restrictions.ts +16 -77
  77. package/src/section-marker-plugin.ts +306 -0
  78. package/src/threshold-plugin.ts +105 -0
@@ -0,0 +1,78 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ * @file A no-unnamed-threshold rule, authored as an oxlint JS plugin (ESLint v9-compatible API). It
6
+ * flags a numeric literal used as a threshold — an operand of a comparison — and nothing else.
7
+ * Numbers inside array and object literals are left alone, because a bounding box or a codepoint
8
+ * table is data rather than a tuning knob. That distinction is why this exists instead of
9
+ * `no-magic-numbers`, which cannot express it.
10
+ */
11
+ /**
12
+ * Operators whose operands read as thresholds.
13
+ */
14
+ const COMPARISON_OPERATORS = new Set(["<", ">", "<=", ">=", "===", "!==", "==", "!="]);
15
+ /**
16
+ * Numbers so conventional that naming them costs more clarity than it buys.
17
+ */
18
+ const DEFAULT_IGNORE = [-1, 0, 1, 2, 0.5, 10, 100, 1000];
19
+ /**
20
+ * Radix-prefixed literals: `0x1f`, `0b1010`, `0o777`.
21
+ */
22
+ const RADIX_PREFIXED = /^0[xXbBoO]/;
23
+ export const noUnnamedThresholdRule = {
24
+ meta: {
25
+ name: "no-unnamed-threshold",
26
+ type: "suggestion",
27
+ schema: [{ type: "object", additionalProperties: true }],
28
+ },
29
+ create(context) {
30
+ const options = (context.options[0] ?? {});
31
+ const ignore = new Set(options.ignore ?? DEFAULT_IGNORE);
32
+ const allowHex = options.allowHex ?? true;
33
+ function check(operand) {
34
+ if (!operand)
35
+ return;
36
+ let literal = operand;
37
+ let sign = 1;
38
+ // `x < -273.15` parses as a unary minus wrapping the literal.
39
+ if (operand.type === "UnaryExpression") {
40
+ if (operand.operator !== "-" && operand.operator !== "+")
41
+ return;
42
+ if (!operand.argument)
43
+ return;
44
+ sign = operand.operator === "-" ? -1 : 1;
45
+ literal = operand.argument;
46
+ }
47
+ // oxlint's AST uses ESTree `Literal` in some positions and Babel-style `NumericLiteral` in
48
+ // others, so both are accepted.
49
+ if (literal.type !== "Literal" && literal.type !== "NumericLiteral")
50
+ return;
51
+ if (typeof literal.value !== "number")
52
+ return;
53
+ const raw = literal.raw ?? String(literal.value);
54
+ // `cp >= 0x3040` already reads as a codepoint boundary; a name adds nothing.
55
+ if (allowHex && RADIX_PREFIXED.test(raw))
56
+ return;
57
+ if (ignore.has(sign * literal.value))
58
+ return;
59
+ // `raw` is the literal's own text, so a negated value reads as `273.15` without this.
60
+ const shown = sign === -1 ? `-${raw}` : raw;
61
+ context.report({
62
+ node: literal,
63
+ message: `Unnamed threshold \`${shown}\` — extract it to a documented named constant so a reader can tell ` +
64
+ `what it means and where the value came from.`,
65
+ });
66
+ }
67
+ return {
68
+ BinaryExpression(node) {
69
+ if (!node.operator || !COMPARISON_OPERATORS.has(node.operator))
70
+ return;
71
+ check(node.left);
72
+ check(node.right);
73
+ },
74
+ };
75
+ },
76
+ };
77
+ export default noUnnamedThresholdRule;
78
+ //# sourceMappingURL=threshold-plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"threshold-plugin.js","sourceRoot":"","sources":["../src/threshold-plugin.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sister.software/oxlint-config",
3
- "version": "9.3.0",
3
+ "version": "11.0.0",
4
4
  "description": "Sister Software's oxlint config",
5
5
  "license": "AGPL-3.0",
6
6
  "author": "teffen@sister.software",
@@ -40,7 +40,7 @@
40
40
  "typescript": "^6.0.3"
41
41
  },
42
42
  "peerDependencies": {
43
- "oxlint": "^1.71.0"
43
+ "oxlint": ">=1.75.0 <2"
44
44
  },
45
45
  "engines": {
46
46
  "node": ">=24.0"
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ * @file The browser globals whose bare use is ambiguous, and the rule entry built from them. A list
6
+ * this long is reference data — it belongs beside the code that consumes it, not inside the file
7
+ * that assembles the platform-layering overrides.
8
+ */
9
+
10
+ /**
11
+ * Browser globals whose bare use is ambiguous (they collide with common identifiers). Browser- and node-runtime files
12
+ * warn on these, nudging toward an explicit `window.` access.
13
+ */
14
+ export const BROWSER_GLOBALS = [
15
+ "addEventListener",
16
+ "blur",
17
+ "close",
18
+ "closed",
19
+ "confirm",
20
+ "defaultStatus",
21
+ "defaultstatus",
22
+ "event",
23
+ "external",
24
+ "find",
25
+ "focus",
26
+ "frameElement",
27
+ "frames",
28
+ "history",
29
+ "innerHeight",
30
+ "innerWidth",
31
+ "length",
32
+ "location",
33
+ "locationbar",
34
+ "menubar",
35
+ "moveBy",
36
+ "moveTo",
37
+ "name",
38
+ "onblur",
39
+ "onerror",
40
+ "onfocus",
41
+ "onload",
42
+ "onresize",
43
+ "onunload",
44
+ "open",
45
+ "opener",
46
+ "opera",
47
+ "outerHeight",
48
+ "outerWidth",
49
+ "pageXOffset",
50
+ "pageYOffset",
51
+ "parent",
52
+ "print",
53
+ "removeEventListener",
54
+ "resizeBy",
55
+ "resizeTo",
56
+ "screen",
57
+ "screenLeft",
58
+ "screenTop",
59
+ "screenX",
60
+ "screenY",
61
+ "scroll",
62
+ "scrollbars",
63
+ "scrollBy",
64
+ "scrollTo",
65
+ "scrollX",
66
+ "scrollY",
67
+ "self",
68
+ "status",
69
+ "statusbar",
70
+ "stop",
71
+ "toolbar",
72
+ "top",
73
+ ] as const
74
+
75
+ export function restrictedBrowserGlobalsRule(): unknown {
76
+ return ["warn", ...BROWSER_GLOBALS.map((name) => ({ name, message: `Ambiguous: did you mean \`window.${name}\`?` }))]
77
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ * @file The `sister-software/console-padding` rule: a `console.*` call gets a blank line on each
6
+ * side, so the lines that produce output stand apart from the lines that compute it. Scanning a
7
+ * long procedural script for "what does this print" then works visually.
8
+ *
9
+ * A RUN of console calls is one group — no blank lines inside it, one before the first and one
10
+ * after the last, because consecutive calls are one block of output. Neither edge is required
11
+ * where the block boundary already provides the separation: nothing is needed before a call that
12
+ * opens a block, or after one that closes it.
13
+ */
14
+
15
+ import { createPaddingHelpers } from "./padding-utils.js"
16
+ import type { AstNode, Rule } from "./plugin-types.js"
17
+
18
+ /**
19
+ * The object whose method calls this rule treats as output.
20
+ */
21
+ const CONSOLE_OBJECT = "console"
22
+
23
+ /**
24
+ * Is this statement a bare `console.<method>(…)` call?
25
+ */
26
+ function isConsoleStatement(node: AstNode | undefined): boolean {
27
+ if (node?.type !== "ExpressionStatement") return false
28
+ const call = node.expression
29
+
30
+ if (call?.type !== "CallExpression") return false
31
+ const callee = call.callee
32
+
33
+ return (
34
+ callee?.type === "MemberExpression" && callee.object?.type === "Identifier" && callee.object.name === CONSOLE_OBJECT
35
+ )
36
+ }
37
+
38
+ export const consolePaddingRule: Rule = {
39
+ meta: {
40
+ name: "console-padding",
41
+ type: "layout",
42
+ fixable: "whitespace",
43
+ schema: [{ type: "object", additionalProperties: true }],
44
+ },
45
+ create(context) {
46
+ const sourceCode = context.sourceCode ?? context.getSourceCode!()
47
+ const text = sourceCode.getText()
48
+ const { requirePadding } = createPaddingHelpers(context)
49
+
50
+ return {
51
+ ExpressionStatement(node: AstNode) {
52
+ if (!isConsoleStatement(node)) return
53
+ const parent = node.parent
54
+
55
+ // Only statements in a statement list have neighbours to pad against.
56
+ if (!parent || !Array.isArray(parent.body)) return
57
+ const body = parent.body
58
+ const index = body.indexOf(node)
59
+
60
+ if (index === -1) return
61
+ const previous = body[index - 1]
62
+ const next = body[index + 1]
63
+
64
+ // Nothing before a call that opens the block, and nothing between calls in one run.
65
+ if (previous && !isConsoleStatement(previous)) {
66
+ requirePadding(previous, node, node, "Expected a blank line before this console call.")
67
+ }
68
+
69
+ // Nothing after a call that closes the block — the brace already separates it.
70
+ if (next && !isConsoleStatement(next)) {
71
+ requirePadding(node, next, node, "Expected a blank line after this console call.")
72
+ }
73
+ },
74
+ }
75
+ },
76
+ }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ * @file A require-constant-doc rule, authored as an oxlint JS plugin (ESLint v9-compatible API). A
6
+ * module-level constant that is exported or SCREAMING_CASE is either public surface or a tuning
7
+ * knob, and in both cases a reader needs to know what the value means and where it came from. A
8
+ * documented table also answers for every number inside it, which is why data-heavy files satisfy
9
+ * this rule without extracting a constant per row.
10
+ */
11
+
12
+ import type { Rule } from "./plugin-types.js"
13
+
14
+ /**
15
+ * Names in SCREAMING_SNAKE_CASE, the convention for a tuning knob.
16
+ */
17
+ const SCREAMING_CASE = /^[A-Z][A-Z0-9_]*$/
18
+
19
+ /**
20
+ * Initializers that make the binding a function rather than a constant value.
21
+ */
22
+ const FUNCTION_INITIALIZERS = new Set(["ArrowFunctionExpression", "FunctionExpression"])
23
+
24
+ /**
25
+ * Which module-level constants the rule applies to.
26
+ */
27
+ export type ConstantDocScope = "exported" | "screaming" | "exported-or-screaming"
28
+
29
+ /**
30
+ * Options accepted by {@link requireConstantDocRule}.
31
+ */
32
+ export interface ConstantDocOptions {
33
+ /**
34
+ * Defaults to `"exported-or-screaming"`.
35
+ */
36
+ scope?: ConstantDocScope
37
+ /**
38
+ * Export names a framework requires and gives meaning to, which a JSDoc block cannot improve on — Pastel's
39
+ * `description` (whose value IS the help text), a route module's `loader`, and so on.
40
+ */
41
+ ignoreNames?: string[]
42
+ }
43
+
44
+ export const requireConstantDocRule: Rule = {
45
+ meta: {
46
+ name: "require-constant-doc",
47
+ type: "suggestion",
48
+ schema: [{ type: "object", additionalProperties: true }],
49
+ },
50
+ create(context) {
51
+ const options = (context.options[0] ?? {}) as ConstantDocOptions
52
+ const scope = options.scope ?? "exported-or-screaming"
53
+ const ignoreNames = new Set(options.ignoreNames)
54
+ const sourceCode = context.sourceCode ?? context.getSourceCode!()
55
+ const text = sourceCode.getText()
56
+ const comments = sourceCode.getAllComments()
57
+
58
+ /**
59
+ * True when a JSDoc block documents the declaration starting at `start`.
60
+ *
61
+ * Attachment requires the comment to be directly above it — whitespace spanning a single newline. A blank line in
62
+ * between means the comment belongs to something else, which is what separates a declaration's own JSDoc from the
63
+ * file header block that precedes the first declaration in every file.
64
+ */
65
+ function hasJSDocBefore(start: number): boolean {
66
+ for (const comment of comments) {
67
+ if (comment.range[1] > start) continue
68
+
69
+ if (comment.type !== "Block" || !comment.value.startsWith("*")) continue
70
+
71
+ const gap = text.slice(comment.range[1], start)
72
+
73
+ if (/^\s*$/.test(gap) && (gap.match(/\n/g) ?? []).length <= 1) return true
74
+ }
75
+
76
+ return false
77
+ }
78
+
79
+ function inScope(exported: boolean, screaming: boolean): boolean {
80
+ if (scope === "exported") return exported
81
+
82
+ if (scope === "screaming") return screaming
83
+
84
+ return exported || screaming
85
+ }
86
+
87
+ return {
88
+ VariableDeclaration(node) {
89
+ if (node.kind !== "const") return
90
+
91
+ const exported = node.parent?.type === "ExportNamedDeclaration"
92
+ const container = exported ? node.parent! : node
93
+
94
+ // Module level only: the declaration (or its export wrapper) sits directly in Program.
95
+ if (container.parent?.type !== "Program") return
96
+
97
+ if (hasJSDocBefore(container.range[0])) return
98
+
99
+ for (const declarator of node.declarations ?? []) {
100
+ const id = declarator.id
101
+
102
+ if (id?.type !== "Identifier" || !id.name) continue
103
+
104
+ if (ignoreNames.has(id.name)) continue
105
+
106
+ const initializer = declarator.init
107
+
108
+ if (initializer && FUNCTION_INITIALIZERS.has(initializer.type)) continue
109
+
110
+ if (!inScope(exported, SCREAMING_CASE.test(id.name))) continue
111
+
112
+ context.report({
113
+ node: declarator,
114
+ message:
115
+ `\`${id.name}\` is ${exported ? "exported" : "a named constant"} but undocumented — add a ` +
116
+ `JSDoc block saying what the value means and where it came from.`,
117
+ })
118
+ }
119
+ },
120
+ }
121
+ },
122
+ }
123
+
124
+ export default requireConstantDocRule
@@ -9,7 +9,9 @@
9
9
 
10
10
  import type { Comment, Plugin, Rule } from "./plugin-types.js"
11
11
 
12
- /** Options accepted by the header rule. */
12
+ /**
13
+ * Options accepted by the header rule.
14
+ */
13
15
  export interface HeaderRuleOptions {
14
16
  copyrightHolder?: string
15
17
  spdxLicenseIdentifier?: string
@@ -67,7 +69,7 @@ export const headerRule: Rule = {
67
69
  const existing = parseInnerLines(leading)
68
70
  const missing = headerLines.filter((line) => !existing.includes(line))
69
71
 
70
- if (missing.length === 0) return
72
+ if (!missing.length) return
71
73
 
72
74
  const preserved = existing.filter((line) => !HEADER_TAG_PATTERN.test(line))
73
75
  const block = buildBlock(preserved)
@@ -96,7 +98,9 @@ export const headerRule: Rule = {
96
98
  },
97
99
  }
98
100
 
99
- /** The Sister Software oxlint header plugin. Registers the `sister-software/require-file-header` rule. */
101
+ /**
102
+ * The Sister Software oxlint header plugin. Registers the `sister-software/require-file-header` rule.
103
+ */
100
104
  const headerPlugin: Plugin = {
101
105
  meta: { name: "sister-software" },
102
106
  rules: { "require-file-header": headerRule },