@sister.software/oxlint-config 9.1.0 → 9.3.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/out/braces-plugin.d.ts +14 -0
- package/out/braces-plugin.d.ts.map +1 -0
- package/out/braces-plugin.js +80 -0
- package/out/braces-plugin.js.map +1 -0
- package/out/index.d.ts +7 -0
- package/out/index.d.ts.map +1 -1
- package/out/index.js +12 -2
- package/out/index.js.map +1 -1
- package/out/plugin-types.d.ts +5 -1
- package/out/plugin-types.d.ts.map +1 -1
- package/out/plugin.d.ts.map +1 -1
- package/out/plugin.js +4 -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/package.json +1 -1
- package/src/braces-plugin.ts +89 -0
- package/src/index.ts +22 -1
- package/src/plugin-types.ts +5 -1
- package/src/plugin.ts +4 -0
- package/src/process-globals-plugin.ts +68 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
* @file A require-braces rule, authored as an oxlint JS plugin (ESLint v9-compatible API). It
|
|
6
|
+
* requires braces around single-statement `if`/`else`/`for`/`while`/`do` bodies that do work
|
|
7
|
+
* (assignments, calls, …) and autofixes by wrapping them — but leaves a bare control-flow terminal
|
|
8
|
+
* (`return`/`throw`/`break`/`continue`) alone (braces optional). Unlike the built-in `curly` rule,
|
|
9
|
+
* which can't exempt those.
|
|
10
|
+
*/
|
|
11
|
+
import type { Rule } from "./plugin-types.js";
|
|
12
|
+
export declare const bracesRule: Rule;
|
|
13
|
+
export default bracesRule;
|
|
14
|
+
//# sourceMappingURL=braces-plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"braces-plugin.d.ts","sourceRoot":"","sources":["../src/braces-plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAW,IAAI,EAAE,MAAM,mBAAmB,CAAA;AAgBtD,eAAO,MAAM,UAAU,EAAE,IA2DxB,CAAA;AAED,eAAe,UAAU,CAAA"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
* @file A require-braces rule, authored as an oxlint JS plugin (ESLint v9-compatible API). It
|
|
6
|
+
* requires braces around single-statement `if`/`else`/`for`/`while`/`do` bodies that do work
|
|
7
|
+
* (assignments, calls, …) and autofixes by wrapping them — but leaves a bare control-flow terminal
|
|
8
|
+
* (`return`/`throw`/`break`/`continue`) alone (braces optional). Unlike the built-in `curly` rule,
|
|
9
|
+
* which can't exempt those.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Body node types that may stay brace-less: an existing block; a bare control-flow terminal
|
|
13
|
+
* (`return`/`throw`/`break`/`continue`, exempt by house style); or an empty statement (a different concern —
|
|
14
|
+
* `no-empty`).
|
|
15
|
+
*/
|
|
16
|
+
const EXEMPT_BODY_TYPES = new Set([
|
|
17
|
+
"BlockStatement",
|
|
18
|
+
"ReturnStatement",
|
|
19
|
+
"ThrowStatement",
|
|
20
|
+
"BreakStatement",
|
|
21
|
+
"ContinueStatement",
|
|
22
|
+
"EmptyStatement",
|
|
23
|
+
]);
|
|
24
|
+
export const bracesRule = {
|
|
25
|
+
meta: {
|
|
26
|
+
name: "require-braces",
|
|
27
|
+
type: "layout",
|
|
28
|
+
fixable: "code",
|
|
29
|
+
schema: [{ type: "object", additionalProperties: true }],
|
|
30
|
+
},
|
|
31
|
+
create(context) {
|
|
32
|
+
const sourceCode = context.sourceCode ?? context.getSourceCode();
|
|
33
|
+
function check(body) {
|
|
34
|
+
if (!body || EXEMPT_BODY_TYPES.has(body.type))
|
|
35
|
+
return;
|
|
36
|
+
// `else if (…)` — the alternate is itself an `if`; its own body is visited separately.
|
|
37
|
+
if (body.type === "IfStatement")
|
|
38
|
+
return;
|
|
39
|
+
const inner = sourceCode.getText().slice(body.range[0], body.range[1]);
|
|
40
|
+
context.report({
|
|
41
|
+
node: body,
|
|
42
|
+
message: "Expected braces around this single-statement body.",
|
|
43
|
+
fix(fixer) {
|
|
44
|
+
// Wrap inline; oxfmt expands it to a formatted multi-line block afterward.
|
|
45
|
+
return fixer.replaceTextRange(body.range, `{ ${inner} }`);
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
IfStatement(node) {
|
|
51
|
+
check(node.consequent);
|
|
52
|
+
check(node.alternate);
|
|
53
|
+
},
|
|
54
|
+
ForStatement(node) {
|
|
55
|
+
checkLoopBody(node);
|
|
56
|
+
},
|
|
57
|
+
ForInStatement(node) {
|
|
58
|
+
checkLoopBody(node);
|
|
59
|
+
},
|
|
60
|
+
ForOfStatement(node) {
|
|
61
|
+
checkLoopBody(node);
|
|
62
|
+
},
|
|
63
|
+
WhileStatement(node) {
|
|
64
|
+
checkLoopBody(node);
|
|
65
|
+
},
|
|
66
|
+
DoWhileStatement(node) {
|
|
67
|
+
checkLoopBody(node);
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
function checkLoopBody(node) {
|
|
71
|
+
const body = node.body;
|
|
72
|
+
// A loop body is a single statement (never a statement-list array).
|
|
73
|
+
if (body && !Array.isArray(body)) {
|
|
74
|
+
check(body);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
export default bracesRule;
|
|
80
|
+
//# sourceMappingURL=braces-plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"braces-plugin.js","sourceRoot":"","sources":["../src/braces-plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH;;;;GAIG;AACH,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC;IACjC,gBAAgB;IAChB,iBAAiB;IACjB,gBAAgB;IAChB,gBAAgB;IAChB,mBAAmB;IACnB,gBAAgB;CAChB,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,UAAU,GAAS;IAC/B,IAAI,EAAE;QACL,IAAI,EAAE,gBAAgB;QACtB,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,MAAM;QACf,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,oBAAoB,EAAE,IAAI,EAAE,CAAC;KACxD;IACD,MAAM,CAAC,OAAO;QACb,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,aAAc,EAAE,CAAA;QAEjE,SAAS,KAAK,CAAC,IAAgC;YAC9C,IAAI,CAAC,IAAI,IAAI,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;gBAAE,OAAM;YAErD,uFAAuF;YACvF,IAAI,IAAI,CAAC,IAAI,KAAK,aAAa;gBAAE,OAAM;YAEvC,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;YAEtE,OAAO,CAAC,MAAM,CAAC;gBACd,IAAI,EAAE,IAAI;gBACV,OAAO,EAAE,oDAAoD;gBAC7D,GAAG,CAAC,KAAK;oBACR,2EAA2E;oBAC3E,OAAO,KAAK,CAAC,gBAAgB,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC,CAAA;gBAC1D,CAAC;aACD,CAAC,CAAA;QACH,CAAC;QAED,OAAO;YACN,WAAW,CAAC,IAAI;gBACf,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;gBACtB,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;YACtB,CAAC;YACD,YAAY,CAAC,IAAI;gBAChB,aAAa,CAAC,IAAI,CAAC,CAAA;YACpB,CAAC;YACD,cAAc,CAAC,IAAI;gBAClB,aAAa,CAAC,IAAI,CAAC,CAAA;YACpB,CAAC;YACD,cAAc,CAAC,IAAI;gBAClB,aAAa,CAAC,IAAI,CAAC,CAAA;YACpB,CAAC;YACD,cAAc,CAAC,IAAI;gBAClB,aAAa,CAAC,IAAI,CAAC,CAAA;YACpB,CAAC;YACD,gBAAgB,CAAC,IAAI;gBACpB,aAAa,CAAC,IAAI,CAAC,CAAA;YACpB,CAAC;SACD,CAAA;QAED,SAAS,aAAa,CAAC,IAAa;YACnC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAA;YAEtB,oEAAoE;YACpE,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;gBAClC,KAAK,CAAC,IAAI,CAAC,CAAA;YACZ,CAAC;QACF,CAAC;IACF,CAAC;CACD,CAAA;AAED,eAAe,UAAU,CAAA"}
|
package/out/index.d.ts
CHANGED
|
@@ -23,6 +23,13 @@ export interface OxlintConfigOptions {
|
|
|
23
23
|
headers?: boolean;
|
|
24
24
|
/** Require a blank line before `return`/block-like statements (on by default). */
|
|
25
25
|
padding?: boolean;
|
|
26
|
+
/** Require braces around single-statement work bodies (bare `return` exempt; on by default). */
|
|
27
|
+
braces?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Forbid direct `process.env` / `process.argv` access (off by default). Turn on once the project funnels those reads
|
|
30
|
+
* through blessed helpers, which disable `sister-software/no-process-globals`.
|
|
31
|
+
*/
|
|
32
|
+
restrictProcessGlobals?: boolean;
|
|
26
33
|
/** Override the default ignore patterns. */
|
|
27
34
|
ignorePatterns?: string[];
|
|
28
35
|
/** Extra config deep-merged last; an escape hatch for per-repo tweaks. */
|
package/out/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,cAAc,mBAAmB,CAAA;AAEjC,4FAA4F;AAC5F,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAElD,8CAA8C;AAC9C,MAAM,WAAW,mBAAmB;IACnC,4FAA4F;IAC5F,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,sDAAsD;IACtD,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,6DAA6D;IAC7D,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,qDAAqD;IACrD,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,sEAAsE;IACtE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,kFAAkF;IAClF,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,4CAA4C;IAC5C,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;IACzB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,YAAY,CAAA;CACxB;AAED,0DAA0D;AAC1D,eAAO,MAAM,qBAAqB,UAOjC,CAAA;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,mBAAwB,GAAG,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,cAAc,mBAAmB,CAAA;AAEjC,4FAA4F;AAC5F,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAElD,8CAA8C;AAC9C,MAAM,WAAW,mBAAmB;IACnC,4FAA4F;IAC5F,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,sDAAsD;IACtD,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,6DAA6D;IAC7D,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,qDAAqD;IACrD,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,sEAAsE;IACtE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,kFAAkF;IAClF,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,gGAAgG;IAChG,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB;;;OAGG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAA;IAChC,4CAA4C;IAC5C,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;IACzB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,YAAY,CAAA;CACxB;AAED,0DAA0D;AAC1D,eAAO,MAAM,qBAAqB,UAOjC,CAAA;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,mBAAwB,GAAG,YAAY,CAkFlF;AAED,eAAe,kBAAkB,CAAA"}
|
package/out/index.js
CHANGED
|
@@ -30,7 +30,7 @@ export const DefaultIgnorePatterns = [
|
|
|
30
30
|
* @returns A complete oxlint config object (no `extends` required).
|
|
31
31
|
*/
|
|
32
32
|
export function createOxlintConfig(options = {}) {
|
|
33
|
-
const { packageNamespace = "@sister.software", copyrightHolder = "Sister Software", spdxLicenseIdentifier = "UNLICENSED", author = "Teffen Ellis, et al.", react = false, headers = true, padding = true, ignorePatterns = DefaultIgnorePatterns, overrides = {}, } = options;
|
|
33
|
+
const { packageNamespace = "@sister.software", copyrightHolder = "Sister Software", spdxLicenseIdentifier = "UNLICENSED", author = "Teffen Ellis, et al.", react = false, headers = true, padding = true, braces = true, restrictProcessGlobals = false, ignorePatterns = DefaultIgnorePatterns, overrides = {}, } = options;
|
|
34
34
|
const plugins = ["typescript", "unicorn", "oxc", ...(react ? ["react"] : [])];
|
|
35
35
|
const rules = {
|
|
36
36
|
// JavaScript
|
|
@@ -72,9 +72,19 @@ export function createOxlintConfig(options = {}) {
|
|
|
72
72
|
// Warning severity: a stylistic nudge (auto-fixable), not a hard CI gate.
|
|
73
73
|
rules["sister-software/padding-lines"] = "warn";
|
|
74
74
|
}
|
|
75
|
+
if (braces) {
|
|
76
|
+
// Warning severity: a stylistic nudge (auto-fixable), not a hard CI gate.
|
|
77
|
+
rules["sister-software/require-braces"] = "warn";
|
|
78
|
+
}
|
|
79
|
+
if (restrictProcessGlobals) {
|
|
80
|
+
// Error severity: a governance gate — direct env/argv access should fail the lint.
|
|
81
|
+
rules["sister-software/no-process-globals"] = "error";
|
|
82
|
+
}
|
|
75
83
|
return {
|
|
76
84
|
plugins,
|
|
77
|
-
...(headers || padding
|
|
85
|
+
...(headers || padding || braces || restrictProcessGlobals
|
|
86
|
+
? { jsPlugins: ["@sister.software/oxlint-config/plugin"] }
|
|
87
|
+
: {}),
|
|
78
88
|
categories: { correctness: "error" },
|
|
79
89
|
ignorePatterns,
|
|
80
90
|
rules,
|
package/out/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAA;AAE1D,cAAc,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/out/plugin-types.d.ts
CHANGED
|
@@ -15,7 +15,11 @@ export interface AstNode {
|
|
|
15
15
|
type: string;
|
|
16
16
|
range: [number, number];
|
|
17
17
|
parent?: AstNode;
|
|
18
|
-
body
|
|
18
|
+
/** A statement-list body (block/program) is an array; a loop body is a single statement. */
|
|
19
|
+
body?: AstNode[] | AstNode;
|
|
20
|
+
/** `if`/`else` branches, for the require-braces rule. */
|
|
21
|
+
consequent?: AstNode;
|
|
22
|
+
alternate?: AstNode | null;
|
|
19
23
|
}
|
|
20
24
|
export interface SourceCode {
|
|
21
25
|
getText(): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin-types.d.ts","sourceRoot":"","sources":["../src/plugin-types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,OAAO;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACvB;AAED,yEAAyE;AACzE,MAAM,WAAW,OAAO;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACvB,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,IAAI,CAAC,EAAE,OAAO,EAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"plugin-types.d.ts","sourceRoot":"","sources":["../src/plugin-types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,OAAO;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACvB;AAED,yEAAyE;AACzE,MAAM,WAAW,OAAO;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACvB,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,4FAA4F;IAC5F,IAAI,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAA;IAC1B,yDAAyD;IACzD,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,SAAS,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;CAC1B;AAED,MAAM,WAAW,UAAU;IAC1B,OAAO,IAAI,MAAM,CAAA;IACjB,cAAc,IAAI,OAAO,EAAE,CAAA;CAC3B;AAED,MAAM,WAAW,KAAK;IACrB,qBAAqB,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;IACrE,oBAAoB,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;IACpE,gBAAgB,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;CAChE;AAED,MAAM,WAAW,WAAW;IAC3B,OAAO,EAAE,OAAO,EAAE,CAAA;IAClB,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,aAAa,CAAC,IAAI,UAAU,CAAA;IAC5B,MAAM,CAAC,UAAU,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAA;KAAE,GAAG,IAAI,CAAA;CACzF;AAED,MAAM,WAAW,IAAI;IACpB,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,GAAG,YAAY,CAAC;QAAC,MAAM,EAAE,OAAO,EAAE,CAAA;KAAE,CAAA;IACxF,MAAM,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC,CAAA;CACrE;AAED,MAAM,WAAW,MAAM;IACtB,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IACtB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;CAC3B"}
|
package/out/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAG/C,QAAA,MAAM,oBAAoB,EAAE,MAQ3B,CAAA;AAED,eAAe,oBAAoB,CAAA"}
|
package/out/plugin.js
CHANGED
|
@@ -5,13 +5,17 @@
|
|
|
5
5
|
* @file The bundled Sister Software oxlint JS plugin. A single plugin (oxlint requires unique plugin
|
|
6
6
|
* names) exposing all of Sister Software's custom rules under the `sister-software/` namespace.
|
|
7
7
|
*/
|
|
8
|
+
import { bracesRule } from "./braces-plugin.js";
|
|
8
9
|
import { headerRule } from "./headers-plugin.js";
|
|
9
10
|
import { paddingRule } from "./padding-plugin.js";
|
|
11
|
+
import { noProcessGlobalsRule } from "./process-globals-plugin.js";
|
|
10
12
|
const sisterSoftwarePlugin = {
|
|
11
13
|
meta: { name: "sister-software" },
|
|
12
14
|
rules: {
|
|
13
15
|
"require-file-header": headerRule,
|
|
14
16
|
"padding-lines": paddingRule,
|
|
17
|
+
"require-braces": bracesRule,
|
|
18
|
+
"no-process-globals": noProcessGlobalsRule,
|
|
15
19
|
},
|
|
16
20
|
};
|
|
17
21
|
export default sisterSoftwarePlugin;
|
package/out/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAEjD,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAA;AAElE,MAAM,oBAAoB,GAAW;IACpC,IAAI,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE;IACjC,KAAK,EAAE;QACN,qBAAqB,EAAE,UAAU;QACjC,eAAe,EAAE,WAAW;QAC5B,gBAAgB,EAAE,UAAU;QAC5B,oBAAoB,EAAE,oBAAoB;KAC1C;CACD,CAAA;AAED,eAAe,oBAAoB,CAAA"}
|
|
@@ -0,0 +1,13 @@
|
|
|
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
|
+
import type { Rule } from "./plugin-types.js";
|
|
11
|
+
export declare const noProcessGlobalsRule: Rule;
|
|
12
|
+
export default noProcessGlobalsRule;
|
|
13
|
+
//# sourceMappingURL=process-globals-plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-globals-plugin.d.ts","sourceRoot":"","sources":["../src/process-globals-plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAW,IAAI,EAAE,MAAM,mBAAmB,CAAA;AA+BtD,eAAO,MAAM,oBAAoB,EAAE,IAwBlC,CAAA;AAED,eAAe,oBAAoB,CAAA"}
|
|
@@ -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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
* @file A require-braces rule, authored as an oxlint JS plugin (ESLint v9-compatible API). It
|
|
6
|
+
* requires braces around single-statement `if`/`else`/`for`/`while`/`do` bodies that do work
|
|
7
|
+
* (assignments, calls, …) and autofixes by wrapping them — but leaves a bare control-flow terminal
|
|
8
|
+
* (`return`/`throw`/`break`/`continue`) alone (braces optional). Unlike the built-in `curly` rule,
|
|
9
|
+
* which can't exempt those.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { AstNode, Rule } from "./plugin-types.js"
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Body node types that may stay brace-less: an existing block; a bare control-flow terminal
|
|
16
|
+
* (`return`/`throw`/`break`/`continue`, exempt by house style); or an empty statement (a different concern —
|
|
17
|
+
* `no-empty`).
|
|
18
|
+
*/
|
|
19
|
+
const EXEMPT_BODY_TYPES = new Set([
|
|
20
|
+
"BlockStatement",
|
|
21
|
+
"ReturnStatement",
|
|
22
|
+
"ThrowStatement",
|
|
23
|
+
"BreakStatement",
|
|
24
|
+
"ContinueStatement",
|
|
25
|
+
"EmptyStatement",
|
|
26
|
+
])
|
|
27
|
+
|
|
28
|
+
export const bracesRule: Rule = {
|
|
29
|
+
meta: {
|
|
30
|
+
name: "require-braces",
|
|
31
|
+
type: "layout",
|
|
32
|
+
fixable: "code",
|
|
33
|
+
schema: [{ type: "object", additionalProperties: true }],
|
|
34
|
+
},
|
|
35
|
+
create(context) {
|
|
36
|
+
const sourceCode = context.sourceCode ?? context.getSourceCode!()
|
|
37
|
+
|
|
38
|
+
function check(body: AstNode | null | undefined) {
|
|
39
|
+
if (!body || EXEMPT_BODY_TYPES.has(body.type)) return
|
|
40
|
+
|
|
41
|
+
// `else if (…)` — the alternate is itself an `if`; its own body is visited separately.
|
|
42
|
+
if (body.type === "IfStatement") return
|
|
43
|
+
|
|
44
|
+
const inner = sourceCode.getText().slice(body.range[0], body.range[1])
|
|
45
|
+
|
|
46
|
+
context.report({
|
|
47
|
+
node: body,
|
|
48
|
+
message: "Expected braces around this single-statement body.",
|
|
49
|
+
fix(fixer) {
|
|
50
|
+
// Wrap inline; oxfmt expands it to a formatted multi-line block afterward.
|
|
51
|
+
return fixer.replaceTextRange(body.range, `{ ${inner} }`)
|
|
52
|
+
},
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
IfStatement(node) {
|
|
58
|
+
check(node.consequent)
|
|
59
|
+
check(node.alternate)
|
|
60
|
+
},
|
|
61
|
+
ForStatement(node) {
|
|
62
|
+
checkLoopBody(node)
|
|
63
|
+
},
|
|
64
|
+
ForInStatement(node) {
|
|
65
|
+
checkLoopBody(node)
|
|
66
|
+
},
|
|
67
|
+
ForOfStatement(node) {
|
|
68
|
+
checkLoopBody(node)
|
|
69
|
+
},
|
|
70
|
+
WhileStatement(node) {
|
|
71
|
+
checkLoopBody(node)
|
|
72
|
+
},
|
|
73
|
+
DoWhileStatement(node) {
|
|
74
|
+
checkLoopBody(node)
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function checkLoopBody(node: AstNode) {
|
|
79
|
+
const body = node.body
|
|
80
|
+
|
|
81
|
+
// A loop body is a single statement (never a statement-list array).
|
|
82
|
+
if (body && !Array.isArray(body)) {
|
|
83
|
+
check(body)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export default bracesRule
|
package/src/index.ts
CHANGED
|
@@ -28,6 +28,13 @@ export interface OxlintConfigOptions {
|
|
|
28
28
|
headers?: boolean
|
|
29
29
|
/** Require a blank line before `return`/block-like statements (on by default). */
|
|
30
30
|
padding?: boolean
|
|
31
|
+
/** Require braces around single-statement work bodies (bare `return` exempt; on by default). */
|
|
32
|
+
braces?: boolean
|
|
33
|
+
/**
|
|
34
|
+
* Forbid direct `process.env` / `process.argv` access (off by default). Turn on once the project funnels those reads
|
|
35
|
+
* through blessed helpers, which disable `sister-software/no-process-globals`.
|
|
36
|
+
*/
|
|
37
|
+
restrictProcessGlobals?: boolean
|
|
31
38
|
/** Override the default ignore patterns. */
|
|
32
39
|
ignorePatterns?: string[]
|
|
33
40
|
/** Extra config deep-merged last; an escape hatch for per-repo tweaks. */
|
|
@@ -67,6 +74,8 @@ export function createOxlintConfig(options: OxlintConfigOptions = {}): OxlintCon
|
|
|
67
74
|
react = false,
|
|
68
75
|
headers = true,
|
|
69
76
|
padding = true,
|
|
77
|
+
braces = true,
|
|
78
|
+
restrictProcessGlobals = false,
|
|
70
79
|
ignorePatterns = DefaultIgnorePatterns,
|
|
71
80
|
overrides = {},
|
|
72
81
|
} = options
|
|
@@ -117,9 +126,21 @@ export function createOxlintConfig(options: OxlintConfigOptions = {}): OxlintCon
|
|
|
117
126
|
rules["sister-software/padding-lines"] = "warn"
|
|
118
127
|
}
|
|
119
128
|
|
|
129
|
+
if (braces) {
|
|
130
|
+
// Warning severity: a stylistic nudge (auto-fixable), not a hard CI gate.
|
|
131
|
+
rules["sister-software/require-braces"] = "warn"
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (restrictProcessGlobals) {
|
|
135
|
+
// Error severity: a governance gate — direct env/argv access should fail the lint.
|
|
136
|
+
rules["sister-software/no-process-globals"] = "error"
|
|
137
|
+
}
|
|
138
|
+
|
|
120
139
|
return {
|
|
121
140
|
plugins,
|
|
122
|
-
...(headers || padding
|
|
141
|
+
...(headers || padding || braces || restrictProcessGlobals
|
|
142
|
+
? { jsPlugins: ["@sister.software/oxlint-config/plugin"] }
|
|
143
|
+
: {}),
|
|
123
144
|
categories: { correctness: "error" },
|
|
124
145
|
ignorePatterns,
|
|
125
146
|
rules,
|
package/src/plugin-types.ts
CHANGED
|
@@ -17,7 +17,11 @@ export interface AstNode {
|
|
|
17
17
|
type: string
|
|
18
18
|
range: [number, number]
|
|
19
19
|
parent?: AstNode
|
|
20
|
-
body
|
|
20
|
+
/** A statement-list body (block/program) is an array; a loop body is a single statement. */
|
|
21
|
+
body?: AstNode[] | AstNode
|
|
22
|
+
/** `if`/`else` branches, for the require-braces rule. */
|
|
23
|
+
consequent?: AstNode
|
|
24
|
+
alternate?: AstNode | null
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
export interface SourceCode {
|
package/src/plugin.ts
CHANGED
|
@@ -6,15 +6,19 @@
|
|
|
6
6
|
* names) exposing all of Sister Software's custom rules under the `sister-software/` namespace.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { bracesRule } from "./braces-plugin.js"
|
|
9
10
|
import { headerRule } from "./headers-plugin.js"
|
|
10
11
|
import { paddingRule } from "./padding-plugin.js"
|
|
11
12
|
import type { Plugin } from "./plugin-types.js"
|
|
13
|
+
import { noProcessGlobalsRule } from "./process-globals-plugin.js"
|
|
12
14
|
|
|
13
15
|
const sisterSoftwarePlugin: Plugin = {
|
|
14
16
|
meta: { name: "sister-software" },
|
|
15
17
|
rules: {
|
|
16
18
|
"require-file-header": headerRule,
|
|
17
19
|
"padding-lines": paddingRule,
|
|
20
|
+
"require-braces": bracesRule,
|
|
21
|
+
"no-process-globals": noProcessGlobalsRule,
|
|
18
22
|
},
|
|
19
23
|
}
|
|
20
24
|
|
|
@@ -0,0 +1,68 @@
|
|
|
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
|
+
|
|
11
|
+
import type { AstNode, Rule } from "./plugin-types.js"
|
|
12
|
+
|
|
13
|
+
/** `process` members that must be reached through a blessed helper, not accessed directly. */
|
|
14
|
+
const RESTRICTED_MEMBERS = new Set(["env", "argv"])
|
|
15
|
+
|
|
16
|
+
interface Identifierish extends AstNode {
|
|
17
|
+
name?: string
|
|
18
|
+
value?: unknown
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface MemberNode extends AstNode {
|
|
22
|
+
object?: Identifierish
|
|
23
|
+
property?: Identifierish
|
|
24
|
+
computed?: boolean
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** The accessed member name for `process.env` (identifier) or `process["env"]` (string literal). */
|
|
28
|
+
function accessedMember(node: MemberNode): string | null {
|
|
29
|
+
const property = node.property
|
|
30
|
+
|
|
31
|
+
if (!property) return null
|
|
32
|
+
|
|
33
|
+
if (!node.computed && property.type === "Identifier") return property.name ?? null
|
|
34
|
+
|
|
35
|
+
if (node.computed && (property.type === "Literal" || property.type === "StringLiteral")) {
|
|
36
|
+
return typeof property.value === "string" ? property.value : null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return null
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const noProcessGlobalsRule: Rule = {
|
|
43
|
+
meta: {
|
|
44
|
+
name: "no-process-globals",
|
|
45
|
+
type: "problem",
|
|
46
|
+
schema: [{ type: "object", additionalProperties: true }],
|
|
47
|
+
},
|
|
48
|
+
create(context) {
|
|
49
|
+
return {
|
|
50
|
+
MemberExpression(node) {
|
|
51
|
+
const member = node as MemberNode
|
|
52
|
+
|
|
53
|
+
if (member.object?.type !== "Identifier" || member.object.name !== "process") return
|
|
54
|
+
|
|
55
|
+
const name = accessedMember(member)
|
|
56
|
+
|
|
57
|
+
if (!name || !RESTRICTED_MEMBERS.has(name)) return
|
|
58
|
+
|
|
59
|
+
context.report({
|
|
60
|
+
node,
|
|
61
|
+
message: `Direct \`process.${name}\` access is restricted — read it through the project's blessed helper (disable \`sister-software/no-process-globals\` there).`,
|
|
62
|
+
})
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export default noProcessGlobalsRule
|