@sister.software/oxlint-config 10.0.0 → 12.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.
Files changed (82) hide show
  1. package/README.md +169 -0
  2. package/out/acronym-case-plugin.d.ts +80 -0
  3. package/out/acronym-case-plugin.d.ts.map +1 -0
  4. package/out/acronym-case-plugin.js +272 -0
  5. package/out/acronym-case-plugin.js.map +1 -0
  6. package/out/browser-globals.d.ts +15 -0
  7. package/out/browser-globals.d.ts.map +1 -0
  8. package/out/browser-globals.js +76 -0
  9. package/out/browser-globals.js.map +1 -0
  10. package/out/console-padding-plugin.d.ts +16 -0
  11. package/out/console-padding-plugin.d.ts.map +1 -0
  12. package/out/console-padding-plugin.js +68 -0
  13. package/out/console-padding-plugin.js.map +1 -0
  14. package/out/constant-doc-plugin.d.ts +9 -3
  15. package/out/constant-doc-plugin.d.ts.map +1 -1
  16. package/out/constant-doc-plugin.js +6 -2
  17. package/out/constant-doc-plugin.js.map +1 -1
  18. package/out/headers-plugin.d.ts +6 -2
  19. package/out/headers-plugin.d.ts.map +1 -1
  20. package/out/headers-plugin.js +3 -1
  21. package/out/headers-plugin.js.map +1 -1
  22. package/out/index.d.ts +123 -27
  23. package/out/index.d.ts.map +1 -1
  24. package/out/index.js +63 -5
  25. package/out/index.js.map +1 -1
  26. package/out/jsdoc-plugin.d.ts +16 -0
  27. package/out/jsdoc-plugin.d.ts.map +1 -0
  28. package/out/jsdoc-plugin.js +68 -0
  29. package/out/jsdoc-plugin.js.map +1 -0
  30. package/out/length-truthiness-plugin.d.ts.map +1 -1
  31. package/out/length-truthiness-plugin.js +15 -5
  32. package/out/length-truthiness-plugin.js.map +1 -1
  33. package/out/multiline-statement-plugin.d.ts +18 -0
  34. package/out/multiline-statement-plugin.d.ts.map +1 -0
  35. package/out/multiline-statement-plugin.js +93 -0
  36. package/out/multiline-statement-plugin.js.map +1 -0
  37. package/out/padding-plugin.d.ts +4 -3
  38. package/out/padding-plugin.d.ts.map +1 -1
  39. package/out/padding-plugin.js +44 -27
  40. package/out/padding-plugin.js.map +1 -1
  41. package/out/padding-utils.d.ts +34 -0
  42. package/out/padding-utils.d.ts.map +1 -0
  43. package/out/padding-utils.js +50 -0
  44. package/out/padding-utils.js.map +1 -0
  45. package/out/plugin-types.d.ts +75 -13
  46. package/out/plugin-types.d.ts.map +1 -1
  47. package/out/plugin.d.ts.map +1 -1
  48. package/out/plugin.js +13 -0
  49. package/out/plugin.js.map +1 -1
  50. package/out/process-globals-plugin.d.ts.map +1 -1
  51. package/out/process-globals-plugin.js +6 -2
  52. package/out/process-globals-plugin.js.map +1 -1
  53. package/out/restrictions.d.ts +9 -3
  54. package/out/restrictions.d.ts.map +1 -1
  55. package/out/restrictions.js +4 -70
  56. package/out/restrictions.js.map +1 -1
  57. package/out/section-marker-plugin.d.ts +25 -0
  58. package/out/section-marker-plugin.d.ts.map +1 -0
  59. package/out/section-marker-plugin.js +234 -0
  60. package/out/section-marker-plugin.js.map +1 -0
  61. package/out/threshold-plugin.d.ts +9 -3
  62. package/out/threshold-plugin.d.ts.map +1 -1
  63. package/out/threshold-plugin.js +9 -3
  64. package/out/threshold-plugin.js.map +1 -1
  65. package/package.json +3 -3
  66. package/src/acronym-case-plugin.ts +370 -0
  67. package/src/browser-globals.ts +77 -0
  68. package/src/console-padding-plugin.ts +76 -0
  69. package/src/constant-doc-plugin.ts +15 -5
  70. package/src/headers-plugin.ts +6 -2
  71. package/src/index.ts +183 -29
  72. package/src/jsdoc-plugin.ts +75 -0
  73. package/src/length-truthiness-plugin.ts +15 -5
  74. package/src/multiline-statement-plugin.ts +104 -0
  75. package/src/padding-plugin.ts +44 -29
  76. package/src/padding-utils.ts +70 -0
  77. package/src/plugin-types.ts +75 -13
  78. package/src/plugin.ts +18 -0
  79. package/src/process-globals-plugin.ts +6 -2
  80. package/src/restrictions.ts +16 -77
  81. package/src/section-marker-plugin.ts +306 -0
  82. package/src/threshold-plugin.ts +18 -6
