eslint-plugin-flawless 1.4.1 → 1.4.3
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/dist/index.mjs
CHANGED
package/dist/oxlint.mjs
CHANGED
|
@@ -14,7 +14,7 @@ import * as core from "@eslint-react/core";
|
|
|
14
14
|
import { getStaticTOMLValue } from "toml-eslint-parser";
|
|
15
15
|
//#region package.json
|
|
16
16
|
var name = "eslint-plugin-flawless";
|
|
17
|
-
var version = "1.4.
|
|
17
|
+
var version = "1.4.3";
|
|
18
18
|
var repository = {
|
|
19
19
|
"url": "git+https://github.com/christopher-buss/eslint-plugin-flawless.git",
|
|
20
20
|
"type": "git"
|
|
@@ -3030,6 +3030,11 @@ const DEFAULTS$1 = {
|
|
|
3030
3030
|
};
|
|
3031
3031
|
/** Callee identifiers that name a vitest test block (`describe` is excluded). */
|
|
3032
3032
|
const TEST_BLOCK_NAMES$1 = /* @__PURE__ */ new Set(["it", "test"]);
|
|
3033
|
+
/**
|
|
3034
|
+
* Root identifiers whose call arguments are assertion conditions. Matched by
|
|
3035
|
+
* name as well as by vitest resolution, so `assert` from `node:assert` counts.
|
|
3036
|
+
*/
|
|
3037
|
+
const ASSERTION_ROOT_NAMES = /* @__PURE__ */ new Set(["assert", "expect"]);
|
|
3033
3038
|
const messages$14 = { [MESSAGE_ID$9]: "Avoid having conditionals in tests." };
|
|
3034
3039
|
const schema$3 = [{
|
|
3035
3040
|
additionalProperties: false,
|
|
@@ -3113,12 +3118,43 @@ function resolveVitestName$1(sourceCode, identifier) {
|
|
|
3113
3118
|
return null;
|
|
3114
3119
|
}
|
|
3115
3120
|
/**
|
|
3116
|
-
*
|
|
3117
|
-
*
|
|
3118
|
-
*
|
|
3119
|
-
*
|
|
3120
|
-
*
|
|
3121
|
-
*
|
|
3121
|
+
* Determines whether a node sits directly in the argument list of an assertion
|
|
3122
|
+
* call (`expect(...)`, `assert(...)`, `assert.ok(...)`, `expect(x).toBe(...)`).
|
|
3123
|
+
* The search walks up to the nearest enclosing call and stops there, so an
|
|
3124
|
+
* argument of some other function nested inside an assertion
|
|
3125
|
+
* (`expect(wrap(a && b))`) does not qualify; it also stops at a function
|
|
3126
|
+
* boundary, since a callback's body runs on the callback's terms rather than as
|
|
3127
|
+
* part of the assertion's condition.
|
|
3128
|
+
*
|
|
3129
|
+
* @param node - The node to locate.
|
|
3130
|
+
* @param sourceCode - Provides the scope used to resolve the callee.
|
|
3131
|
+
* @returns `true` when the node is an argument of an assertion call.
|
|
3132
|
+
*/
|
|
3133
|
+
function isInAssertionArgument(node, sourceCode) {
|
|
3134
|
+
let current = node;
|
|
3135
|
+
while (current.type !== AST_NODE_TYPES.Program) {
|
|
3136
|
+
const { parent } = current;
|
|
3137
|
+
if (parent.type === AST_NODE_TYPES.ArrowFunctionExpression || parent.type === AST_NODE_TYPES.FunctionDeclaration || parent.type === AST_NODE_TYPES.FunctionExpression) return false;
|
|
3138
|
+
if (parent.type === AST_NODE_TYPES.CallExpression) {
|
|
3139
|
+
if (current === parent.callee) return false;
|
|
3140
|
+
const root = getRootIdentifier$1(parent.callee);
|
|
3141
|
+
if (root === null) return false;
|
|
3142
|
+
return ASSERTION_ROOT_NAMES.has(root.name) || ASSERTION_ROOT_NAMES.has(resolveVitestName$1(sourceCode, root) ?? "");
|
|
3143
|
+
}
|
|
3144
|
+
current = parent;
|
|
3145
|
+
}
|
|
3146
|
+
return false;
|
|
3147
|
+
}
|
|
3148
|
+
/**
|
|
3149
|
+
* Collects the `?.` tokens along an optional chain's primary spine and the parts
|
|
3150
|
+
* that convert each into a non-null assertion. The `!` is anchored to the token
|
|
3151
|
+
* preceding `?.` rather than written in place of it, because TypeScript forbids
|
|
3152
|
+
* a line break before `!` — a chain wrapped as `a\n?.b` has to become `a!\n.b`,
|
|
3153
|
+
* not `a\n!.b`. What replaces `?.` is therefore only the connector: `.` for a
|
|
3154
|
+
* plain member (`a?.b` -> `a!.b`), nothing for a computed member or optional
|
|
3155
|
+
* call (`a?.[x]` -> `a![x]`, `fn?.()` -> `fn!()`). Only the object/callee spine
|
|
3156
|
+
* is walked — optional chains inside computed keys or call arguments are their
|
|
3157
|
+
* own `ChainExpression` nodes and are visited (and fixed) separately.
|
|
3122
3158
|
*
|
|
3123
3159
|
* @param chain - The chain expression to convert.
|
|
3124
3160
|
* @param sourceCode - Provides token lookups.
|
|
@@ -3131,8 +3167,10 @@ function collectOptionalTokenFixes(chain, sourceCode) {
|
|
|
3131
3167
|
if (node.type === AST_NODE_TYPES.MemberExpression) {
|
|
3132
3168
|
if (node.optional) {
|
|
3133
3169
|
const token = sourceCode.getTokenAfter(node.object, { filter: (candidate) => candidate.value === "?." });
|
|
3134
|
-
|
|
3135
|
-
|
|
3170
|
+
const anchor = token === null ? null : sourceCode.getTokenBefore(token);
|
|
3171
|
+
if (token !== null && anchor !== null) fixes.push({
|
|
3172
|
+
anchor,
|
|
3173
|
+
text: node.computed ? "" : ".",
|
|
3136
3174
|
token
|
|
3137
3175
|
});
|
|
3138
3176
|
}
|
|
@@ -3142,8 +3180,10 @@ function collectOptionalTokenFixes(chain, sourceCode) {
|
|
|
3142
3180
|
if (node.type === AST_NODE_TYPES.CallExpression) {
|
|
3143
3181
|
if (node.optional) {
|
|
3144
3182
|
const token = sourceCode.getTokenAfter(node.callee, { filter: (candidate) => candidate.value === "?." });
|
|
3145
|
-
|
|
3146
|
-
|
|
3183
|
+
const anchor = token === null ? null : sourceCode.getTokenBefore(token);
|
|
3184
|
+
if (token !== null && anchor !== null) fixes.push({
|
|
3185
|
+
anchor,
|
|
3186
|
+
text: "",
|
|
3147
3187
|
token
|
|
3148
3188
|
});
|
|
3149
3189
|
}
|
|
@@ -3165,13 +3205,13 @@ function collectOptionalTokenFixes(chain, sourceCode) {
|
|
|
3165
3205
|
function createOnce$10(context) {
|
|
3166
3206
|
let config;
|
|
3167
3207
|
let sourceCode;
|
|
3168
|
-
|
|
3208
|
+
const testBlocks = [];
|
|
3169
3209
|
/**
|
|
3170
|
-
* Determines whether a call
|
|
3210
|
+
* Determines whether a call names a test block (a resolved vitest `it`/`test`,
|
|
3171
3211
|
* or a configured `additionalTestBlockFunctions` name).
|
|
3172
3212
|
*
|
|
3173
3213
|
* @param node - The call to inspect.
|
|
3174
|
-
* @returns `true` when the call
|
|
3214
|
+
* @returns `true` when the call is rooted at a test block name.
|
|
3175
3215
|
*/
|
|
3176
3216
|
function isTestBlock(node) {
|
|
3177
3217
|
const root = getRootIdentifier$1(node.callee);
|
|
@@ -3180,12 +3220,46 @@ function createOnce$10(context) {
|
|
|
3180
3220
|
return name !== null && config.additionalTestBlockFunctions.includes(name);
|
|
3181
3221
|
}
|
|
3182
3222
|
/**
|
|
3223
|
+
* Determines whether a test block call is a modifier applied to the block
|
|
3224
|
+
* rather than the call that opens it. In `it.skipIf(cond)("works", fn)` both
|
|
3225
|
+
* calls are rooted at `it`, but only the outer one takes the test body — the
|
|
3226
|
+
* inner one sits in its callee position.
|
|
3227
|
+
*
|
|
3228
|
+
* @param node - The test block call to inspect.
|
|
3229
|
+
* @returns `true` when the call is a modifier in a callee chain.
|
|
3230
|
+
*/
|
|
3231
|
+
function isTestBlockModifier(node) {
|
|
3232
|
+
const { parent } = node;
|
|
3233
|
+
return parent.type === AST_NODE_TYPES.CallExpression && parent.callee === node;
|
|
3234
|
+
}
|
|
3235
|
+
/**
|
|
3236
|
+
* Determines whether a node sits in the body of the innermost open test block.
|
|
3237
|
+
* A node reached through that block's callee — a modifier's arguments
|
|
3238
|
+
* (`it.skipIf(cond)`, `it.each(cases)`) or a computed key (`it[flag ? …]`) —
|
|
3239
|
+
* decides whether and how the test runs rather than what it does, so it is not
|
|
3240
|
+
* part of the body.
|
|
3241
|
+
*
|
|
3242
|
+
* @param node - The node to locate.
|
|
3243
|
+
* @returns `true` when the node is inside a test body.
|
|
3244
|
+
*/
|
|
3245
|
+
function isInTestBody(node) {
|
|
3246
|
+
const block = testBlocks.at(-1);
|
|
3247
|
+
if (block === void 0) return false;
|
|
3248
|
+
let current = node;
|
|
3249
|
+
while (current.type !== AST_NODE_TYPES.Program) {
|
|
3250
|
+
const { parent } = current;
|
|
3251
|
+
if (parent === block) return current !== block.callee;
|
|
3252
|
+
current = parent;
|
|
3253
|
+
}
|
|
3254
|
+
return false;
|
|
3255
|
+
}
|
|
3256
|
+
/**
|
|
3183
3257
|
* Reports a conditional node when the traversal is inside a test body.
|
|
3184
3258
|
*
|
|
3185
3259
|
* @param node - The `if`/`switch`/ternary/logical construct to flag.
|
|
3186
3260
|
*/
|
|
3187
3261
|
function maybeReportConditional(node) {
|
|
3188
|
-
if (
|
|
3262
|
+
if (isInTestBody(node)) context.report({
|
|
3189
3263
|
messageId: MESSAGE_ID$9,
|
|
3190
3264
|
node
|
|
3191
3265
|
});
|
|
@@ -3198,20 +3272,20 @@ function createOnce$10(context) {
|
|
|
3198
3272
|
allowOptionalChaining: options?.allowOptionalChaining ?? DEFAULTS$1.allowOptionalChaining
|
|
3199
3273
|
};
|
|
3200
3274
|
({sourceCode} = context);
|
|
3201
|
-
|
|
3275
|
+
testBlocks.length = 0;
|
|
3202
3276
|
},
|
|
3203
3277
|
"CallExpression": function(node) {
|
|
3204
|
-
if (isTestBlock(node)
|
|
3278
|
+
if (isTestBlock(node) && !isTestBlockModifier(node)) testBlocks.push(node);
|
|
3205
3279
|
},
|
|
3206
3280
|
"CallExpression:exit": function(node) {
|
|
3207
|
-
if (
|
|
3281
|
+
if (testBlocks.at(-1) === node) testBlocks.pop();
|
|
3208
3282
|
},
|
|
3209
3283
|
"ChainExpression": function(node) {
|
|
3210
|
-
if (
|
|
3284
|
+
if (config.allowOptionalChaining || !isInTestBody(node)) return;
|
|
3211
3285
|
context.report({
|
|
3212
3286
|
fix: (fixer) => {
|
|
3213
|
-
return collectOptionalTokenFixes(node, sourceCode).
|
|
3214
|
-
return fixer.replaceText(optionalFix.token, optionalFix.text);
|
|
3287
|
+
return collectOptionalTokenFixes(node, sourceCode).flatMap((optionalFix) => {
|
|
3288
|
+
return [fixer.insertTextAfter(optionalFix.anchor, "!"), fixer.replaceText(optionalFix.token, optionalFix.text)];
|
|
3215
3289
|
});
|
|
3216
3290
|
},
|
|
3217
3291
|
messageId: MESSAGE_ID$9,
|
|
@@ -3220,7 +3294,10 @@ function createOnce$10(context) {
|
|
|
3220
3294
|
},
|
|
3221
3295
|
"ConditionalExpression": maybeReportConditional,
|
|
3222
3296
|
"IfStatement": maybeReportConditional,
|
|
3223
|
-
"LogicalExpression":
|
|
3297
|
+
"LogicalExpression": function(node) {
|
|
3298
|
+
if (node.operator === "&&" && isInAssertionArgument(node, sourceCode)) return;
|
|
3299
|
+
maybeReportConditional(node);
|
|
3300
|
+
},
|
|
3224
3301
|
"SwitchStatement": maybeReportConditional
|
|
3225
3302
|
};
|
|
3226
3303
|
}
|