@cjser/eslint-node-test 0.2.0-cjser.2

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 (128) hide show
  1. package/dist-cjser/index.cjs +8561 -0
  2. package/index.d.ts +11 -0
  3. package/index.js +49 -0
  4. package/license +9 -0
  5. package/package.json +141 -0
  6. package/readme.md +172 -0
  7. package/rules/assertion-arguments.js +147 -0
  8. package/rules/ast/call-or-new-expression.js +94 -0
  9. package/rules/ast/function-types.js +7 -0
  10. package/rules/ast/index.js +17 -0
  11. package/rules/ast/is-expression-statement.js +7 -0
  12. package/rules/ast/is-function.js +5 -0
  13. package/rules/ast/is-loop.js +5 -0
  14. package/rules/ast/is-member-expression.js +92 -0
  15. package/rules/ast/is-method-call.js +62 -0
  16. package/rules/ast/literal.js +32 -0
  17. package/rules/ast/loop-types.js +9 -0
  18. package/rules/consistent-assert-style.js +169 -0
  19. package/rules/consistent-assert-throws-callback-style.js +241 -0
  20. package/rules/consistent-modifier-style.js +95 -0
  21. package/rules/consistent-test-context-name.js +75 -0
  22. package/rules/consistent-test-filename.js +70 -0
  23. package/rules/consistent-test-it.js +84 -0
  24. package/rules/fix/index.js +5 -0
  25. package/rules/fix/remove-argument.js +58 -0
  26. package/rules/fix/replace-member-expression-property.js +25 -0
  27. package/rules/hooks-order.js +147 -0
  28. package/rules/index.js +84 -0
  29. package/rules/max-assertions.js +88 -0
  30. package/rules/max-nested-describe.js +70 -0
  31. package/rules/no-assert-in-describe.js +61 -0
  32. package/rules/no-assert-in-hook.js +61 -0
  33. package/rules/no-assert-match-string.js +166 -0
  34. package/rules/no-assert-throws-async.js +113 -0
  35. package/rules/no-assert-throws-call.js +106 -0
  36. package/rules/no-assert-throws-multiple-statements.js +194 -0
  37. package/rules/no-assert-throws-string.js +78 -0
  38. package/rules/no-async-describe.js +50 -0
  39. package/rules/no-async-fn-without-await.js +74 -0
  40. package/rules/no-callback-and-promise.js +56 -0
  41. package/rules/no-commented-tests.js +64 -0
  42. package/rules/no-compound-assertion.js +131 -0
  43. package/rules/no-conditional-assertion.js +108 -0
  44. package/rules/no-conditional-in-test.js +66 -0
  45. package/rules/no-conditional-tests.js +75 -0
  46. package/rules/no-conflicting-modifiers.js +73 -0
  47. package/rules/no-constant-assertion.js +218 -0
  48. package/rules/no-done-callback.js +58 -0
  49. package/rules/no-duplicate-assertions.js +172 -0
  50. package/rules/no-duplicate-hooks.js +75 -0
  51. package/rules/no-duplicate-plan.js +209 -0
  52. package/rules/no-export.js +45 -0
  53. package/rules/no-identical-assertion-arguments.js +84 -0
  54. package/rules/no-identical-title.js +101 -0
  55. package/rules/no-incorrect-deep-equal.js +113 -0
  56. package/rules/no-incorrect-strict-equal.js +99 -0
  57. package/rules/no-loop-static-title.js +93 -0
  58. package/rules/no-misused-concurrency.js +85 -0
  59. package/rules/no-mock-timers-destructured-import.js +150 -0
  60. package/rules/no-nested-tests.js +71 -0
  61. package/rules/no-only-test.js +11 -0
  62. package/rules/no-parent-test-context.js +244 -0
  63. package/rules/no-process-env-mutation.js +495 -0
  64. package/rules/no-process-exit-in-test.js +96 -0
  65. package/rules/no-skip-test.js +11 -0
  66. package/rules/no-skip-without-reason.js +90 -0
  67. package/rules/no-skip-without-return.js +123 -0
  68. package/rules/no-sleep-in-test.js +482 -0
  69. package/rules/no-snapshot-in-loop.js +132 -0
  70. package/rules/no-standalone-assert.js +51 -0
  71. package/rules/no-test-inside-hook.js +68 -0
  72. package/rules/no-test-return-statement.js +217 -0
  73. package/rules/no-todo-test.js +11 -0
  74. package/rules/no-unawaited-promise-assertion.js +366 -0
  75. package/rules/no-unawaited-rejects.js +87 -0
  76. package/rules/no-unawaited-subtest.js +66 -0
  77. package/rules/no-unknown-test-options.js +77 -0
  78. package/rules/no-useless-assertion.js +60 -0
  79. package/rules/prefer-assert-match.js +253 -0
  80. package/rules/prefer-assert-throws.js +97 -0
  81. package/rules/prefer-async-await.js +203 -0
  82. package/rules/prefer-context-mock.js +55 -0
  83. package/rules/prefer-diagnostic.js +94 -0
  84. package/rules/prefer-equality-assertion.js +124 -0
  85. package/rules/prefer-hooks-on-top.js +75 -0
  86. package/rules/prefer-lowercase-title.js +119 -0
  87. package/rules/prefer-mock-method.js +115 -0
  88. package/rules/prefer-strict-assert.js +82 -0
  89. package/rules/prefer-test-context-assert.js +162 -0
  90. package/rules/prefer-todo.js +98 -0
  91. package/rules/require-assertion.js +99 -0
  92. package/rules/require-await-concurrent-subtests.js +119 -0
  93. package/rules/require-context-assert-with-plan.js +127 -0
  94. package/rules/require-hook.js +108 -0
  95. package/rules/require-mock-timers-advance.js +313 -0
  96. package/rules/require-mock-timers-apis.js +383 -0
  97. package/rules/require-throws-expectation.js +65 -0
  98. package/rules/require-throws-validator-return-true.js +156 -0
  99. package/rules/require-top-level-describe.js +89 -0
  100. package/rules/rule/index.js +9 -0
  101. package/rules/rule/to-eslint-create.js +35 -0
  102. package/rules/rule/to-eslint-listener.js +52 -0
  103. package/rules/rule/to-eslint-problem.js +36 -0
  104. package/rules/rule/to-eslint-rule-fixer.js +47 -0
  105. package/rules/rule/to-eslint-rule.js +36 -0
  106. package/rules/rule/to-eslint-rules.js +8 -0
  107. package/rules/rule/unicorn-context.js +34 -0
  108. package/rules/rule/unicorn-listeners.js +56 -0
  109. package/rules/rule/utilities.js +26 -0
  110. package/rules/shared/test-modifier-rule.js +92 -0
  111. package/rules/test-title-format.js +86 -0
  112. package/rules/test-title.js +139 -0
  113. package/rules/utils/contains-suspension-point.js +35 -0
  114. package/rules/utils/get-comments.js +15 -0
  115. package/rules/utils/get-documentation-url.js +9 -0
  116. package/rules/utils/get-enclosing-function.js +18 -0
  117. package/rules/utils/index.js +15 -0
  118. package/rules/utils/is-conditional-branch.js +37 -0
  119. package/rules/utils/is-promise-type.js +26 -0
  120. package/rules/utils/is-same-reference.js +179 -0
  121. package/rules/utils/is-value-not-usable.js +5 -0
  122. package/rules/utils/node-test.js +883 -0
  123. package/rules/utils/parentheses/get-parent-syntax-opening-parenthesis.js +78 -0
  124. package/rules/utils/parentheses/iterate-surrounding-parentheses.js +80 -0
  125. package/rules/utils/parentheses/parentheses.js +69 -0
  126. package/rules/utils/types.js +5 -0
  127. package/rules/utils/unwrap-typescript-expression.js +16 -0
  128. package/rules/valid-describe-callback.js +63 -0
