[eslint-miner] eslint-factory: add no-split-filter-trim-without-trim-map rule by github-actions[bot] · Pull Request #69831 · github/gh-aw · GitHub
Skip to content

[eslint-miner] eslint-factory: add no-split-filter-trim-without-trim-map rule - #69831

Merged
pelikhan merged 3 commits into
mainfrom
eslint-miner-split-filter-trim-20261018-6b26026061e0b8ab
Oct 18, 2026
Merged

pelikhan merged 3 commits into
mainfrom
eslint-miner-split-filter-trim-20261018-6b26026061e0b8ab

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

Adds gh-aw-custom/no-split-filter-trim-without-trim-map (warn).

Pattern: x.split(",").filter(v => v.trim()) drops blank entries but keeps surrounding whitespace on the rest ("a, b" gives " b"). Newline separators are excluded to limit false positives.

Evidence in actions/setup/js (lint output): check_permissions_utils.cjs (GH_AW_REQUIRED_ROLES, GH_AW_ALLOWED_BOTS), redact_secrets.cjs (secret names, 2 sites), extra_empty_commit.cjs. Env-list values with spaces would silently fail matching.

Validation: npm run build, rule unit tests, and npm run lint:setup-js run; the rule reports the sites above. No files outside eslint-factory changed.

Generated by ESLint Miner · copilot · auto · 31.9 AIC · ⌖ 0.655 AIC · ⊞ 9K · ◷

  • expires on Oct 24, 2026, 7:40 PM UTC-08:00

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot added automation cookie Issue Monster Loves Cookies! eslint labels Oct 18, 2026
@pelikhan
pelikhan marked this pull request as ready for review October 18, 2026 06:23
Copilot AI max review requested due to automatic review settings October 18, 2026 06:23
@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor Author

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor Author

✅ Design Decision Gate 🏗️ completed the design decision gate check. See the comment below for the result and any generated ADR draft.

Design Decision Gate 🏗️

Outcome: reported_no_action

The agent completed and reported no action. A no-op output alone does not verify that all required work was completed or that publication was possible.

Agent messages are omitted from this comment. The category uses structured run status, not interpretation of agent prose.

Generated from Design Decision Gate 🏗️ · opus55 · 11.2 AIC · ⌖ 36.2 AIC · ◇ 17.1 AIC · ⊞ 1.9K · ◷

🏗️ ADR gate enforced by Design Decision Gate 🏗️

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor Author

✅ Ponytail Reviewer completed successfully!

Ponytail Reviewer

Outcome: reported_no_action

The agent completed and reported no action. A no-op output alone does not verify that all required work was completed or that publication was possible.

Agent messages are omitted from this comment. The category uses structured run status, not interpretation of agent prose.

Generated from Ponytail Reviewer · gpt56 · 5.68 AIC · ⌖ 5.36 AIC · ⊞ 14.6K · ◷

Generated by Ponytail Reviewer for #69831

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor Author

✅ PR Code Quality Reviewer completed the code quality review.

🔎 Code quality review by PR Code Quality Reviewer

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor Author

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer

@github-actions

Copy link
Copy Markdown
Contributor Author

Comment Memory

Peek at saved memory (pr-code-quality-reviewer)
reviewed_at: 2026-10-18T06:26:06.166+00:00
review_event: REQUEST_CHANGES
top_themes:
  - optional-chain false negative in split matcher
  - overbroad binary-expression matching / false positives
files_reviewed:
  - eslint-factory/eslint.config.cjs
  - eslint-factory/src/index.ts
  - eslint-factory/src/rules/no-split-filter-trim-without-trim-map.test.ts
  - eslint-factory/src/rules/no-split-filter-trim-without-trim-map.ts
comment_count: 2

Note

This comment is managed by comment memory.

Expand the saved memory block to view or edit the persistent context for this thread.
Edit only the text inside the backtick fences; workflow metadata and the footer are regenerated automatically.

Learn more about comment memory

🔎 Code quality review by PR Code Quality Reviewer · copilot · gpt54 · 59.5 AIC · ⌖ 5.97 AIC · ⊞ 12.7K · ◷
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request changes

This rule has correctness gaps in the matcher itself: it currently misses optional-chained split() receivers and it also over-matches unrelated binary comparisons, so it can both under-report and over-report the pattern it claims to enforce.

Themes- optional chaining is not unwrapped before the .split(...) check

  • BinaryExpression matching is too loose for a rule that is supposed to detect blank-entry truthiness filters

Static review only; I reviewed the capped diff and a few adjacent rule implementations, but did not run the rule locally.

