@sister.software/oxlint-config 9.3.0 → 11.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +198 -10
- package/out/browser-globals.d.ts +15 -0
- package/out/browser-globals.d.ts.map +1 -0
- package/out/browser-globals.js +76 -0
- package/out/browser-globals.js.map +1 -0
- package/out/console-padding-plugin.d.ts +16 -0
- package/out/console-padding-plugin.d.ts.map +1 -0
- package/out/console-padding-plugin.js +68 -0
- package/out/console-padding-plugin.js.map +1 -0
- package/out/constant-doc-plugin.d.ts +32 -0
- package/out/constant-doc-plugin.d.ts.map +1 -0
- package/out/constant-doc-plugin.js +91 -0
- package/out/constant-doc-plugin.js.map +1 -0
- package/out/headers-plugin.d.ts +6 -2
- package/out/headers-plugin.d.ts.map +1 -1
- package/out/headers-plugin.js +4 -2
- package/out/headers-plugin.js.map +1 -1
- package/out/index.d.ts +166 -13
- package/out/index.d.ts.map +1 -1
- package/out/index.js +332 -6
- package/out/index.js.map +1 -1
- package/out/jsdoc-plugin.d.ts +16 -0
- package/out/jsdoc-plugin.d.ts.map +1 -0
- package/out/jsdoc-plugin.js +68 -0
- package/out/jsdoc-plugin.js.map +1 -0
- package/out/length-truthiness-plugin.d.ts +18 -0
- package/out/length-truthiness-plugin.d.ts.map +1 -0
- package/out/length-truthiness-plugin.js +123 -0
- package/out/length-truthiness-plugin.js.map +1 -0
- package/out/multiline-statement-plugin.d.ts +18 -0
- package/out/multiline-statement-plugin.d.ts.map +1 -0
- package/out/multiline-statement-plugin.js +93 -0
- package/out/multiline-statement-plugin.js.map +1 -0
- package/out/padding-plugin.d.ts +4 -3
- package/out/padding-plugin.d.ts.map +1 -1
- package/out/padding-plugin.js +44 -27
- package/out/padding-plugin.js.map +1 -1
- package/out/padding-utils.d.ts +34 -0
- package/out/padding-utils.d.ts.map +1 -0
- package/out/padding-utils.js +50 -0
- package/out/padding-utils.js.map +1 -0
- package/out/plugin-types.d.ts +68 -3
- package/out/plugin-types.d.ts.map +1 -1
- package/out/plugin.d.ts.map +1 -1
- package/out/plugin.js +17 -0
- package/out/plugin.js.map +1 -1
- package/out/process-globals-plugin.d.ts.map +1 -1
- package/out/process-globals-plugin.js +6 -2
- package/out/process-globals-plugin.js.map +1 -1
- package/out/restrictions.d.ts +9 -3
- package/out/restrictions.d.ts.map +1 -1
- package/out/restrictions.js +4 -70
- package/out/restrictions.js.map +1 -1
- package/out/section-marker-plugin.d.ts +25 -0
- package/out/section-marker-plugin.d.ts.map +1 -0
- package/out/section-marker-plugin.js +234 -0
- package/out/section-marker-plugin.js.map +1 -0
- package/out/threshold-plugin.d.ts +27 -0
- package/out/threshold-plugin.d.ts.map +1 -0
- package/out/threshold-plugin.js +78 -0
- package/out/threshold-plugin.js.map +1 -0
- package/package.json +2 -2
- package/src/browser-globals.ts +77 -0
- package/src/console-padding-plugin.ts +76 -0
- package/src/constant-doc-plugin.ts +124 -0
- package/src/headers-plugin.ts +7 -3
- package/src/index.ts +490 -17
- package/src/jsdoc-plugin.ts +75 -0
- package/src/length-truthiness-plugin.ts +142 -0
- package/src/multiline-statement-plugin.ts +104 -0
- package/src/padding-plugin.ts +44 -29
- package/src/padding-utils.ts +70 -0
- package/src/plugin-types.ts +68 -3
- package/src/plugin.ts +22 -0
- package/src/process-globals-plugin.ts +6 -2
- package/src/restrictions.ts +16 -77
- package/src/section-marker-plugin.ts +306 -0
- package/src/threshold-plugin.ts +105 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
* @file A prefer-length-truthiness rule, authored as an oxlint JS plugin (ESLint v9-compatible API).
|
|
6
|
+
* It is the house counterpart to `unicorn/explicit-length-check`, which enforces the opposite
|
|
7
|
+
* convention and is therefore off: `if (items.length)` reads better here than
|
|
8
|
+
* `if (items.length > 0)`.
|
|
9
|
+
*
|
|
10
|
+
* The rule only fires where the value is ALREADY coerced to a boolean — a condition, a ternary
|
|
11
|
+
* test, or the operand of `!` — including through `&&`/`||` nested inside one. Outside those
|
|
12
|
+
* positions the comparison is the value itself, and rewriting `const hasItems = items.length > 0`
|
|
13
|
+
* would silently change its type from boolean to number.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { AstNode, Fixer, Rule, RuleContext } from "./plugin-types.js"
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Comparisons meaning "non-empty", which become the bare length.
|
|
20
|
+
*/
|
|
21
|
+
const TRUTHY_FORMS = new Set(["> 0", "!== 0", "!= 0", ">= 1"])
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Comparisons meaning "empty", which become a negated length.
|
|
25
|
+
*/
|
|
26
|
+
const FALSY_FORMS = new Set(["=== 0", "== 0", "< 1"])
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Members whose length-ness the rule understands.
|
|
30
|
+
*/
|
|
31
|
+
const LENGTH_PROPERTIES = new Set(["length", "size"])
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The comparison rendered as `<operator> <literal>`, or null when it is not a length comparison.
|
|
35
|
+
*/
|
|
36
|
+
function classify(node: AstNode): { member: AstNode; negate: boolean } | null {
|
|
37
|
+
if (node.type !== "BinaryExpression" || !node.operator || !node.left || !node.right) return null
|
|
38
|
+
|
|
39
|
+
// Accept both `x.length > 0` and the flipped `0 < x.length`.
|
|
40
|
+
const flipped: Record<string, string> = { "<": ">", ">": "<", "<=": ">=", ">=": "<=" }
|
|
41
|
+
let { left, right, operator } = { left: node.left, right: node.right, operator: node.operator }
|
|
42
|
+
|
|
43
|
+
if (left.type === "Literal" || left.type === "NumericLiteral") {
|
|
44
|
+
;[left, right] = [right, left]
|
|
45
|
+
operator = flipped[operator] ?? operator
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (left.type !== "MemberExpression" && left.type !== "StaticMemberExpression") return null
|
|
49
|
+
|
|
50
|
+
const property = (left as AstNode & { property?: AstNode }).property
|
|
51
|
+
|
|
52
|
+
if (!property || property.type !== "Identifier" || !LENGTH_PROPERTIES.has(property.name ?? "")) return null
|
|
53
|
+
|
|
54
|
+
if (right.type !== "Literal" && right.type !== "NumericLiteral") return null
|
|
55
|
+
|
|
56
|
+
if (typeof right.value !== "number") return null
|
|
57
|
+
|
|
58
|
+
const form = `${operator} ${right.value}`
|
|
59
|
+
|
|
60
|
+
if (TRUTHY_FORMS.has(form)) return { member: left, negate: false }
|
|
61
|
+
|
|
62
|
+
if (FALSY_FORMS.has(form)) return { member: left, negate: true }
|
|
63
|
+
|
|
64
|
+
return null
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const preferLengthTruthinessRule: Rule = {
|
|
68
|
+
meta: {
|
|
69
|
+
name: "prefer-length-truthiness",
|
|
70
|
+
type: "suggestion",
|
|
71
|
+
fixable: "code",
|
|
72
|
+
schema: [{ type: "object", additionalProperties: true }],
|
|
73
|
+
},
|
|
74
|
+
create(context: RuleContext) {
|
|
75
|
+
const sourceCode = context.sourceCode ?? context.getSourceCode!()
|
|
76
|
+
const text = sourceCode.getText()
|
|
77
|
+
|
|
78
|
+
function report(node: AstNode) {
|
|
79
|
+
const hit = classify(node)
|
|
80
|
+
|
|
81
|
+
if (!hit) return
|
|
82
|
+
|
|
83
|
+
const member = text.slice(hit.member.range[0], hit.member.range[1])
|
|
84
|
+
const replacement = hit.negate ? `!${member}` : member
|
|
85
|
+
|
|
86
|
+
context.report({
|
|
87
|
+
node,
|
|
88
|
+
message: `Prefer \`${replacement}\` over an explicit length comparison — the house convention is truthiness.`,
|
|
89
|
+
fix(fixer: Fixer) {
|
|
90
|
+
return fixer.replaceTextRange(node.range, replacement)
|
|
91
|
+
},
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Walk into a boolean context: logical operands and `!` arguments stay boolean.
|
|
97
|
+
*/
|
|
98
|
+
function visitCondition(node: AstNode | null | undefined) {
|
|
99
|
+
if (!node) return
|
|
100
|
+
|
|
101
|
+
if (node.type === "LogicalExpression") {
|
|
102
|
+
visitCondition(node.left)
|
|
103
|
+
visitCondition(node.right)
|
|
104
|
+
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (node.type === "UnaryExpression" && node.operator === "!") {
|
|
109
|
+
visitCondition(node.argument)
|
|
110
|
+
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
report(node)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
IfStatement(node) {
|
|
119
|
+
visitCondition(node.test)
|
|
120
|
+
},
|
|
121
|
+
WhileStatement(node) {
|
|
122
|
+
visitCondition(node.test)
|
|
123
|
+
},
|
|
124
|
+
DoWhileStatement(node) {
|
|
125
|
+
visitCondition(node.test)
|
|
126
|
+
},
|
|
127
|
+
ForStatement(node) {
|
|
128
|
+
visitCondition(node.test)
|
|
129
|
+
},
|
|
130
|
+
ConditionalExpression(node) {
|
|
131
|
+
visitCondition(node.test)
|
|
132
|
+
},
|
|
133
|
+
UnaryExpression(node) {
|
|
134
|
+
if (node.operator === "!") {
|
|
135
|
+
visitCondition(node.argument)
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export default preferLengthTruthinessRule
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
* @file The `sister-software/multiline-statement-padding` rule: a statement that spans lines gets a
|
|
6
|
+
* blank line on each side, so a reader can see where it starts and stops without matching brackets.
|
|
7
|
+
*
|
|
8
|
+
* One rule covers what look like two habits — a big object literal jammed against the next
|
|
9
|
+
* declaration, and a multi-line call jammed against the counter after it. Both are the same thing:
|
|
10
|
+
* a statement tall enough to read as a block, sitting flush against its neighbour.
|
|
11
|
+
*
|
|
12
|
+
* Neither edge is required at a block boundary, where the brace already separates. `console.*` calls
|
|
13
|
+
* are left to `console-padding`, which groups consecutive ones — this rule would split a run that
|
|
14
|
+
* happens to contain a tall call, and a run of output is one thing.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { createPaddingHelpers } from "./padding-utils.js"
|
|
18
|
+
import type { AstNode, Rule } from "./plugin-types.js"
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The object whose calls belong to `console-padding` rather than to this rule.
|
|
22
|
+
*/
|
|
23
|
+
const CONSOLE_OBJECT = "console"
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Does this statement's own source span more than one line?
|
|
27
|
+
*/
|
|
28
|
+
function isMultiline(node: AstNode, text: string): boolean {
|
|
29
|
+
return text.slice(node.range[0], node.range[1]).includes("\n")
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Is this statement a bare `console.<method>(…)` call, which `console-padding` owns?
|
|
34
|
+
*/
|
|
35
|
+
function isConsoleStatement(node: AstNode | undefined): boolean {
|
|
36
|
+
if (node?.type !== "ExpressionStatement") return false
|
|
37
|
+
const callee = node.expression?.type === "CallExpression" ? node.expression.callee : undefined
|
|
38
|
+
|
|
39
|
+
return callee?.type === "MemberExpression" && callee.object?.name === CONSOLE_OBJECT
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const multilineStatementPaddingRule: Rule = {
|
|
43
|
+
meta: {
|
|
44
|
+
name: "multiline-statement-padding",
|
|
45
|
+
type: "layout",
|
|
46
|
+
fixable: "whitespace",
|
|
47
|
+
schema: [{ type: "object", additionalProperties: true }],
|
|
48
|
+
},
|
|
49
|
+
create(context) {
|
|
50
|
+
const sourceCode = context.sourceCode ?? context.getSourceCode!()
|
|
51
|
+
const text = sourceCode.getText()
|
|
52
|
+
const { requirePadding } = createPaddingHelpers(context)
|
|
53
|
+
|
|
54
|
+
function check(node: AstNode): void {
|
|
55
|
+
const parent = node.parent
|
|
56
|
+
|
|
57
|
+
if (!parent || !Array.isArray(parent.body)) return
|
|
58
|
+
|
|
59
|
+
if (!isMultiline(node, text) || isConsoleStatement(node)) return
|
|
60
|
+
const body = parent.body
|
|
61
|
+
const index = body.indexOf(node)
|
|
62
|
+
|
|
63
|
+
if (index === -1) return
|
|
64
|
+
const previous = body[index - 1]
|
|
65
|
+
const next = body[index + 1]
|
|
66
|
+
|
|
67
|
+
if (previous && !isConsoleStatement(previous)) {
|
|
68
|
+
requirePadding(previous, node, node, "Expected a blank line before this multi-line statement.")
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (next && !isConsoleStatement(next)) {
|
|
72
|
+
requirePadding(node, next, node, "Expected a blank line after this multi-line statement.")
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Every statement type that can stand in a statement list and span lines. Block-like statements are
|
|
77
|
+
// already padded from the front by `padding-lines`; this adds their trailing edge.
|
|
78
|
+
return Object.fromEntries(
|
|
79
|
+
[
|
|
80
|
+
"VariableDeclaration",
|
|
81
|
+
"ExpressionStatement",
|
|
82
|
+
"IfStatement",
|
|
83
|
+
"ForStatement",
|
|
84
|
+
"ForInStatement",
|
|
85
|
+
"ForOfStatement",
|
|
86
|
+
"WhileStatement",
|
|
87
|
+
"DoWhileStatement",
|
|
88
|
+
"SwitchStatement",
|
|
89
|
+
"TryStatement",
|
|
90
|
+
"FunctionDeclaration",
|
|
91
|
+
"ClassDeclaration",
|
|
92
|
+
"ReturnStatement",
|
|
93
|
+
"TSInterfaceDeclaration",
|
|
94
|
+
"TSTypeAliasDeclaration",
|
|
95
|
+
"TSEnumDeclaration",
|
|
96
|
+
"TSModuleDeclaration",
|
|
97
|
+
// An exported declaration arrives wrapped, and the wrapper spans the same lines as what it
|
|
98
|
+
// wraps — so matching the wrapper is enough. Without these, every `export` was invisible here.
|
|
99
|
+
"ExportNamedDeclaration",
|
|
100
|
+
"ExportDefaultDeclaration",
|
|
101
|
+
].map((type) => [type, check])
|
|
102
|
+
)
|
|
103
|
+
},
|
|
104
|
+
}
|
package/src/padding-plugin.ts
CHANGED
|
@@ -3,19 +3,23 @@
|
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
* @file A padding-lines rule, authored as an oxlint JS plugin (ESLint v9-compatible API). It
|
|
6
|
-
* requires a blank line before
|
|
7
|
-
* `
|
|
8
|
-
*
|
|
6
|
+
* requires a blank line before the statements that END a path or a step — `return`, `continue`,
|
|
7
|
+
* `break`, a bare `x++`/`x--` — and before block-like statements. Roughly ESLint's
|
|
8
|
+
* `padding-line-between-statements` with `{ blankLine: "always", prev: "*", next: "return" | "block-like" }`,
|
|
9
|
+
* widened to the rest of that family. Autofixes by inserting the blank line before any leading comments.
|
|
9
10
|
*/
|
|
10
11
|
|
|
12
|
+
import { createPaddingHelpers } from "./padding-utils.js"
|
|
11
13
|
import type { AstNode, Rule } from "./plugin-types.js"
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
|
-
* Statement node types that require a preceding blank line:
|
|
16
|
+
* Statement node types that require a preceding blank line: the path-enders plus all "block-like" statements (matching
|
|
15
17
|
* ESLint's `block-like` selector).
|
|
16
18
|
*/
|
|
17
19
|
const PADDED_STATEMENT_TYPES = [
|
|
18
20
|
"ReturnStatement",
|
|
21
|
+
"ContinueStatement",
|
|
22
|
+
"BreakStatement",
|
|
19
23
|
"BlockStatement",
|
|
20
24
|
"IfStatement",
|
|
21
25
|
"ForStatement",
|
|
@@ -25,8 +29,21 @@ const PADDED_STATEMENT_TYPES = [
|
|
|
25
29
|
"DoWhileStatement",
|
|
26
30
|
"SwitchStatement",
|
|
27
31
|
"TryStatement",
|
|
32
|
+
// An interface or a type alias with a body reads as a block too — same braces, same weight on the
|
|
33
|
+
// page — so it wants the same separation from whatever precedes it.
|
|
34
|
+
"TSInterfaceDeclaration",
|
|
35
|
+
"TSTypeAliasDeclaration",
|
|
36
|
+
"TSEnumDeclaration",
|
|
37
|
+
"TSModuleDeclaration",
|
|
28
38
|
] as const
|
|
29
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Does this node type require a preceding blank line?
|
|
42
|
+
*/
|
|
43
|
+
function isPaddedType(type: string | undefined): boolean {
|
|
44
|
+
return !!type && (PADDED_STATEMENT_TYPES as readonly string[]).includes(type)
|
|
45
|
+
}
|
|
46
|
+
|
|
30
47
|
export const paddingRule: Rule = {
|
|
31
48
|
meta: {
|
|
32
49
|
name: "padding-lines",
|
|
@@ -35,9 +52,7 @@ export const paddingRule: Rule = {
|
|
|
35
52
|
schema: [{ type: "object", additionalProperties: true }],
|
|
36
53
|
},
|
|
37
54
|
create(context) {
|
|
38
|
-
const
|
|
39
|
-
const text = sourceCode.getText()
|
|
40
|
-
const comments = sourceCode.getAllComments()
|
|
55
|
+
const { requirePadding } = createPaddingHelpers(context)
|
|
41
56
|
|
|
42
57
|
function check(node: AstNode) {
|
|
43
58
|
const parent = node.parent
|
|
@@ -49,32 +64,32 @@ export const paddingRule: Rule = {
|
|
|
49
64
|
const index = parent.body.indexOf(node)
|
|
50
65
|
|
|
51
66
|
if (index <= 0) return // first statement in the block — nothing to pad against.
|
|
52
|
-
const previous = parent.body[index - 1]!
|
|
53
67
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
let start = node.range[0]
|
|
68
|
+
requirePadding(parent.body[index - 1]!, node, node, "Expected a blank line before this statement.")
|
|
69
|
+
}
|
|
57
70
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
71
|
+
return {
|
|
72
|
+
...Object.fromEntries(PADDED_STATEMENT_TYPES.map((type) => [type, check])),
|
|
73
|
+
// A bare `x++` / `x--` is a counter step, and reads like one only when it stands apart. It
|
|
74
|
+
// arrives as an ExpressionStatement, so it cannot be matched by node type alone.
|
|
75
|
+
ExpressionStatement(node: AstNode) {
|
|
76
|
+
if (node.expression?.type === "UpdateExpression") {
|
|
77
|
+
check(node)
|
|
61
78
|
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
}
|
|
79
|
+
},
|
|
80
|
+
// An exported declaration arrives wrapped. Look through the wrapper so `export interface Foo {}`
|
|
81
|
+
// is padded exactly like the unexported form.
|
|
82
|
+
ExportNamedDeclaration(node: AstNode) {
|
|
83
|
+
if (isPaddedType(node.declaration?.type)) {
|
|
84
|
+
check(node)
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
ExportDefaultDeclaration(node: AstNode) {
|
|
88
|
+
if (isPaddedType(node.declaration?.type)) {
|
|
89
|
+
check(node)
|
|
90
|
+
}
|
|
91
|
+
},
|
|
75
92
|
}
|
|
76
|
-
|
|
77
|
-
return Object.fromEntries(PADDED_STATEMENT_TYPES.map((type) => [type, check]))
|
|
78
93
|
},
|
|
79
94
|
}
|
|
80
95
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
* @file The blank-line machinery the three padding rules share: how to tell whether two statements are
|
|
6
|
+
* already separated, and how to ask for the separation when they are not.
|
|
7
|
+
*
|
|
8
|
+
* All three anchor their fix to the END of the earlier statement. That is deliberate: when two rules
|
|
9
|
+
* want the same blank line — a console call before a `return`, say — they emit the identical edit, so
|
|
10
|
+
* one is applied and the other is a no-op, instead of each inserting a newline and producing a gap of
|
|
11
|
+
* two.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { AstNode, Fixer, RuleContext, SourceCode } from "./plugin-types.js"
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* What a padding rule needs from its context, resolved once per `create`.
|
|
18
|
+
*/
|
|
19
|
+
export interface PaddingHelpers {
|
|
20
|
+
/**
|
|
21
|
+
* Is there already a blank line between these two statements?
|
|
22
|
+
*/
|
|
23
|
+
isPadded(from: AstNode, to: AstNode): boolean
|
|
24
|
+
/**
|
|
25
|
+
* Report and fix a missing blank line between `from` and `to`, unless one is impossible or present.
|
|
26
|
+
*/
|
|
27
|
+
requirePadding(from: AstNode, to: AstNode, node: AstNode, message: string): void
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Build the helpers for one rule invocation.
|
|
32
|
+
*
|
|
33
|
+
* A gap is measured to the earliest comment that leads the later statement, so the blank line lands above a statement's
|
|
34
|
+
* own comments rather than between them and their subject.
|
|
35
|
+
*/
|
|
36
|
+
export function createPaddingHelpers(context: RuleContext): PaddingHelpers {
|
|
37
|
+
const sourceCode: SourceCode = context.sourceCode ?? context.getSourceCode!()
|
|
38
|
+
const text = sourceCode.getText()
|
|
39
|
+
const comments = sourceCode.getAllComments()
|
|
40
|
+
|
|
41
|
+
function isPadded(from: AstNode, to: AstNode): boolean {
|
|
42
|
+
// Two statements with no newline between them cannot be separated by a blank line. This is the
|
|
43
|
+
// `;(expr)` ASI guard: the semicolon terminates the PREVIOUS statement, so the gap here is zero
|
|
44
|
+
// characters wide. Asking for padding there is asking for the impossible.
|
|
45
|
+
if (!text.slice(from.range[1], to.range[0]).includes("\n")) return true
|
|
46
|
+
let start = to.range[0]
|
|
47
|
+
|
|
48
|
+
for (const comment of comments) {
|
|
49
|
+
if (comment.range[0] >= from.range[1] && comment.range[1] <= to.range[0]) {
|
|
50
|
+
start = Math.min(start, comment.range[0])
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return (text.slice(from.range[1], start).match(/\n/g) ?? []).length >= 2
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function requirePadding(from: AstNode, to: AstNode, node: AstNode, message: string): void {
|
|
58
|
+
if (isPadded(from, to)) return
|
|
59
|
+
|
|
60
|
+
context.report({
|
|
61
|
+
node,
|
|
62
|
+
message,
|
|
63
|
+
fix(fixer: Fixer) {
|
|
64
|
+
return fixer.insertTextAfterRange([from.range[1], from.range[1]], "\n")
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return { isPadded, requirePadding }
|
|
70
|
+
}
|
package/src/plugin-types.ts
CHANGED
|
@@ -12,16 +12,81 @@ export interface Comment {
|
|
|
12
12
|
range: [number, number]
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* A loosely-typed AST node — only the fields the bundled rules read.
|
|
17
|
+
*/
|
|
16
18
|
export interface AstNode {
|
|
17
19
|
type: string
|
|
18
20
|
range: [number, number]
|
|
19
21
|
parent?: AstNode
|
|
20
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* A statement-list body (block/program) is an array; a loop body is a single statement.
|
|
24
|
+
*/
|
|
21
25
|
body?: AstNode[] | AstNode
|
|
22
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* `if`/`else` branches, for the require-braces rule.
|
|
28
|
+
*/
|
|
23
29
|
consequent?: AstNode
|
|
24
30
|
alternate?: AstNode | null
|
|
31
|
+
/**
|
|
32
|
+
* Binary/unary operator text, for the threshold rule.
|
|
33
|
+
*/
|
|
34
|
+
operator?: string
|
|
35
|
+
/**
|
|
36
|
+
* Binary-expression operands.
|
|
37
|
+
*/
|
|
38
|
+
left?: AstNode
|
|
39
|
+
right?: AstNode
|
|
40
|
+
/**
|
|
41
|
+
* Unary-expression operand.
|
|
42
|
+
*/
|
|
43
|
+
argument?: AstNode
|
|
44
|
+
/**
|
|
45
|
+
* A literal's value, and its verbatim source text (`raw` preserves a `0x` prefix).
|
|
46
|
+
*/
|
|
47
|
+
value?: unknown
|
|
48
|
+
raw?: string
|
|
49
|
+
/**
|
|
50
|
+
* `const` / `let` / `var`, for the constant-doc rule.
|
|
51
|
+
*/
|
|
52
|
+
kind?: string
|
|
53
|
+
/**
|
|
54
|
+
* Declarators of a variable declaration.
|
|
55
|
+
*/
|
|
56
|
+
declarations?: AstNode[]
|
|
57
|
+
/**
|
|
58
|
+
* A declarator's binding identifier and initializer.
|
|
59
|
+
*/
|
|
60
|
+
id?: AstNode
|
|
61
|
+
init?: AstNode | null
|
|
62
|
+
/**
|
|
63
|
+
* An identifier's name.
|
|
64
|
+
*/
|
|
65
|
+
name?: string
|
|
66
|
+
/**
|
|
67
|
+
* The condition of an `if`/`while`/`for`/ternary, for the length-truthiness rule.
|
|
68
|
+
*/
|
|
69
|
+
test?: AstNode | null
|
|
70
|
+
/**
|
|
71
|
+
* A member expression's accessed property.
|
|
72
|
+
*/
|
|
73
|
+
property?: AstNode
|
|
74
|
+
/**
|
|
75
|
+
* An expression statement's expression, for the console-padding rule.
|
|
76
|
+
*/
|
|
77
|
+
expression?: AstNode
|
|
78
|
+
/**
|
|
79
|
+
* An export statement's inner declaration, for the padding rules.
|
|
80
|
+
*/
|
|
81
|
+
declaration?: AstNode | null
|
|
82
|
+
/**
|
|
83
|
+
* A call expression's callee.
|
|
84
|
+
*/
|
|
85
|
+
callee?: AstNode
|
|
86
|
+
/**
|
|
87
|
+
* A member expression's object.
|
|
88
|
+
*/
|
|
89
|
+
object?: AstNode
|
|
25
90
|
}
|
|
26
91
|
|
|
27
92
|
export interface SourceCode {
|
package/src/plugin.ts
CHANGED
|
@@ -7,18 +7,40 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { bracesRule } from "./braces-plugin.js"
|
|
10
|
+
import { consolePaddingRule } from "./console-padding-plugin.js"
|
|
11
|
+
import { requireConstantDocRule } from "./constant-doc-plugin.js"
|
|
10
12
|
import { headerRule } from "./headers-plugin.js"
|
|
13
|
+
import { multilineJSDocRule } from "./jsdoc-plugin.js"
|
|
14
|
+
import { preferLengthTruthinessRule } from "./length-truthiness-plugin.js"
|
|
15
|
+
import { multilineStatementPaddingRule } from "./multiline-statement-plugin.js"
|
|
11
16
|
import { paddingRule } from "./padding-plugin.js"
|
|
12
17
|
import type { Plugin } from "./plugin-types.js"
|
|
13
18
|
import { noProcessGlobalsRule } from "./process-globals-plugin.js"
|
|
19
|
+
import {
|
|
20
|
+
conciseSectionMarkerRule,
|
|
21
|
+
maxRegionsRule,
|
|
22
|
+
preferMarkCommentRule,
|
|
23
|
+
preferRegionOverMarksRule,
|
|
24
|
+
} from "./section-marker-plugin.js"
|
|
25
|
+
import { noUnnamedThresholdRule } from "./threshold-plugin.js"
|
|
14
26
|
|
|
15
27
|
const sisterSoftwarePlugin: Plugin = {
|
|
16
28
|
meta: { name: "sister-software" },
|
|
17
29
|
rules: {
|
|
18
30
|
"require-file-header": headerRule,
|
|
19
31
|
"padding-lines": paddingRule,
|
|
32
|
+
"console-padding": consolePaddingRule,
|
|
33
|
+
"multiline-statement-padding": multilineStatementPaddingRule,
|
|
20
34
|
"require-braces": bracesRule,
|
|
21
35
|
"no-process-globals": noProcessGlobalsRule,
|
|
36
|
+
"no-unnamed-threshold": noUnnamedThresholdRule,
|
|
37
|
+
"require-constant-doc": requireConstantDocRule,
|
|
38
|
+
"prefer-length-truthiness": preferLengthTruthinessRule,
|
|
39
|
+
"multiline-jsdoc": multilineJSDocRule,
|
|
40
|
+
"prefer-mark-comment": preferMarkCommentRule,
|
|
41
|
+
"concise-section-marker": conciseSectionMarkerRule,
|
|
42
|
+
"prefer-region-over-marks": preferRegionOverMarksRule,
|
|
43
|
+
"max-regions": maxRegionsRule,
|
|
22
44
|
},
|
|
23
45
|
}
|
|
24
46
|
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
|
|
11
11
|
import type { AstNode, Rule } from "./plugin-types.js"
|
|
12
12
|
|
|
13
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* `process` members that must be reached through a blessed helper, not accessed directly.
|
|
15
|
+
*/
|
|
14
16
|
const RESTRICTED_MEMBERS = new Set(["env", "argv"])
|
|
15
17
|
|
|
16
18
|
interface Identifierish extends AstNode {
|
|
@@ -24,7 +26,9 @@ interface MemberNode extends AstNode {
|
|
|
24
26
|
computed?: boolean
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* The accessed member name for `process.env` (identifier) or `process["env"]` (string literal).
|
|
31
|
+
*/
|
|
28
32
|
function accessedMember(node: MemberNode): string | null {
|
|
29
33
|
const property = node.property
|
|
30
34
|
|