@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.
- package/README.md +102 -10
- package/out/constant-doc-plugin.d.ts +26 -0
- package/out/constant-doc-plugin.d.ts.map +1 -0
- package/out/constant-doc-plugin.js +87 -0
- package/out/constant-doc-plugin.js.map +1 -0
- package/out/headers-plugin.js +1 -1
- package/out/headers-plugin.js.map +1 -1
- package/out/index.d.ts +75 -1
- package/out/index.d.ts.map +1 -1
- package/out/index.js +286 -6
- package/out/index.js.map +1 -1
- package/out/length-truthiness-plugin.d.ts +18 -0
- package/out/length-truthiness-plugin.d.ts.map +1 -0
- package/out/length-truthiness-plugin.js +113 -0
- package/out/length-truthiness-plugin.js.map +1 -0
- package/out/plugin-types.d.ts +23 -0
- package/out/plugin-types.d.ts.map +1 -1
- package/out/plugin.d.ts.map +1 -1
- package/out/plugin.js +8 -0
- package/out/plugin.js.map +1 -1
- package/out/process-globals-plugin.d.ts +13 -0
- package/out/process-globals-plugin.d.ts.map +1 -0
- package/out/process-globals-plugin.js +48 -0
- package/out/process-globals-plugin.js.map +1 -0
- package/out/threshold-plugin.d.ts +21 -0
- package/out/threshold-plugin.d.ts.map +1 -0
- package/out/threshold-plugin.js +72 -0
- package/out/threshold-plugin.js.map +1 -0
- package/package.json +2 -2
- package/src/constant-doc-plugin.ts +114 -0
- package/src/headers-plugin.ts +1 -1
- package/src/index.ts +354 -5
- package/src/length-truthiness-plugin.ts +132 -0
- package/src/plugin-types.ts +23 -0
- package/src/plugin.ts +8 -0
- package/src/process-globals-plugin.ts +68 -0
- package/src/threshold-plugin.ts +93 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
* @file A no-process-globals rule, authored as an oxlint JS plugin (ESLint v9-compatible API). It
|
|
6
|
+
* forbids direct `process.env` / `process.argv` access so those reads can be funneled through a
|
|
7
|
+
* few blessed helpers; the helper files disable the rule (`sister-software/no-process-globals`).
|
|
8
|
+
* oxlint has no `no-restricted-syntax`, so this is a small dedicated rule instead.
|
|
9
|
+
*/
|
|
10
|
+
/** `process` members that must be reached through a blessed helper, not accessed directly. */
|
|
11
|
+
const RESTRICTED_MEMBERS = new Set(["env", "argv"]);
|
|
12
|
+
/** The accessed member name for `process.env` (identifier) or `process["env"]` (string literal). */
|
|
13
|
+
function accessedMember(node) {
|
|
14
|
+
const property = node.property;
|
|
15
|
+
if (!property)
|
|
16
|
+
return null;
|
|
17
|
+
if (!node.computed && property.type === "Identifier")
|
|
18
|
+
return property.name ?? null;
|
|
19
|
+
if (node.computed && (property.type === "Literal" || property.type === "StringLiteral")) {
|
|
20
|
+
return typeof property.value === "string" ? property.value : null;
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
export const noProcessGlobalsRule = {
|
|
25
|
+
meta: {
|
|
26
|
+
name: "no-process-globals",
|
|
27
|
+
type: "problem",
|
|
28
|
+
schema: [{ type: "object", additionalProperties: true }],
|
|
29
|
+
},
|
|
30
|
+
create(context) {
|
|
31
|
+
return {
|
|
32
|
+
MemberExpression(node) {
|
|
33
|
+
const member = node;
|
|
34
|
+
if (member.object?.type !== "Identifier" || member.object.name !== "process")
|
|
35
|
+
return;
|
|
36
|
+
const name = accessedMember(member);
|
|
37
|
+
if (!name || !RESTRICTED_MEMBERS.has(name))
|
|
38
|
+
return;
|
|
39
|
+
context.report({
|
|
40
|
+
node,
|
|
41
|
+
message: `Direct \`process.${name}\` access is restricted — read it through the project's blessed helper (disable \`sister-software/no-process-globals\` there).`,
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
export default noProcessGlobalsRule;
|
|
48
|
+
//# sourceMappingURL=process-globals-plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-globals-plugin.js","sourceRoot":"","sources":["../src/process-globals-plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,8FAA8F;AAC9F,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAA;AAanD,oGAAoG;AACpG,SAAS,cAAc,CAAC,IAAgB;IACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAA;IAE9B,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAA;IAE1B,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,IAAI,KAAK,YAAY;QAAE,OAAO,QAAQ,CAAC,IAAI,IAAI,IAAI,CAAA;IAElF,IAAI,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ,CAAC,IAAI,KAAK,SAAS,IAAI,QAAQ,CAAC,IAAI,KAAK,eAAe,CAAC,EAAE,CAAC;QACzF,OAAO,OAAO,QAAQ,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA;IAClE,CAAC;IAED,OAAO,IAAI,CAAA;AACZ,CAAC;AAED,MAAM,CAAC,MAAM,oBAAoB,GAAS;IACzC,IAAI,EAAE;QACL,IAAI,EAAE,oBAAoB;QAC1B,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,oBAAoB,EAAE,IAAI,EAAE,CAAC;KACxD;IACD,MAAM,CAAC,OAAO;QACb,OAAO;YACN,gBAAgB,CAAC,IAAI;gBACpB,MAAM,MAAM,GAAG,IAAkB,CAAA;gBAEjC,IAAI,MAAM,CAAC,MAAM,EAAE,IAAI,KAAK,YAAY,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,SAAS;oBAAE,OAAM;gBAEpF,MAAM,IAAI,GAAG,cAAc,CAAC,MAAM,CAAC,CAAA;gBAEnC,IAAI,CAAC,IAAI,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC;oBAAE,OAAM;gBAElD,OAAO,CAAC,MAAM,CAAC;oBACd,IAAI;oBACJ,OAAO,EAAE,oBAAoB,IAAI,gIAAgI;iBACjK,CAAC,CAAA;YACH,CAAC;SACD,CAAA;IACF,CAAC;CACD,CAAA;AAED,eAAe,oBAAoB,CAAA"}
|
|
@@ -0,0 +1,21 @@
|
|
|
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
|
+
import type { Rule } from "./plugin-types.js";
|
|
12
|
+
/** Options accepted by {@link noUnnamedThresholdRule}. */
|
|
13
|
+
export interface ThresholdOptions {
|
|
14
|
+
/** Values that may appear unnamed. Replaces the default list rather than extending it. */
|
|
15
|
+
ignore?: number[];
|
|
16
|
+
/** Exempt radix-prefixed literals, which already read as codepoints or bit masks. */
|
|
17
|
+
allowHex?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare const noUnnamedThresholdRule: Rule;
|
|
20
|
+
export default noUnnamedThresholdRule;
|
|
21
|
+
//# sourceMappingURL=threshold-plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"threshold-plugin.d.ts","sourceRoot":"","sources":["../src/threshold-plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAW,IAAI,EAAE,MAAM,mBAAmB,CAAA;AAWtD,0DAA0D;AAC1D,MAAM,WAAW,gBAAgB;IAChC,0FAA0F;IAC1F,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,qFAAqF;IACrF,QAAQ,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,eAAO,MAAM,sBAAsB,EAAE,IA4DpC,CAAA;AAED,eAAe,sBAAsB,CAAA"}
|
|
@@ -0,0 +1,72 @@
|
|
|
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
|
+
/** Operators whose operands read as thresholds. */
|
|
12
|
+
const COMPARISON_OPERATORS = new Set(["<", ">", "<=", ">=", "===", "!==", "==", "!="]);
|
|
13
|
+
/** Numbers so conventional that naming them costs more clarity than it buys. */
|
|
14
|
+
const DEFAULT_IGNORE = [-1, 0, 1, 2, 0.5, 10, 100, 1000];
|
|
15
|
+
/** Radix-prefixed literals: `0x1f`, `0b1010`, `0o777`. */
|
|
16
|
+
const RADIX_PREFIXED = /^0[xXbBoO]/;
|
|
17
|
+
export const noUnnamedThresholdRule = {
|
|
18
|
+
meta: {
|
|
19
|
+
name: "no-unnamed-threshold",
|
|
20
|
+
type: "suggestion",
|
|
21
|
+
schema: [{ type: "object", additionalProperties: true }],
|
|
22
|
+
},
|
|
23
|
+
create(context) {
|
|
24
|
+
const options = (context.options[0] ?? {});
|
|
25
|
+
const ignore = new Set(options.ignore ?? DEFAULT_IGNORE);
|
|
26
|
+
const allowHex = options.allowHex ?? true;
|
|
27
|
+
function check(operand) {
|
|
28
|
+
if (!operand)
|
|
29
|
+
return;
|
|
30
|
+
let literal = operand;
|
|
31
|
+
let sign = 1;
|
|
32
|
+
// `x < -273.15` parses as a unary minus wrapping the literal.
|
|
33
|
+
if (operand.type === "UnaryExpression") {
|
|
34
|
+
if (operand.operator !== "-" && operand.operator !== "+")
|
|
35
|
+
return;
|
|
36
|
+
if (!operand.argument)
|
|
37
|
+
return;
|
|
38
|
+
sign = operand.operator === "-" ? -1 : 1;
|
|
39
|
+
literal = operand.argument;
|
|
40
|
+
}
|
|
41
|
+
// oxlint's AST uses ESTree `Literal` in some positions and Babel-style `NumericLiteral` in
|
|
42
|
+
// others, so both are accepted.
|
|
43
|
+
if (literal.type !== "Literal" && literal.type !== "NumericLiteral")
|
|
44
|
+
return;
|
|
45
|
+
if (typeof literal.value !== "number")
|
|
46
|
+
return;
|
|
47
|
+
const raw = literal.raw ?? String(literal.value);
|
|
48
|
+
// `cp >= 0x3040` already reads as a codepoint boundary; a name adds nothing.
|
|
49
|
+
if (allowHex && RADIX_PREFIXED.test(raw))
|
|
50
|
+
return;
|
|
51
|
+
if (ignore.has(sign * literal.value))
|
|
52
|
+
return;
|
|
53
|
+
// `raw` is the literal's own text, so a negated value reads as `273.15` without this.
|
|
54
|
+
const shown = sign === -1 ? `-${raw}` : raw;
|
|
55
|
+
context.report({
|
|
56
|
+
node: literal,
|
|
57
|
+
message: `Unnamed threshold \`${shown}\` — extract it to a documented named constant so a reader can tell ` +
|
|
58
|
+
`what it means and where the value came from.`,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
BinaryExpression(node) {
|
|
63
|
+
if (!node.operator || !COMPARISON_OPERATORS.has(node.operator))
|
|
64
|
+
return;
|
|
65
|
+
check(node.left);
|
|
66
|
+
check(node.right);
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
export default noUnnamedThresholdRule;
|
|
72
|
+
//# 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": "
|
|
3
|
+
"version": "10.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": "
|
|
43
|
+
"oxlint": ">=1.75.0 <2"
|
|
44
44
|
},
|
|
45
45
|
"engines": {
|
|
46
46
|
"node": ">=24.0"
|
|
@@ -0,0 +1,114 @@
|
|
|
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
|
+
/** Names in SCREAMING_SNAKE_CASE, the convention for a tuning knob. */
|
|
15
|
+
const SCREAMING_CASE = /^[A-Z][A-Z0-9_]*$/
|
|
16
|
+
|
|
17
|
+
/** Initializers that make the binding a function rather than a constant value. */
|
|
18
|
+
const FUNCTION_INITIALIZERS = new Set(["ArrowFunctionExpression", "FunctionExpression"])
|
|
19
|
+
|
|
20
|
+
/** Which module-level constants the rule applies to. */
|
|
21
|
+
export type ConstantDocScope = "exported" | "screaming" | "exported-or-screaming"
|
|
22
|
+
|
|
23
|
+
/** Options accepted by {@link requireConstantDocRule}. */
|
|
24
|
+
export interface ConstantDocOptions {
|
|
25
|
+
/** Defaults to `"exported-or-screaming"`. */
|
|
26
|
+
scope?: ConstantDocScope
|
|
27
|
+
/**
|
|
28
|
+
* Export names a framework requires and gives meaning to, which a JSDoc block cannot improve on — Pastel's
|
|
29
|
+
* `description` (whose value IS the help text), a route module's `loader`, and so on.
|
|
30
|
+
*/
|
|
31
|
+
ignoreNames?: string[]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const requireConstantDocRule: Rule = {
|
|
35
|
+
meta: {
|
|
36
|
+
name: "require-constant-doc",
|
|
37
|
+
type: "suggestion",
|
|
38
|
+
schema: [{ type: "object", additionalProperties: true }],
|
|
39
|
+
},
|
|
40
|
+
create(context) {
|
|
41
|
+
const options = (context.options[0] ?? {}) as ConstantDocOptions
|
|
42
|
+
const scope = options.scope ?? "exported-or-screaming"
|
|
43
|
+
const ignoreNames = new Set(options.ignoreNames)
|
|
44
|
+
const sourceCode = context.sourceCode ?? context.getSourceCode!()
|
|
45
|
+
const text = sourceCode.getText()
|
|
46
|
+
const comments = sourceCode.getAllComments()
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* True when a JSDoc block documents the declaration starting at `start`.
|
|
50
|
+
*
|
|
51
|
+
* Attachment requires the comment to be directly above it — whitespace spanning a single newline. A blank line in
|
|
52
|
+
* between means the comment belongs to something else, which is what separates a declaration's own JSDoc from the
|
|
53
|
+
* file header block that precedes the first declaration in every file.
|
|
54
|
+
*/
|
|
55
|
+
function hasJSDocBefore(start: number): boolean {
|
|
56
|
+
for (const comment of comments) {
|
|
57
|
+
if (comment.range[1] > start) continue
|
|
58
|
+
|
|
59
|
+
if (comment.type !== "Block" || !comment.value.startsWith("*")) continue
|
|
60
|
+
|
|
61
|
+
const gap = text.slice(comment.range[1], start)
|
|
62
|
+
|
|
63
|
+
if (/^\s*$/.test(gap) && (gap.match(/\n/g) ?? []).length <= 1) return true
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return false
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function inScope(exported: boolean, screaming: boolean): boolean {
|
|
70
|
+
if (scope === "exported") return exported
|
|
71
|
+
|
|
72
|
+
if (scope === "screaming") return screaming
|
|
73
|
+
|
|
74
|
+
return exported || screaming
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
VariableDeclaration(node) {
|
|
79
|
+
if (node.kind !== "const") return
|
|
80
|
+
|
|
81
|
+
const exported = node.parent?.type === "ExportNamedDeclaration"
|
|
82
|
+
const container = exported ? node.parent! : node
|
|
83
|
+
|
|
84
|
+
// Module level only: the declaration (or its export wrapper) sits directly in Program.
|
|
85
|
+
if (container.parent?.type !== "Program") return
|
|
86
|
+
|
|
87
|
+
if (hasJSDocBefore(container.range[0])) return
|
|
88
|
+
|
|
89
|
+
for (const declarator of node.declarations ?? []) {
|
|
90
|
+
const id = declarator.id
|
|
91
|
+
|
|
92
|
+
if (id?.type !== "Identifier" || !id.name) continue
|
|
93
|
+
|
|
94
|
+
if (ignoreNames.has(id.name)) continue
|
|
95
|
+
|
|
96
|
+
const initializer = declarator.init
|
|
97
|
+
|
|
98
|
+
if (initializer && FUNCTION_INITIALIZERS.has(initializer.type)) continue
|
|
99
|
+
|
|
100
|
+
if (!inScope(exported, SCREAMING_CASE.test(id.name))) continue
|
|
101
|
+
|
|
102
|
+
context.report({
|
|
103
|
+
node: declarator,
|
|
104
|
+
message:
|
|
105
|
+
`\`${id.name}\` is ${exported ? "exported" : "a named constant"} but undocumented — add a ` +
|
|
106
|
+
`JSDoc block saying what the value means and where it came from.`,
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export default requireConstantDocRule
|
package/src/headers-plugin.ts
CHANGED
|
@@ -67,7 +67,7 @@ export const headerRule: Rule = {
|
|
|
67
67
|
const existing = parseInnerLines(leading)
|
|
68
68
|
const missing = headerLines.filter((line) => !existing.includes(line))
|
|
69
69
|
|
|
70
|
-
if (missing.length
|
|
70
|
+
if (!missing.length) return
|
|
71
71
|
|
|
72
72
|
const preserved = existing.filter((line) => !HEADER_TAG_PATTERN.test(line))
|
|
73
73
|
const block = buildBlock(preserved)
|