@@ -0,0 +1,370 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ * @file A no-title-case-acronym rule, authored as an oxlint JS plugin (ESLint v9-compatible API). An
6
+ * acronym is capitalized as a whole camelCase component — `parseJSON`, `POILookup`,
7
+ * `createWOFResolver` — never title-cased. The mechanism knows nothing about any particular
8
+ * project: the acronyms are options, so adding one is a config edit rather than a code change.
9
+ *
10
+ * The subtlety the rule exists for is the COMPONENT BOUNDARY. `Poi` appears inside `Point`, `Id`
11
+ * inside `Identifier`, `Us` inside `User` — none of those are acronyms, they are prefixes of
12
+ * ordinary words. A substring match reports every one of them; a component match reports none.
13
+ *
14
+ * Deliberately not fixable: renaming a declaration without renaming its references produces code
15
+ * that does not compile, and a linter cannot see the references.
16
+ */
17
+
18
+ import type { AstNode, Rule } from "./plugin-types.js"
19
+
20
+ /**
21
+ * Acronyms enforced when the caller names none. Deliberately generic — every entry is an acronym in general programming
22
+ * rather than in some particular domain, because this list ships to consumers who share none of each other's
23
+ * vocabulary. Domain acronyms belong in `extraAcronyms`.
24
+ *
25
+ * `ID` is on the list and `Id` is NOT the house form: `placeID`, `sourceID`, `nodeID`. It is the one entry short enough
26
+ * to hide inside ordinary words — `Id` leads `Identifier`, `Idle`, `Ideal` — which is what the component boundary is
27
+ * for, and it holds: none of those end a component.
28
+ *
29
+ * The other short candidates were measured against a 1,587-file corpus and turned down. Read this before adding one
30
+ * back:
31
+ *
32
+ * - `IO` — `IoT` is the settled spelling of the commonest identifier component starting with `Io`, so the rule would
33
+ * report `IoTGateway` and suggest the wrong `IOTGateway`. Zero drift in the corpus, so nothing offsets that.
34
+ * - `UI` — zero drift in the corpus, and the uppercase form is not settled the way `ID`'s is. `UiButton` is a deliberate
35
+ * choice in plenty of React codebases, and `UiPath` is a vendor that spells itself that way.
36
+ * - `DB` — a shorthand for one word rather than an initialism, so there is no English uppercase form to settle on. It is
37
+ * also the exact collision the exemptions exist for: every hit in the corpus was a Pastel option key, where the
38
+ * framework derives `adminDb` from `--admin-db` and the schema has to match its derivation.
39
+ */
40
+ export const DefaultAcronyms = [
41
+ "API",
42
+ // oxlint-disable-next-line unicorn/text-encoding-identifier-case -- an entry in an acronym list, not an encoding label.
43
+ "ASCII",
44
+ "CLI",
45
+ "CPU",
46
+ "CSS",
47
+ "CSV",
48
+ "DNS",
49
+ "DOM",
50
+ "GPU",
51
+ "HTML",
52
+ "HTTP",
53
+ "HTTPS",
54
+ "ID",
55
+ "JSON",
56
+ "JSONL",
57
+ "JWT",
58
+ "MIME",
59
+ "PDF",
60
+ "RGB",
61
+ "RGBA",
62
+ "SDK",
63
+ "SQL",
64
+ "SSL",
65
+ "SVG",
66
+ "TLS",
67
+ "TSV",
68
+ "TTL",
69
+ "URI",
70
+ "URL",
71
+ "UTC",
72
+ // oxlint-disable-next-line unicorn/text-encoding-identifier-case -- an entry in an acronym list, not an encoding label.
73
+ "UTF8",
74
+ "UUID",
75
+ "XML",
76
+ "YAML",
77
+ ] as const
78
+
79
+ /**
80
+ * Which declarations the rule reads.
81
+ */
82
+ export type AcronymCaseScope = "exported" | "all"
83
+
84
+ /**
85
+ * Options accepted by {@link noTitleCaseAcronymRule}.
86
+ */
87
+ export interface AcronymCaseOptions {
88
+ /**
89
+ * Acronyms in the ALL-CAPS spelling identifiers should use; the rule derives the title-case form (`POI` → `Poi`) and
90
+ * reports that. Replaces {@link DefaultAcronyms} rather than extending it.
91
+ *
92
+ * Add an acronym once the project has settled on its capitalized form. This reports drift from an established
93
+ * convention; it does not invent one.
94
+ */
95
+ acronyms?: string[]
96
+ /**
97
+ * Acronyms added to whichever list is in effect — the usual way to layer a project's own vocabulary on the default.
98
+ */
99
+ extraAcronyms?: string[]
100
+ /**
101
+ * Identifiers exempt everywhere, for names whose casing is not ours to choose: an external library's own spelling
102
+ * (`HttpStatusCode` from axios), a wire field someone else named (`operationId` in OpenAPI), or a prop a framework
103
+ * derives from something else (Pastel turns `--run-id` into `runId`, so the schema key has to match its derivation).
104
+ *
105
+ * Prefer a scoped `// oxlint-disable-next-line sister-software/no-title-case-acronym` at the one site that needs it.
106
+ * A config entry silently covers every future declaration of the same name.
107
+ */
108
+ ignoreNames?: string[]
109
+ /**
110
+ * `"exported"` (the default) reads the names a consumer can see: exported declarations, the members of exported
111
+ * interfaces, classes and enums, and the exported half of a re-export. That is where a wrong acronym costs someone a
112
+ * breaking rename later; a local costs nothing to rename at any time.
113
+ *
114
+ * `"all"` reads every declaration in the file, exported or not. Object-literal keys are never read under either
115
+ * setting — a wire contract is a string, and its casing belongs to whoever is on the other end.
116
+ */
117
+ scope?: AcronymCaseScope
118
+ }
119
+
120
+ /**
121
+ * Characters that may follow a title-cased run for it to be a whole component: the start of the next component, a
122
+ * digit, or an underscore.
123
+ */
124
+ const COMPONENT_END = /[A-Z0-9_]/
125
+
126
+ /**
127
+ * Characters that may precede a title-cased run for it to START a component.
128
+ */
129
+ const COMPONENT_START = /[a-z0-9_]/
130
+
131
+ /**
132
+ * Node types opening a scope whose names are private to it. Meeting one on the walk up means the declaration is a
133
+ * local, however deep inside an exported declaration it sits — the body of an exported function is not public surface.
134
+ */
135
+ const SCOPE_BOUNDARIES = new Set([
136
+ "ArrowFunctionExpression",
137
+ "BlockStatement",
138
+ "FunctionDeclaration",
139
+ "FunctionExpression",
140
+ "Program",
141
+ "StaticBlock",
142
+ "TSModuleBlock",
143
+ ])
144
+
145
+ /**
146
+ * Node types that make what they wrap part of the module's public surface.
147
+ */
148
+ const EXPORT_WRAPPERS = new Set(["ExportNamedDeclaration", "ExportDefaultDeclaration"])
149
+
150
+ /**
151
+ * One title-cased run found inside an identifier.
152
+ */
153
+ interface AcronymHit {
154
+ /**
155
+ * Where the run starts in the identifier.
156
+ */
157
+ index: number
158
+ /**
159
+ * The house spelling, e.g. `POI`.
160
+ */
161
+ acronym: string
162
+ /**
163
+ * The drifted spelling actually written, e.g. `Poi`.
164
+ */
165
+ found: string
166
+ }
167
+
168
+ /**
169
+ * Title case of an acronym — the drifted spelling (`POI` → `Poi`, `NZ` → `Nz`).
170
+ */
171
+ function titleCase(acronym: string): string {
172
+ return acronym[0]! + acronym.slice(1).toLowerCase()
173
+ }
174
+
175
+ /**
176
+ * Is the run of `found` at `index` a whole camelCase component of `identifier`?
177
+ *
178
+ * This is the entire subtlety of the rule. A component ENDS where the next character starts another one — uppercase, a
179
+ * digit, `_`, or the end of the identifier — and STARTS at the identifier's start or after a lowercase letter, digit or
180
+ * underscore. `PoiBoard` qualifies (`B` follows); `Point` does not (`n` follows).
181
+ */
182
+ function isWholeComponent(identifier: string, index: number, found: string): boolean {
183
+ const after = identifier[index + found.length]
184
+
185
+ if (after !== undefined && !COMPONENT_END.test(after)) return false
186
+
187
+ const before = identifier[index - 1]
188
+
189
+ return before === undefined || COMPONENT_START.test(before)
190
+ }
191
+
192
+ /**
193
+ * Every title-cased acronym in `identifier`, plus the identifier as it should have been written.
194
+ */
195
+ function findTitleCasedAcronyms(
196
+ identifier: string,
197
+ acronyms: readonly string[]
198
+ ): { hits: AcronymHit[]; expected: string } {
199
+ const candidates: AcronymHit[] = []
200
+
201
+ for (const acronym of acronyms) {
202
+ const found = titleCase(acronym)
203
+
204
+ // A one-letter acronym, or one whose tail is already caseless (`H3`), has no drifted spelling to
205
+ // look for: title case and house case are the same string, so every match would be a correct one.
206
+ if (found === acronym) continue
207
+
208
+ for (let index = identifier.indexOf(found); index !== -1; index = identifier.indexOf(found, index + 1)) {
209
+ if (isWholeComponent(identifier, index, found)) {
210
+ candidates.push({ index, acronym, found })
211
+ }
212
+ }
213
+ }
214
+
215
+ // Longest run first where two start together, so `Jsonl` wins over `Json`.
216
+ const ordered = candidates.toSorted(
217
+ (left, right) => left.index - right.index || right.found.length - left.found.length
218
+ )
219
+
220
+ const hits: AcronymHit[] = []
221
+ let expected = ""
222
+ let cursor = 0
223
+
224
+ for (const hit of ordered) {
225
+ if (hit.index < cursor) continue
226
+
227
+ hits.push(hit)
228
+ expected += identifier.slice(cursor, hit.index) + hit.acronym
229
+ cursor = hit.index + hit.found.length
230
+ }
231
+
232
+ return { hits, expected: expected + identifier.slice(cursor) }
233
+ }
234
+
235
+ /**
236
+ * Is `node` part of what the module exports?
237
+ *
238
+ * The walk stops at the first scope boundary rather than running to the root, which is what separates an exported
239
+ * declaration from a local one that merely lives inside an exported function.
240
+ */
241
+ function isPublicSurface(node: AstNode): boolean {
242
+ let current = node.parent
243
+
244
+ while (current) {
245
+ if (EXPORT_WRAPPERS.has(current.type)) return true
246
+
247
+ if (SCOPE_BOUNDARIES.has(current.type)) return false
248
+
249
+ current = current.parent
250
+ }
251
+
252
+ return false
253
+ }
254
+
255
+ export const noTitleCaseAcronymRule: Rule = {
256
+ meta: {
257
+ name: "no-title-case-acronym",
258
+ type: "suggestion",
259
+ schema: [{ type: "object", additionalProperties: true }],
260
+ },
261
+ create(context) {
262
+ const options = (context.options[0] ?? {}) as AcronymCaseOptions
263
+ const configured = options.acronyms ?? DefaultAcronyms
264
+ const acronyms = [...configured, ...(options.extraAcronyms ?? [])].map((acronym) => acronym.toUpperCase())
265
+ const ignoreNames = new Set(options.ignoreNames)
266
+ const scope = options.scope ?? "exported"
267
+ const reported = new Set<string>()
268
+
269
+ /**
270
+ * Reports the identifier bound by `node`, if it title-cases an acronym.
271
+ */
272
+ function check(node: AstNode | undefined | null): void {
273
+ if (node?.type !== "Identifier" || !node.name) return
274
+
275
+ if (ignoreNames.has(node.name)) return
276
+
277
+ // A declaration can be reached by two visitors at once (an export specifier and the binding it
278
+ // names, say); the range keys the site so it is reported once.
279
+ const site = `${node.range[0]}:${node.range[1]}`
280
+
281
+ if (reported.has(site)) return
282
+
283
+ const { hits, expected } = findTitleCasedAcronyms(node.name, acronyms)
284
+
285
+ if (!hits.length) return
286
+
287
+ reported.add(site)
288
+
289
+ const spelled = hits.map((hit) => `\`${hit.acronym}\``).join(", ")
290
+
291
+ context.report({
292
+ node,
293
+ message:
294
+ `\`${node.name}\` title-cases ${hits.length > 1 ? "the acronyms" : "the acronym"} ${spelled} — an ` +
295
+ `acronym is capitalized as a whole camelCase component, so this reads \`${expected}\`.`,
296
+ })
297
+ }
298
+
299
+ function inScope(node: AstNode): boolean {
300
+ return scope === "all" || isPublicSurface(node)
301
+ }
302
+
303
+ /**
304
+ * Parameters are declarations too, but renaming one breaks nobody, so they are read only when the caller has asked
305
+ * for every declaration in the file.
306
+ */
307
+ function parameters(node: AstNode): void {
308
+ if (scope !== "all") return
309
+
310
+ for (const parameter of node.params ?? []) {
311
+ check(parameter)
312
+ }
313
+ }
314
+
315
+ /**
316
+ * A named declaration: `function`, `class`, `interface`, `type`, `enum`, `module`, and enum members.
317
+ */
318
+ function declared(node: AstNode): void {
319
+ parameters(node)
320
+
321
+ if (inScope(node)) {
322
+ check(node.id)
323
+ }
324
+ }
325
+
326
+ /**
327
+ * A member of an interface, type literal, or class — public surface of the type that holds it.
328
+ */
329
+ function member(node: AstNode): void {
330
+ if (node.computed || node.accessibility === "private") return
331
+
332
+ if (inScope(node)) {
333
+ check(node.key)
334
+ }
335
+ }
336
+
337
+ return {
338
+ FunctionDeclaration: declared,
339
+ ClassDeclaration: declared,
340
+ TSInterfaceDeclaration: declared,
341
+ TSTypeAliasDeclaration: declared,
342
+ TSEnumDeclaration: declared,
343
+ TSModuleDeclaration: declared,
344
+ TSEnumMember: declared,
345
+ TSPropertySignature: member,
346
+ TSMethodSignature: member,
347
+ PropertyDefinition: member,
348
+ MethodDefinition: member,
349
+ VariableDeclaration(node) {
350
+ if (!inScope(node)) return
351
+
352
+ for (const declarator of node.declarations ?? []) {
353
+ check(declarator.id)
354
+ }
355
+ },
356
+ // The exported half of `export { poiBoard as PoiBoard }` and `export * as PoiBoard from "…"` is
357
+ // the name a consumer imports, whatever the local one is called.
358
+ ExportSpecifier(node) {
359
+ check(node.exported)
360
+ },
361
+ ExportAllDeclaration(node) {
362
+ check(node.exported)
363
+ },
364
+ FunctionExpression: parameters,
365
+ ArrowFunctionExpression: parameters,
366
+ }
367
+ },
368
+ }
369
+
370
+ export default noTitleCaseAcronymRule
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ * @file The browser globals whose bare use is ambiguous, and the rule entry built from them. A list
6
+ * this long is reference data — it belongs beside the code that consumes it, not inside the file
7
+ * that assembles the platform-layering overrides.
8
+ */
9
+
10
+ /**
11
+ * Browser globals whose bare use is ambiguous (they collide with common identifiers). Browser- and node-runtime files
12
+ * warn on these, nudging toward an explicit `window.` access.
13
+ */
14
+ export const BROWSER_GLOBALS = [
15
+ "addEventListener",
16
+ "blur",
17
+ "close",
18
+ "closed",
19
+ "confirm",
20
+ "defaultStatus",
21
+ "defaultstatus",
22
+ "event",
23
+ "external",
24
+ "find",
25
+ "focus",
26
+ "frameElement",
27
+ "frames",
28
+ "history",
29
+ "innerHeight",
30
+ "innerWidth",
31
+ "length",
32
+ "location",
33
+ "locationbar",
34
+ "menubar",
35
+ "moveBy",
36
+ "moveTo",
37
+ "name",
38
+ "onblur",
39
+ "onerror",
40
+ "onfocus",
41
+ "onload",
42
+ "onresize",
43
+ "onunload",
44
+ "open",
45
+ "opener",
46
+ "opera",
47
+ "outerHeight",
48
+ "outerWidth",
49
+ "pageXOffset",
50
+ "pageYOffset",
51
+ "parent",
52
+ "print",
53
+ "removeEventListener",
54
+ "resizeBy",
55
+ "resizeTo",
56
+ "screen",
57
+ "screenLeft",
58
+ "screenTop",
59
+ "screenX",
60
+ "screenY",
61
+ "scroll",
62
+ "scrollbars",
63
+ "scrollBy",
64
+ "scrollTo",
65
+ "scrollX",
66
+ "scrollY",
67
+ "self",
68
+ "status",
69
+ "statusbar",
70
+ "stop",
71
+ "toolbar",
72
+ "top",
73
+ ] as const
74
+
75
+ export function restrictedBrowserGlobalsRule(): unknown {
76
+ return ["warn", ...BROWSER_GLOBALS.map((name) => ({ name, message: `Ambiguous: did you mean \`window.${name}\`?` }))]
77
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ * @file The `sister-software/console-padding` rule: a `console.*` call gets a blank line on each
6
+ * side, so the lines that produce output stand apart from the lines that compute it. Scanning a
7
+ * long procedural script for "what does this print" then works visually.
8
+ *
9
+ * A RUN of console calls is one group — no blank lines inside it, one before the first and one
10
+ * after the last, because consecutive calls are one block of output. Neither edge is required
11
+ * where the block boundary already provides the separation: nothing is needed before a call that
12
+ * opens a block, or after one that closes it.
13
+ */
14
+
15
+ import { createPaddingHelpers } from "./padding-utils.js"
16
+ import type { AstNode, Rule } from "./plugin-types.js"
17
+
18
+ /**
19
+ * The object whose method calls this rule treats as output.
20
+ */
21
+ const CONSOLE_OBJECT = "console"
22
+
23
+ /**
24
+ * Is this statement a bare `console.<method>(…)` call?
25
+ */
26
+ function isConsoleStatement(node: AstNode | undefined): boolean {
27
+ if (node?.type !== "ExpressionStatement") return false
28
+ const call = node.expression
29
+
30
+ if (call?.type !== "CallExpression") return false
31
+ const callee = call.callee
32
+
33
+ return (
34
+ callee?.type === "MemberExpression" && callee.object?.type === "Identifier" && callee.object.name === CONSOLE_OBJECT
35
+ )
36
+ }
37
+
38
+ export const consolePaddingRule: Rule = {
39
+ meta: {
40
+ name: "console-padding",
41
+ type: "layout",
42
+ fixable: "whitespace",
43
+ schema: [{ type: "object", additionalProperties: true }],
44
+ },
45
+ create(context) {
46
+ const sourceCode = context.sourceCode ?? context.getSourceCode!()
47
+ const text = sourceCode.getText()
48
+ const { requirePadding } = createPaddingHelpers(context)
49
+
50
+ return {
51
+ ExpressionStatement(node: AstNode) {
52
+ if (!isConsoleStatement(node)) return
53
+ const parent = node.parent
54
+
55
+ // Only statements in a statement list have neighbours to pad against.
56
+ if (!parent || !Array.isArray(parent.body)) return
57
+ const body = parent.body
58
+ const index = body.indexOf(node)
59
+
60
+ if (index === -1) return
61
+ const previous = body[index - 1]
62
+ const next = body[index + 1]
63
+
64
+ // Nothing before a call that opens the block, and nothing between calls in one run.
65
+ if (previous && !isConsoleStatement(previous)) {
66
+ requirePadding(previous, node, node, "Expected a blank line before this console call.")
67
+ }
68
+
69
+ // Nothing after a call that closes the block — the brace already separates it.
70
+ if (next && !isConsoleStatement(next)) {
71
+ requirePadding(node, next, node, "Expected a blank line after this console call.")
72
+ }
73
+ },
74
+ }
75
+ },
76
+ }
@@ -11,18 +11,28 @@
11
11
 
12
12
  import type { Rule } from "./plugin-types.js"
13
13
 
14
- /** Names in SCREAMING_SNAKE_CASE, the convention for a tuning knob. */
14
+ /**
15
+ * Names in SCREAMING_SNAKE_CASE, the convention for a tuning knob.
16
+ */
15
17
  const SCREAMING_CASE = /^[A-Z][A-Z0-9_]*$/
16
18
 
17
- /** Initializers that make the binding a function rather than a constant value. */
19
+ /**
20
+ * Initializers that make the binding a function rather than a constant value.
21
+ */
18
22
  const FUNCTION_INITIALIZERS = new Set(["ArrowFunctionExpression", "FunctionExpression"])
19
23
 
20
- /** Which module-level constants the rule applies to. */
24
+ /**
25
+ * Which module-level constants the rule applies to.
26
+ */
21
27
  export type ConstantDocScope = "exported" | "screaming" | "exported-or-screaming"
22
28
 
23
- /** Options accepted by {@link requireConstantDocRule}. */
29
+ /**
30
+ * Options accepted by {@link requireConstantDocRule}.
31
+ */
24
32
  export interface ConstantDocOptions {
25
- /** Defaults to `"exported-or-screaming"`. */
33
+ /**
34
+ * Defaults to `"exported-or-screaming"`.
35
+ */
26
36
  scope?: ConstantDocScope
27
37
  /**
28
38
  * Export names a framework requires and gives meaning to, which a JSDoc block cannot improve on — Pastel's
@@ -9,7 +9,9 @@
9
9
 
10
10
  import type { Comment, Plugin, Rule } from "./plugin-types.js"
11
11
 
12
- /** Options accepted by the header rule. */
12
+ /**
13
+ * Options accepted by the header rule.
14
+ */
13
15
  export interface HeaderRuleOptions {
14
16
  copyrightHolder?: string
15
17
  spdxLicenseIdentifier?: string
@@ -96,7 +98,9 @@ export const headerRule: Rule = {
96
98
  },
97
99
  }
98
100
 
99
- /** The Sister Software oxlint header plugin. Registers the `sister-software/require-file-header` rule. */
101
+ /**
102
+ * The Sister Software oxlint header plugin. Registers the `sister-software/require-file-header` rule.
103
+ */
100
104
  const headerPlugin: Plugin = {
101
105
  meta: { name: "sister-software" },
102
106
  rules: { "require-file-header": headerRule },