🔎 Code quality review by PR Code Quality Reviewer · copilot · gpt54 · 59.5 AIC · ⌖ 5.97 AIC · ⊞ 12.7K
Comment /review to run again

const param = predicate.params[0].name;

let source: TSESTree.Node = callee.object;
if (source.type !== "CallExpression" || source.callee.type !== "MemberExpression" || source.callee.computed || source.callee.property.type !== "Identifier" || source.callee.property.name !== "split") return;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This misses optional-chained split() receivers, so one of the rule's advertised target shapes can slip through unflagged and the current test suite gives false confidence.

💡 Why this is a real correctness gapIn @typescript-eslint/Espree, x?.split(",") is wrapped in a ChainExpression, so callee.object is not always a bare CallExpression. This branch returns early before you ever inspect the inner .split(...), which means code like process.env.X?.split(",").filter(p => p.trim()) can be missed even though the new test marks it invalid. Please unwrap ChainExpression before the source.type !== "CallExpression" check (same pattern other rules here already use), then keep traversing the inner call.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handled in 36be333: unwrap ChainExpression before checking the split call and retain the optional-chain regression case.

if (isTrimCallOn(node, paramName)) return true;
if (node.type === "UnaryExpression" && node.operator === "!") return isTrimmedTruthiness(node.argument, paramName);
if (node.type === "MemberExpression" && !node.computed && node.property.type === "Identifier" && node.property.name === "length") return isTrimCallOn(node.object, paramName);
if (node.type === "BinaryExpression") return isTrimmedTruthiness(node.left, paramName) || isTrimmedTruthiness(node.right, paramName);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The BinaryExpression fallback is broad enough to flag legitimate comparisons, which turns this from a focused bug detector into a noisy false-positive generator.

💡 Why the current match is too looseisTrimmedTruthiness() treats any binary expression with x.trim() on either side as a hit, so predicates like parts.split(",").filter(p => p.trim() !== p) or filter(p => p.trim().length === min) would be reported even though they are not using trim() as a blank-entry truthiness check. That creates merge-blocking lint noise on valid filtering logic. Please narrow the binary handling to the specific truthiness shapes you actually want (x.trim().length > 0, x.trim() !== "", etc.) and add a valid test that preserves an intentional comparison.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handled in 36be333: matching is limited to nonempty trim truthiness and explicit empty-string/positive-length comparisons; unrelated comparisons now have regression coverage.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /tdd — the new rule itself is well-tested and reasonably scoped (newline exclusion, truthiness-shape matching), but the PR is missing a required README update that an existing repo-level test enforces.

📝 Triage

pr-triage classified this as a new_feature adding a custom ESLint rule with comprehensive unit tests; recommended skills /tdd and /grill-with-docs.

📋 Key Themes & Highlights

Key Themes

  • Missing README entry (likely CI-breaking): eslint-factory/src/index-readme-parity.test.ts asserts every rule exported from index.ts has a matching ### \rule-name`heading inREADME.md. This PR adds no-split-filter-trim-without-trim-maptoindex.tsbut not toREADME.md`, so that parity test should fail as written.
  • Minor detection gap: the rule only matches ArrowFunctionExpression predicates, missing function (v) { ... } callbacks with the identical bug. No current site in actions/setup/js uses that form, so it is low severity, but worth a follow-up test case for completeness.

Positive Highlights

  • ✅ Clear, well-justified exclusion of newline separators with an inline comment explaining the rationale.
  • ✅ isTrimmedTruthiness correctly recurses through !x.trim(), .length checks, and binary expressions — good anticipation of real-world variants.
  • ✅ Test suite covers both direct and chained usages (.trim().length > 0, block-statement return), with real evidence cited from actions/setup/js.

Requesting changes primarily to add the README documentation entry so the parity test passes; the predicate-shape gap is a nice-to-have follow-up.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · copilot · sonnet50 · 59.1 AIC · ⌖ 13.9 AIC · ⊞ 10.1K
Comment /matt to run again

"no-single-char-string-replace": noSingleCharStringReplaceRule,
"no-unguarded-async-timer-callback": noUnguardedAsyncTimerCallbackRule,
"no-async-foreach-callback": noAsyncForEachCallbackRule,
"no-split-filter-trim-without-trim-map": noSplitFilterTrimWithoutTrimMapRule,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] This PR registers a new rule but does not add a matching ### no-split-filter-trim-without-trim-map`` section to eslint-factory/README.md. The repo has an automated parity check (`src/index-readme-parity.test.ts`) that asserts every exported rule name has a matching README heading — this PR as-is would fail that test.

