@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.
@@ -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. */
@@ -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,CAoElF;AAED,eAAe,kBAAkB,CAAA"}
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 ? { jsPlugins: ["@sister.software/oxlint-config/plugin"] } : {}),
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;AA2BjC,0DAA0D;AAC1D,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACpC,QAAQ;IACR,SAAS;IACT,mBAAmB;IACnB,iBAAiB;IACjB,aAAa;IACb,qBAAqB;CACrB,CAAA;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,kBAAkB,CAAC,UAA+B,EAAE;IACnE,MAAM,EACL,gBAAgB,GAAG,kBAAkB,EACrC,eAAe,GAAG,iBAAiB,EACnC,qBAAqB,GAAG,YAAY,EACpC,MAAM,GAAG,sBAAsB,EAC/B,KAAK,GAAG,KAAK,EACb,OAAO,GAAG,IAAI,EACd,OAAO,GAAG,IAAI,EACd,cAAc,GAAG,qBAAqB,EACtC,SAAS,GAAG,EAAE,GACd,GAAG,OAAO,CAAA;IAEX,MAAM,OAAO,GAAG,CAAC,YAAY,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IAE7E,MAAM,KAAK,GAA4B;QACtC,aAAa;QACb,MAAM,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;QAC/C,cAAc,EAAE,MAAM;QACtB,kBAAkB,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC;QACtC,WAAW,EAAE,KAAK;QAClB,UAAU,EAAE,KAAK;QACjB,gBAAgB,EAAE;YACjB,MAAM;YACN;gBACC,IAAI,EAAE,KAAK;gBACX,iBAAiB,EAAE,IAAI;gBACvB,YAAY,EAAE,KAAK;gBACnB,yBAAyB,EAAE,IAAI;gBAC/B,8BAA8B,EAAE,IAAI;gBACpC,uFAAuF;gBACvF,gFAAgF;gBAChF,iBAAiB,EAAE,MAAM;gBACzB,kBAAkB,EAAE,IAAI;aACxB;SACD;QAED,2EAA2E;QAC3E,2BAA2B,EAAE,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,wBAAwB,EAAE,CAAC;QAChF,sBAAsB,EAAE,KAAK;QAC7B,+BAA+B,EAAE,KAAK;QACtC,4BAA4B,EAAE,KAAK;QACnC,2BAA2B,EAAE,KAAK;QAClC,kCAAkC,EAAE,KAAK;QACzC,4BAA4B,EAAE,KAAK;QACnC,+BAA+B,EAAE,KAAK;KACtC,CAAA;IAED,IAAI,OAAO,EAAE,CAAC;QACb,0FAA0F;QAC1F,oFAAoF;QACpF,KAAK,CAAC,qCAAqC,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,EAAE,CAAC,CAAA;IAC7G,CAAC;IAED,IAAI,OAAO,EAAE,CAAC;QACb,0EAA0E;QAC1E,KAAK,CAAC,+BAA+B,CAAC,GAAG,MAAM,CAAA;IAChD,CAAC;IAED,OAAO;QACN,OAAO;QACP,GAAG,CAAC,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,uCAAuC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACvF,UAAU,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE;QACpC,cAAc;QACd,KAAK;QACL,SAAS,EAAE,sBAAsB,CAAC,gBAAgB,CAAC;QACnD,GAAG,SAAS;KACZ,CAAA;AACF,CAAC;AAED,eAAe,kBAAkB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -15,7 +15,11 @@ export interface AstNode {
15
15
  type: string;
16
16
  range: [number, number];
17
17
  parent?: AstNode;
18
- body?: AstNode[];
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;CAChB;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"}
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"}
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAE/C,QAAA,MAAM,oBAAoB,EAAE,MAM3B,CAAA;AAED,eAAe,oBAAoB,CAAA"}
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;AAGjD,MAAM,oBAAoB,GAAW;IACpC,IAAI,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE;IACjC,KAAK,EAAE;QACN,qBAAqB,EAAE,UAAU;QACjC,eAAe,EAAE,WAAW;KAC5B;CACD,CAAA;AAED,eAAe,oBAAoB,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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sister.software/oxlint-config",
3
- "version": "9.1.0",
3
+ "version": "9.3.0",
4
4
  "description": "Sister Software's oxlint config",
5
5
  "license": "AGPL-3.0",
6
6
  "author": "teffen@sister.software",
@@ -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 ? { jsPlugins: ["@sister.software/oxlint-config/plugin"] } : {}),
141
+ ...(headers || padding || braces || restrictProcessGlobals
142
+ ? { jsPlugins: ["@sister.software/oxlint-config/plugin"] }
143
+ : {}),
123
144
  categories: { correctness: "error" },
124
145
  ignorePatterns,
125
146
  rules,
@@ -17,7 +17,11 @@ export interface AstNode {
17
17
  type: string
18
18
  range: [number, number]
19
19
  parent?: AstNode
20
- body?: AstNode[]
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