@@ -0,0 +1,17 @@
1
+ export {
2
+ isLiteral,
3
+ isStringExpression,
4
+ isBooleanLiteral,
5
+ getStaticStringValue,
6
+ isRegexLiteral,
7
+ } from './literal.js';
8
+
9
+ export {isCallExpression} from './call-or-new-expression.js';
10
+
11
+ export {default as isExpressionStatement} from './is-expression-statement.js';
12
+ export {default as isFunction} from './is-function.js';
13
+ export {default as isLoop} from './is-loop.js';
14
+ export {default as isMemberExpression} from './is-member-expression.js';
15
+ export {default as isMethodCall} from './is-method-call.js';
16
+ export {default as functionTypes} from './function-types.js';
17
+ export {default as loopTypes} from './loop-types.js';
@@ -0,0 +1,7 @@
1
+ export default function isExpressionStatement(node) {
2
+ return node.type === 'ExpressionStatement'
3
+ || (
4
+ node.type === 'ChainExpression'
5
+ && node.parent.type === 'ExpressionStatement'
6
+ );
7
+ }
@@ -0,0 +1,5 @@
1
+ import functionTypes from './function-types.js';
2
+
3
+ export default function isFunction(node) {
4
+ return functionTypes.includes(node.type);
5
+ }
@@ -0,0 +1,5 @@
1
+ import loopTypes from './loop-types.js';
2
+
3
+ export default function isLoop(node) {
4
+ return loopTypes.includes(node.type);
5
+ }
@@ -0,0 +1,92 @@
1
+ /* eslint-disable complexity */
2
+ /**
3
+ @param {
4
+ {
5
+ property?: string,
6
+ properties?: string[],
7
+ object?: string,
8
+ objects?: string[],
9
+ optional?: boolean,
10
+ computed?: boolean
11
+ } | string | string[]
12
+ } [options]
13
+ @returns {string}
14
+ */
15
+ export default function isMemberExpression(node, options) {
16
+ if (node?.type !== 'MemberExpression') {
17
+ return false;
18
+ }
19
+
20
+ if (typeof options === 'string') {
21
+ options = {properties: [options]};
22
+ }
23
+
24
+ if (Array.isArray(options)) {
25
+ options = {properties: options};
26
+ }
27
+
28
+ let {
29
+ property,
30
+ properties,
31
+ object,
32
+ objects,
33
+ optional,
34
+ computed,
35
+ } = {
36
+ property: '',
37
+ properties: [],
38
+ object: '',
39
+ ...options,
40
+ };
41
+
42
+ if (property) {
43
+ properties = [property];
44
+ }
45
+
46
+ if (object) {
47
+ objects = [object];
48
+ }
49
+
50
+ if (
51
+ (optional === true && (node.optional !== optional))
52
+ || (
53
+ optional === false
54
+ // `node.optional` can be `undefined` in some parsers
55
+ && node.optional
56
+ )
57
+ ) {
58
+ return false;
59
+ }
60
+
61
+ if (
62
+ Array.isArray(properties)
63
+ && properties.length > 0
64
+ ) {
65
+ if (
66
+ node.property.type !== 'Identifier'
67
+ || !properties.includes(node.property.name)
68
+ ) {
69
+ return false;
70
+ }
71
+
72
+ computed ??= false;
73
+ }
74
+
75
+ if (
76
+ (computed === true && (node.computed !== computed))
77
+ || (
78
+ computed === false
79
+ // `node.computed` can be `undefined` in some parsers
80
+ && node.computed
81
+ )
82
+ ) {
83
+ return false;
84
+ }
85
+
86
+ return !(Array.isArray(objects)
87
+ && objects.length > 0
88
+ && (
89
+ node.object.type !== 'Identifier'
90
+ || !objects.includes(node.object.name)
91
+ ));
92
+ }
@@ -0,0 +1,62 @@
1
+ import isMemberExpression from './is-member-expression.js';
2
+ import {isCallExpression} from './call-or-new-expression.js';
3
+
4
+ /**
5
+ @param {
6
+ {
7
+ // `isCallExpression` options
8
+ argumentsLength?: number,
9
+ minimumArguments?: number,
10
+ maximumArguments?: number,
11
+ optionalCall?: boolean,
12
+ allowSpreadElement?: boolean,
13
+
14
+ // `isMemberExpression` options
15
+ method?: string,
16
+ methods?: string[],
17
+ object?: string,
18
+ objects?: string[],
19
+ optionalMember?: boolean,
20
+ computed?: boolean
21
+ } | string | string[]
22
+ } [options]
23
+ @returns {string}
24
+ */
25
+ export default function isMethodCall(node, options) {
26
+ if (typeof options === 'string') {
27
+ options = {methods: [options]};
28
+ }
29
+
30
+ if (Array.isArray(options)) {
31
+ options = {methods: options};
32
+ }
33
+
34
+ const {
35
+ optionalCall,
36
+ optionalMember,
37
+ method,
38
+ methods,
39
+ } = {
40
+ method: '',
41
+ methods: [],
42
+ ...options,
43
+ };
44
+
45
+ return (
46
+ isCallExpression(node, {
47
+ argumentsLength: options.argumentsLength,
48
+ minimumArguments: options.minimumArguments,
49
+ maximumArguments: options.maximumArguments,
50
+ allowSpreadElement: options.allowSpreadElement,
51
+ optional: optionalCall,
52
+ })
53
+ && isMemberExpression(node.callee, {
54
+ object: options.object,
55
+ objects: options.objects,
56
+ computed: options.computed,
57
+ property: method,
58
+ properties: methods,
59
+ optional: optionalMember,
60
+ })
61
+ );
62
+ }
@@ -0,0 +1,32 @@
1
+ export function isLiteral(node, value) {
2
+ if (node?.type !== 'Literal') {
3
+ return false;
4
+ }
5
+
6
+ return node.value === value;
7
+ }
8
+
9
+ const isStringLiteral = node => node?.type === 'Literal' && typeof node.value === 'string';
10
+
11
+ // A node whose value is always a string: a string literal or any template literal.
12
+ export const isStringExpression = node => isStringLiteral(node) || node?.type === 'TemplateLiteral';
13
+
14
+ export const isBooleanLiteral = (node, value) =>
15
+ node?.type === 'Literal'
16
+ && typeof node.value === 'boolean'
17
+ && (value === undefined || node.value === value);
18
+
19
+ export const getStaticStringValue = node => {
20
+ if (isStringLiteral(node)) {
21
+ return node.value;
22
+ }
23
+
24
+ if (
25
+ node?.type === 'TemplateLiteral'
26
+ && node.expressions.length === 0
27
+ ) {
28
+ return node.quasis[0].value.cooked;
29
+ }
30
+ };
31
+
32
+ export const isRegexLiteral = node => node.type === 'Literal' && Boolean(node.regex);
@@ -0,0 +1,9 @@
1
+ const loopTypes = [
2
+ 'DoWhileStatement',
3
+ 'ForInStatement',
4
+ 'ForOfStatement',
5
+ 'ForStatement',
6
+ 'WhileStatement',
7
+ ];
8
+
9
+ export default loopTypes;
@@ -0,0 +1,169 @@
1
+ import {isParenthesized} from './utils/index.js';
2
+
3
+ const MESSAGE_ID = 'consistent-assert-style';
4
+
5
+ const messages = {
6
+ [MESSAGE_ID]: 'Prefer `{{expected}}` over `{{actual}}`.',
7
+ };
8
+
9
+ const ASSERT_MODULES = new Set(['node:assert', 'node:assert/strict', 'assert', 'assert/strict']);
10
+
11
+ function isValueImport(node) {
12
+ return node.importKind === undefined || node.importKind === 'value';
13
+ }
14
+
15
+ function isCallableAssertSpecifier(specifier) {
16
+ if (!isValueImport(specifier)) {
17
+ return false;
18
+ }
19
+
20
+ if (specifier.type === 'ImportDefaultSpecifier') {
21
+ return true;
22
+ }
23
+
24
+ if (
25
+ specifier.type !== 'ImportSpecifier'
26
+ || specifier.imported.type !== 'Identifier'
27
+ ) {
28
+ return false;
29
+ }
30
+
31
+ return specifier.imported.name === 'default'
32
+ || specifier.imported.name === 'strict';
33
+ }
34
+
35
+ function getCallableAssertReferences(context) {
36
+ const {sourceCode} = context;
37
+ const references = new Set();
38
+
39
+ for (const node of sourceCode.ast.body) {
40
+ if (
41
+ node.type !== 'ImportDeclaration'
42
+ || typeof node.source.value !== 'string'
43
+ || !ASSERT_MODULES.has(node.source.value)
44
+ || !isValueImport(node)
45
+ ) {
46
+ continue;
47
+ }
48
+
49
+ for (const specifier of node.specifiers) {
50
+ addCallableAssertReferences(sourceCode, specifier, references);
51
+ }
52
+ }
53
+
54
+ return references;
55
+ }
56
+
57
+ function addCallableAssertReferences(sourceCode, specifier, references) {
58
+ if (!isCallableAssertSpecifier(specifier)) {
59
+ return;
60
+ }
61
+
62
+ const [variable] = sourceCode.getDeclaredVariables(specifier);
63
+ if (!variable) {
64
+ return;
65
+ }
66
+
67
+ for (const reference of variable.references) {
68
+ references.add(reference.identifier);
69
+ }
70
+ }
71
+
72
+ /** @param {import('eslint').Rule.RuleContext} context */
73
+ const create = context => {
74
+ const {sourceCode} = context;
75
+ const {style} = context.options[0];
76
+ const callableAssertReferences = getCallableAssertReferences(context);
77
+
78
+ if (callableAssertReferences.size === 0) {
79
+ return;
80
+ }
81
+
82
+ context.on('CallExpression', node => {
83
+ const {callee} = node;
84
+
85
+ if (style === 'assert-ok') {
86
+ if (
87
+ node.optional
88
+ || callee.type !== 'Identifier'
89
+ || isParenthesized(callee, context)
90
+ || !callableAssertReferences.has(callee)
91
+ ) {
92
+ return;
93
+ }
94
+
95
+ return {
96
+ node: callee,
97
+ messageId: MESSAGE_ID,
98
+ data: {
99
+ expected: `${callee.name}.ok(…)`,
100
+ actual: `${callee.name}(…)`,
101
+ },
102
+ fix: fixer => fixer.insertTextAfter(callee, '.ok'),
103
+ };
104
+ }
105
+
106
+ if (
107
+ node.optional
108
+ || callee.type !== 'MemberExpression'
109
+ || isParenthesized(callee, context)
110
+ || callee.optional
111
+ || callee.computed
112
+ || callee.object.type !== 'Identifier'
113
+ || callee.property.type !== 'Identifier'
114
+ || callee.property.name !== 'ok'
115
+ || isParenthesized(callee.object, context)
116
+ || !callableAssertReferences.has(callee.object)
117
+ ) {
118
+ return;
119
+ }
120
+
121
+ const problem = {
122
+ node: callee.property,
123
+ messageId: MESSAGE_ID,
124
+ data: {
125
+ expected: `${callee.object.name}(…)`,
126
+ actual: `${callee.object.name}.ok(…)`,
127
+ },
128
+ };
129
+
130
+ if (sourceCode.getCommentsInside(callee).length === 0) {
131
+ problem.fix = fixer => fixer.removeRange([
132
+ sourceCode.getRange(callee.object)[1],
133
+ sourceCode.getRange(callee.property)[1],
134
+ ]);
135
+ }
136
+
137
+ return problem;
138
+ });
139
+ };
140
+
141
+ /** @type {import('eslint').Rule.RuleModule} */
142
+ const config = {
143
+ create,
144
+ meta: {
145
+ type: 'suggestion',
146
+ docs: {
147
+ description: 'Enforce a consistent truthiness assertion style.',
148
+ recommended: true,
149
+ },
150
+ fixable: 'code',
151
+ schema: [
152
+ {
153
+ type: 'object',
154
+ properties: {
155
+ style: {
156
+ enum: ['assert', 'assert-ok'],
157
+ description: 'Whether truthiness assertions should use `assert(…)` or `assert.ok(…)`.',
158
+ },
159
+ },
160
+ additionalProperties: false,
161
+ },
162
+ ],
163
+ defaultOptions: [{style: 'assert-ok'}],
164
+ messages,
165
+ languages: ['js/js'],
166
+ },
167
+ };
168
+
169
+ export default config;
@@ -0,0 +1,241 @@
1
+ import {
2
+ resolveImports,
3
+ parseAssertionCall,
4
+ createContextTracker,
5
+ isAssertionCallWithSupportedContext,
6
+ } from './utils/node-test.js';
7
+ import {
8
+ isParenthesized,
9
+ getParentheses,
10
+ getParenthesizedRange,
11
+ unwrapTypeScriptExpression,
12
+ } from './utils/index.js';
13
+
14
+ const MESSAGE_ID_PREFER_BLOCK = 'consistent-assert-throws-callback-style/prefer-block';
15
+ const MESSAGE_ID_PREFER_EXPRESSION = 'consistent-assert-throws-callback-style/prefer-expression';
16
+
17
+ const messages = {
18
+ [MESSAGE_ID_PREFER_BLOCK]: 'Use a block body for the `assert.throws()` callback.',
19
+ [MESSAGE_ID_PREFER_EXPRESSION]: 'Use an expression body for the `assert.throws()` callback.',
20
+ };
21
+
22
+ const expressionStatementUnsafeTypes = new Set([
23
+ 'AssignmentExpression',
24
+ 'ClassExpression',
25
+ 'FunctionExpression',
26
+ 'ObjectExpression',
27
+ ]);
28
+
29
+ const arrowExpressionBodyUnsafeTypes = new Set([
30
+ ...expressionStatementUnsafeTypes,
31
+ 'SequenceExpression',
32
+ ]);
33
+
34
+ function getLineIndentation(sourceCode, node) {
35
+ return sourceCode.lines[sourceCode.getLoc(node).start.line - 1].match(/^\s*/)[0];
36
+ }
37
+
38
+ function getLeadingComments(sourceCode, node, context) {
39
+ const [openingParenthesis] = getParentheses(node, context);
40
+ const target = openingParenthesis ?? node;
41
+ const previousToken = sourceCode.getTokenBefore(target);
42
+ if (!previousToken) {
43
+ return [];
44
+ }
45
+
46
+ const previousTokenEnd = sourceCode.getRange(previousToken)[1];
47
+ return sourceCode.getCommentsBefore(target).filter(comment => sourceCode.getRange(comment)[0] > previousTokenEnd);
48
+ }
49
+
50
+ function hasTrailingComment(sourceCode, node, context) {
51
+ const parentheses = getParentheses(node, context);
52
+ const target = parentheses.at(-1) ?? node;
53
+ const nextToken = sourceCode.getTokenAfter(target, {includeComments: true});
54
+ return nextToken?.type === 'Block' || nextToken?.type === 'Line';
55
+ }
56
+
57
+ function getExpressionReplacement(node, context) {
58
+ const {sourceCode} = context;
59
+ const isBodyParenthesized = isParenthesized(node, context);
60
+ const range = isBodyParenthesized ? getParenthesizedRange(node, context) : sourceCode.getRange(node);
61
+ const [leadingComment] = getLeadingComments(sourceCode, node, context);
62
+ const [start, end] = leadingComment ? [sourceCode.getRange(leadingComment)[0], range[1]] : range;
63
+ const text = sourceCode.text.slice(start, end);
64
+ const unwrappedNode = unwrapTypeScriptExpression(node);
65
+
66
+ return {
67
+ range: [start, end],
68
+ text: !isBodyParenthesized && expressionStatementUnsafeTypes.has(unwrappedNode.type) ? `(${text})` : text,
69
+ };
70
+ }
71
+
72
+ function hasLineCommentInRange(sourceCode, range) {
73
+ return sourceCode.getAllComments().some(comment => comment.type === 'Line' && sourceCode.getRange(comment)[0] >= range[0] && sourceCode.getRange(comment)[1] <= range[1]);
74
+ }
75
+
76
+ function isBodyOnSameLine(callback, context) {
77
+ return context.sourceCode.getLoc(callback.body).start.line === context.sourceCode.getLoc(callback).start.line;
78
+ }
79
+
80
+ function canFixExpressionCallback(callback, context) {
81
+ const {sourceCode} = context;
82
+ if (
83
+ !isBodyOnSameLine(callback, context)
84
+ || callback.async
85
+ || callback.returnType
86
+ ) {
87
+ return false;
88
+ }
89
+
90
+ const replacement = getExpressionReplacement(callback.body, context);
91
+ return (
92
+ !hasLineCommentInRange(sourceCode, sourceCode.getRange(replacement))
93
+ && !hasTrailingComment(sourceCode, callback.body, context)
94
+ );
95
+ }
96
+
97
+ function getSingleExpressionStatement(callback) {
98
+ if (callback.body.type !== 'BlockStatement' || callback.body.body.length !== 1) {
99
+ return undefined;
100
+ }
101
+
102
+ const [statement] = callback.body.body;
103
+ return statement.type === 'ExpressionStatement' ? statement : undefined;
104
+ }
105
+
106
+ function getExpressionBodyReplacement(statement, context) {
107
+ const {sourceCode} = context;
108
+ const {expression} = statement;
109
+ const text = sourceCode.getText(expression);
110
+ const unwrappedExpression = unwrapTypeScriptExpression(expression);
111
+
112
+ return arrowExpressionBodyUnsafeTypes.has(unwrappedExpression.type) ? `(${text})` : text;
113
+ }
114
+
115
+ function canFixBlockCallback(callback, context) {
116
+ const {sourceCode} = context;
117
+ return (
118
+ isBodyOnSameLine(callback, context)
119
+ && !callback.async
120
+ && !callback.returnType
121
+ && sourceCode.getCommentsInside(callback.body).length === 0
122
+ );
123
+ }
124
+
125
+ function getBlockStyleProblem(callback, context) {
126
+ if (callback.body.type === 'BlockStatement') {
127
+ return undefined;
128
+ }
129
+
130
+ const {sourceCode} = context;
131
+ const problem = {
132
+ node: callback.body,
133
+ messageId: MESSAGE_ID_PREFER_BLOCK,
134
+ };
135
+
136
+ if (canFixExpressionCallback(callback, context)) {
137
+ problem.fix = fixer => {
138
+ const indentation = getLineIndentation(sourceCode, callback);
139
+ const replacement = getExpressionReplacement(callback.body, context);
140
+
141
+ return fixer.replaceTextRange(
142
+ sourceCode.getRange(replacement),
143
+ `{\n${indentation}\t${replacement.text};\n${indentation}}`,
144
+ );
145
+ };
146
+ }
147
+
148
+ return problem;
149
+ }
150
+
151
+ function getExpressionStyleProblem(callback, context) {
152
+ const statement = getSingleExpressionStatement(callback);
153
+ if (!statement) {
154
+ return undefined;
155
+ }
156
+
157
+ const problem = {
158
+ node: callback.body,
159
+ messageId: MESSAGE_ID_PREFER_EXPRESSION,
160
+ };
161
+
162
+ if (canFixBlockCallback(callback, context)) {
163
+ problem.fix = fixer => fixer.replaceText(
164
+ callback.body,
165
+ getExpressionBodyReplacement(statement, context),
166
+ );
167
+ }
168
+
169
+ return problem;
170
+ }
171
+
172
+ function getProblem(node, context, state) {
173
+ const {imports, tracker, style} = state;
174
+ const assertion = parseAssertionCall(node, imports);
175
+ if (assertion?.method !== 'throws' || !isAssertionCallWithSupportedContext(node, tracker)) {
176
+ return undefined;
177
+ }
178
+
179
+ const [firstArgument] = node.arguments;
180
+ const callback = firstArgument && unwrapTypeScriptExpression(firstArgument);
181
+ if (
182
+ !callback
183
+ || callback.type !== 'ArrowFunctionExpression'
184
+ ) {
185
+ return undefined;
186
+ }
187
+
188
+ return style === 'block' ? getBlockStyleProblem(callback, context) : getExpressionStyleProblem(callback, context);
189
+ }
190
+
191
+ /** @param {import('eslint').Rule.RuleContext} context */
192
+ const create = context => {
193
+ const imports = resolveImports(context);
194
+ if (!imports.isAssertOrTestFile) {
195
+ return;
196
+ }
197
+
198
+ const tracker = createContextTracker(imports, {trackHooks: true});
199
+ const {style} = context.options[0];
200
+ const state = {imports, tracker, style};
201
+
202
+ context.on('CallExpression', node => {
203
+ const problem = getProblem(node, context, state);
204
+ tracker.update(node);
205
+ return problem;
206
+ });
207
+
208
+ context.onExit('CallExpression', node => {
209
+ tracker.leave(node);
210
+ });
211
+ };
212
+
213
+ /** @type {import('eslint').Rule.RuleModule} */
214
+ const config = {
215
+ create,
216
+ meta: {
217
+ type: 'layout',
218
+ docs: {
219
+ description: 'Enforce a consistent body style for `assert.throws()` arrow callbacks.',
220
+ recommended: false,
221
+ },
222
+ fixable: 'code',
223
+ schema: [
224
+ {
225
+ type: 'object',
226
+ properties: {
227
+ style: {
228
+ enum: ['block', 'expression'],
229
+ description: 'Whether `assert.throws()` arrow callbacks should use block bodies or expression bodies.',
230
+ },
231
+ },
232
+ additionalProperties: false,
233
+ },
234
+ ],
235
+ defaultOptions: [{style: 'block'}],
236
+ messages,
237
+ languages: ['js/js'],
238
+ },
239
+ };
240
+
241
+ export default config;