eslint-plugin-flawless 1.4.1 → 1.4.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.
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { n as plugin, t as PLUGIN_NAME } from "./plugin-p3_AxnS4.mjs";
1
+ import { n as plugin, t as PLUGIN_NAME } from "./plugin-F1dJgCBi.mjs";
2
2
  //#region src/configs/index.ts
3
3
  const configs = { recommended: {
4
4
  plugins: { [PLUGIN_NAME]: plugin },
package/dist/oxlint.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { n as plugin, t as PLUGIN_NAME } from "./plugin-p3_AxnS4.mjs";
1
+ import { n as plugin, t as PLUGIN_NAME } from "./plugin-F1dJgCBi.mjs";
2
2
  import { definePlugin } from "@oxlint/plugins";
3
3
  //#region src/oxlint.ts
4
4
  /**
@@ -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.1";
17
+ var version = "1.4.2";
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
- * Collects the `?.` tokens along an optional chain's primary spine and the text
3117
- * that replaces each with a non-null assertion. A non-computed member becomes
3118
- * `!.` (`a?.b` -> `a!.b`); a computed member or optional call becomes `!`
3119
- * (`a?.[x]` -> `a![x]`, `fn?.()` -> `fn!()`). Only the object/callee spine is
3120
- * walked optional chains inside computed keys or call arguments are their own
3121
- * `ChainExpression` nodes and are visited (and fixed) separately.
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
+ for (;;) {
3136
+ const { parent } = current;
3137
+ if (parent === void 0) return false;
3138
+ if (parent.type === AST_NODE_TYPES.ArrowFunctionExpression || parent.type === AST_NODE_TYPES.FunctionDeclaration || parent.type === AST_NODE_TYPES.FunctionExpression) return false;
3139
+ if (parent.type === AST_NODE_TYPES.CallExpression) {
3140
+ if (current === parent.callee) return false;
3141
+ const root = getRootIdentifier$1(parent.callee);
3142
+ if (root === null) return false;
3143
+ return ASSERTION_ROOT_NAMES.has(root.name) || ASSERTION_ROOT_NAMES.has(resolveVitestName$1(sourceCode, root) ?? "");
3144
+ }
3145
+ current = parent;
3146
+ }
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
- if (token !== null) fixes.push({
3135
- text: node.computed ? "!" : "!.",
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
- if (token !== null) fixes.push({
3146
- text: "!",
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
  }
@@ -3210,8 +3250,8 @@ function createOnce$10(context) {
3210
3250
  if (!inTestCase || config.allowOptionalChaining) return;
3211
3251
  context.report({
3212
3252
  fix: (fixer) => {
3213
- return collectOptionalTokenFixes(node, sourceCode).map((optionalFix) => {
3214
- return fixer.replaceText(optionalFix.token, optionalFix.text);
3253
+ return collectOptionalTokenFixes(node, sourceCode).flatMap((optionalFix) => {
3254
+ return [fixer.insertTextAfter(optionalFix.anchor, "!"), fixer.replaceText(optionalFix.token, optionalFix.text)];
3215
3255
  });
3216
3256
  },
3217
3257
  messageId: MESSAGE_ID$9,
@@ -3220,7 +3260,10 @@ function createOnce$10(context) {
3220
3260
  },
3221
3261
  "ConditionalExpression": maybeReportConditional,
3222
3262
  "IfStatement": maybeReportConditional,
3223
- "LogicalExpression": maybeReportConditional,
3263
+ "LogicalExpression": function(node) {
3264
+ if (node.operator === "&&" && isInAssertionArgument(node, sourceCode)) return;
3265
+ maybeReportConditional(node);
3266
+ },
3224
3267
  "SwitchStatement": maybeReportConditional
3225
3268
  };
3226
3269
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-flawless",
3
- "version": "1.4.1",
3
+ "version": "1.4.2",
4
4
  "description": "Your ESLint plugin description",
5
5
  "keywords": [
6
6
  "eslint",