@sister.software/oxlint-config 9.1.0 → 9.2.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,8 @@ 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;
26
28
  /** Override the default ignore patterns. */
27
29
  ignorePatterns?: string[];
28
30
  /** 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,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,CA0ElF;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, ignorePatterns = DefaultIgnorePatterns, overrides = {}, } = options;
34
34
  const plugins = ["typescript", "unicorn", "oxc", ...(react ? ["react"] : [])];
35
35
  const rules = {
36
36
  // JavaScript
@@ -72,9 +72,13 @@ 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
+ }
75
79
  return {
76
80
  plugins,
77
- ...(headers || padding ? { jsPlugins: ["@sister.software/oxlint-config/plugin"] } : {}),
81
+ ...(headers || padding || braces ? { jsPlugins: ["@sister.software/oxlint-config/plugin"] } : {}),
78
82
  categories: { correctness: "error" },
79
83
  ignorePatterns,
80
84
  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":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAA;AAE1D,cAAc,mBAAmB,CAAA;AA6BjC,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,MAAM,GAAG,IAAI,EACb,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,IAAI,MAAM,EAAE,CAAC;QACZ,0EAA0E;QAC1E,KAAK,CAAC,gCAAgC,CAAC,GAAG,MAAM,CAAA;IACjD,CAAC;IAED,OAAO;QACN,OAAO;QACP,GAAG,CAAC,OAAO,IAAI,OAAO,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,uCAAuC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACjG,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"}
@@ -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;AAE/C,QAAA,MAAM,oBAAoB,EAAE,MAO3B,CAAA;AAED,eAAe,oBAAoB,CAAA"}
package/out/plugin.js CHANGED
@@ -5,6 +5,7 @@
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";
10
11
  const sisterSoftwarePlugin = {
@@ -12,6 +13,7 @@ const sisterSoftwarePlugin = {
12
13
  rules: {
13
14
  "require-file-header": headerRule,
14
15
  "padding-lines": paddingRule,
16
+ "require-braces": bracesRule,
15
17
  },
16
18
  };
17
19
  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;AAGjD,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;KAC5B;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.2.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,8 @@ 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
31
33
  /** Override the default ignore patterns. */
32
34
  ignorePatterns?: string[]
33
35
  /** Extra config deep-merged last; an escape hatch for per-repo tweaks. */
@@ -67,6 +69,7 @@ export function createOxlintConfig(options: OxlintConfigOptions = {}): OxlintCon
67
69
  react = false,
68
70
  headers = true,
69
71
  padding = true,
72
+ braces = true,
70
73
  ignorePatterns = DefaultIgnorePatterns,
71
74
  overrides = {},
72
75
  } = options
@@ -117,9 +120,14 @@ export function createOxlintConfig(options: OxlintConfigOptions = {}): OxlintCon
117
120
  rules["sister-software/padding-lines"] = "warn"
118
121
  }
119
122
 
123
+ if (braces) {
124
+ // Warning severity: a stylistic nudge (auto-fixable), not a hard CI gate.
125
+ rules["sister-software/require-braces"] = "warn"
126
+ }
127
+
120
128
  return {
121
129
  plugins,
122
- ...(headers || padding ? { jsPlugins: ["@sister.software/oxlint-config/plugin"] } : {}),
130
+ ...(headers || padding || braces ? { jsPlugins: ["@sister.software/oxlint-config/plugin"] } : {}),
123
131
  categories: { correctness: "error" },
124
132
  ignorePatterns,
125
133
  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,6 +6,7 @@
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"
@@ -15,6 +16,7 @@ const sisterSoftwarePlugin: Plugin = {
15
16
  rules: {
16
17
  "require-file-header": headerRule,
17
18
  "padding-lines": paddingRule,
19
+ "require-braces": bracesRule,
18
20
  },
19
21
  }
20
22