@sister.software/oxlint-config 9.2.0 → 10.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.
@@ -0,0 +1,93 @@
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
+ import type { AstNode, Rule } from "./plugin-types.js"
13
+
14
+ /** Operators whose operands read as thresholds. */
15
+ const COMPARISON_OPERATORS = new Set(["<", ">", "<=", ">=", "===", "!==", "==", "!="])
16
+
17
+ /** Numbers so conventional that naming them costs more clarity than it buys. */
18
+ const DEFAULT_IGNORE = [-1, 0, 1, 2, 0.5, 10, 100, 1000]
19
+
20
+ /** Radix-prefixed literals: `0x1f`, `0b1010`, `0o777`. */
21
+ const RADIX_PREFIXED = /^0[xXbBoO]/
22
+
23
+ /** Options accepted by {@link noUnnamedThresholdRule}. */
24
+ export interface ThresholdOptions {
25
+ /** Values that may appear unnamed. Replaces the default list rather than extending it. */
26
+ ignore?: number[]
27
+ /** Exempt radix-prefixed literals, which already read as codepoints or bit masks. */
28
+ allowHex?: boolean
29
+ }
30
+
31
+ export const noUnnamedThresholdRule: Rule = {
32
+ meta: {
33
+ name: "no-unnamed-threshold",
34
+ type: "suggestion",
35
+ schema: [{ type: "object", additionalProperties: true }],
36
+ },
37
+ create(context) {
38
+ const options = (context.options[0] ?? {}) as ThresholdOptions
39
+ const ignore = new Set(options.ignore ?? DEFAULT_IGNORE)
40
+ const allowHex = options.allowHex ?? true
41
+
42
+ function check(operand: AstNode | undefined) {
43
+ if (!operand) return
44
+
45
+ let literal = operand
46
+ let sign = 1
47
+
48
+ // `x < -273.15` parses as a unary minus wrapping the literal.
49
+ if (operand.type === "UnaryExpression") {
50
+ if (operand.operator !== "-" && operand.operator !== "+") return
51
+
52
+ if (!operand.argument) return
53
+
54
+ sign = operand.operator === "-" ? -1 : 1
55
+ literal = operand.argument
56
+ }
57
+
58
+ // oxlint's AST uses ESTree `Literal` in some positions and Babel-style `NumericLiteral` in
59
+ // others, so both are accepted.
60
+ if (literal.type !== "Literal" && literal.type !== "NumericLiteral") return
61
+
62
+ if (typeof literal.value !== "number") return
63
+
64
+ const raw = literal.raw ?? String(literal.value)
65
+
66
+ // `cp >= 0x3040` already reads as a codepoint boundary; a name adds nothing.
67
+ if (allowHex && RADIX_PREFIXED.test(raw)) return
68
+
69
+ if (ignore.has(sign * literal.value)) return
70
+
71
+ // `raw` is the literal's own text, so a negated value reads as `273.15` without this.
72
+ const shown = sign === -1 ? `-${raw}` : raw
73
+
74
+ context.report({
75
+ node: literal,
76
+ message:
77
+ `Unnamed threshold \`${shown}\` — extract it to a documented named constant so a reader can tell ` +
78
+ `what it means and where the value came from.`,
79
+ })
80
+ }
81
+
82
+ return {
83
+ BinaryExpression(node) {
84
+ if (!node.operator || !COMPARISON_OPERATORS.has(node.operator)) return
85
+
86
+ check(node.left)
87
+ check(node.right)
88
+ },
89
+ }
90
+ },
91
+ }
92
+
93
+ export default noUnnamedThresholdRule