💡 Details

The PR description claims npm run build, rule unit tests, and npm run lint:setup-js were run and passed, but doesn't mention the full vitest suite. index-readme-parity.test.ts does a structural diff between the rules object in index.ts and ### rule`` headings in README.md (sorted-array equality), so a missing entry fails loudly.

Add a row to the rules table and a ### no-split-filter-trim-without-trim-map`` section (following the existing no-map-parseint-callback pattern) describing the pattern and fix.

@copilot please address this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documented the rule in the linked Rules table and with a supported-pattern/safe-replacement section in 36be333; the README parity test passes.

const callee = node.callee;
if (callee.type !== "MemberExpression" || callee.computed || callee.property.type !== "Identifier" || callee.property.name !== "filter") return;
const predicate = node.arguments[0];
if (!predicate || predicate.type !== "ArrowFunctionExpression" || predicate.params.length !== 1 || predicate.params[0].type !== "Identifier") return;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] The predicate-shape check only accepts ArrowFunctionExpression; a .filter(function (v) { return v.trim(); }) callback with the same bug is silently skipped. No current site in actions/setup/js uses this form, so it's low-severity, but the rule's own message ("Filtering split() output...") implies general coverage.

💡 Suggested fix
if (!predicate || (predicate.type !== "ArrowFunctionExpression" && predicate.type !== "FunctionExpression") || predicate.params.length !== 1 || predicate.params[0].type !== "Identifier") return;

Add a corresponding invalid case to the test file, mirroring the existing no-async-foreach-callback rule which already handles both function shapes.

@copilot please address this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handled in 36be333: both arrow and function-expression filter callbacks are supported, with a function-expression regression case.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

False positives can prompt behavior-changing rewrites, and missing README documentation fails the existing parity check.

3 open issues
What changed in this PR

Adds a warning-level rule to gh-aw’s custom ESLint plugin for split/filter chains that test trimmed values but retain surrounding whitespace.

Changes:

  • Detects trim-based predicates, excluding newline separators.
  • Adds rule tests and documentation-URL validation.
  • Registers and enables the rule as a warning.
File Description
eslint-factory/​src/​rules/​no-split-filter-trim-without-trim-map.ts Implements pattern matching and diagnostics.
eslint-factory/​src/​rules/​no-split-filter-trim-without-trim-map.test.ts Tests accepted and reported patterns and the documentation URL.
eslint-factory/​src/​index.ts Registers the new rule.
eslint-factory/​eslint.config.cjs Enables warning-level diagnostics.

Review effort: Max

Comment on lines +21 to +23
if (node.type === "UnaryExpression" && node.operator === "!") return isTrimmedTruthiness(node.argument, paramName);
if (node.type === "MemberExpression" && !node.computed && node.property.type === "Identifier" && node.property.name === "length") return isTrimCallOn(node.object, paramName);
if (node.type === "BinaryExpression") return isTrimmedTruthiness(node.left, paramName) || isTrimmedTruthiness(node.right, paramName);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handled in 36be333: the matcher now restricts negation to even counts and comparisons to explicit nonempty checks. The cited counterexamples are valid regression cases.

const expression = body.type === "BlockStatement" ? (body.body.length === 1 && body.body[0].type === "ReturnStatement" ? body.body[0].argument : null) : body;
if (!expression || !isTrimmedTruthiness(expression, param)) return;

context.report({ node, messageId: "filterTrimKeepsWhitespace", data: { param } });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handled in 36be333: the rule skips a directly following synchronous trim map, covered by expression-body and single-return-block mapper tests.

"no-single-char-string-replace": noSingleCharStringReplaceRule,
"no-unguarded-async-timer-callback": noUnguardedAsyncTimerCallbackRule,
"no-async-foreach-callback": noAsyncForEachCallbackRule,
"no-split-filter-trim-without-trim-map": noSplitFilterTrimWithoutTrimMapRule,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the linked Rules-table row and backtick-quoted heading with the supported pattern and safe replacement in 36be333; README parity tests pass.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

Copilot AI and others added 2 commits October 18, 2026 07:10
…lter-trim-20261018-6b26026061e0b8ab

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
Copilot AI requested a review from gh-aw-bot October 18, 2026 07:26
@pelikhan
pelikhan merged commit 0af0e45 into main Oct 18, 2026
2 checks passed
Copilot AI pushed a commit that referenced this pull request Oct 18, 2026
…map rule (#69831)

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automation cookie Issue Monster Loves Cookies! eslint

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants