fix(cli): wheels validate must strip CFML comments before checking extends#2595
Conversation
…tends
The `validateModel()` / `validateController()` checks in
`cli/lucli/services/Analysis.cfc` searched the raw file content for
`extends="Model"` / `extends="Controller"`, so a commented-out
declaration above an inheritance-less `component { … }` body satisfied
the substring match and the validator reported success. Strip line,
block, and tag-style CFML comments before the check, and add specs that
pin the behavior for both models and controllers.
Fixes #2491
Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
… extends declaration Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Wheels Bot — Docs updatedAdded a doc commit to this PR:
|
There was a problem hiding this comment.
Wheels Bot -- Reviewer A
This PR correctly fixes the false-pass in wheels validate reported in #2491. The implementation applies $stripCfmlComments() to file content before the extends= substring check in both validateModel() and validateController(), four specs cover the main scenarios, both commits carry DCO trailers and conform to commitlint, the guide doc is updated, and the CHANGELOG entry is accurate. I am leaving a comment (not requesting changes) because the implementation is correct and the issues below are test-quality nits rather than shipping bugs.
Correctness: Vacuous-pass in the still-passes spec (cli/lucli/tests/specs/services/AnalysisSpec.cfc lines 174-179)
The positive test writes CommentedAndReal.cfc, calls validate(), then asserts inside a conditional loop body. When the fix is working correctly, validate() returns zero issues for this file so the loop body is never entered and expect() is never reached -- the test passes vacuously. A regression that silently drops the file from the scan would also pass, providing no protection. The pattern used by the negative tests (accumulate into issueMessages, assert after the loop) does not have this flaw and should be applied symmetrically. Suggested replacement: declare var foundBadIssue = false before the loop, set it to true if the issue file and message both match, then call expect(foundBadIssue).toBeFalse() after the loop.
Tests: Tag-style CFML comment path not exercised
$stripCfmlComments() applies three substitutions in order: tag-style comment, block comment, line comment. The new specs cover the line and block paths but no spec places extends=Model inside a CFML tag-style comment (the four-dash form). A typo in that first regex would be invisible to the suite. A fifth spec analogous to the block-comment one would complete coverage of all three stripping paths.
Conventions: $stripCfmlComments is now the third copy of the same function
Module.cfc at line 5707 has stripCfmlComments (identical three-regex sequence). Doctor.cfc at line 569 has $stripCfmlBlockComments (first two regexes only, no line-comment step). Analysis.cfc at line 386 adds a byte-for-byte copy of the Module.cfc version. The PR body acknowledges this. Pre-existing accumulation, out of scope for a targeted fix -- flagging for a consolidation follow-up in Helpers.cfc.
Conventions: Indentation drift in the first new it() block
AnalysisSpec.cfc line 115 has the it() opener at four-tab indent, one level deeper than the three it() blocks that follow at lines 140, 163, 184. Cosmetic; easy one-line fix.
Cross-engine: No issues. reReplace() with character classes and the all flag is supported identically on Lucee, Adobe CF, and BoxLang. No closures, scope references, struct-member function collisions, or Left(str, 0) patterns.
Commits: Two commits, both conforming to commitlint. fix(cli): wheels validate must strip CFML comments before checking extends (a70c02f) -- type fix, scope cli, subject 72 chars, DCO trailer present. docs(web/guides): clarify wheels validate checks active uncommented extends declaration (5f42850) -- type docs, scope web/guides, full subject 87 chars (within the 100-char cap), DCO trailer present.
Security: No concerns. The fix operates on local filesystem content read by the CLI; it does not touch any HTTP surface, user-supplied runtime input, or SQL.
Wheels Bot — Reviewer B (round 1)A's review is substantively accurate and I am converging with changes needed. The vacuous-pass finding is the most important item: A correctly identified that Sycophancy
False positivesNone detected. Every specific claim A made maps accurately to the diff and the actual file. Missed issuesNone detected. The cross-engine analysis is correct ( Verdict alignmentA chose ConvergenceAligned; changes needed. Two concrete items for the address-review pass:
|
Wheels Bot — Reviewer B (round 1)Note: review 4268512214 (the trigger ID) contains only a test/auth-probe body ("test body for auth check") with no line comments. The substantive Reviewer A content is in review 4268513773, submitted 30 seconds later by the same bot on the same commit. This critique is of 4268513773. A correctly identified the two real test-quality gaps (vacuous-pass spec, missing tag-style coverage) and made accurate technical claims throughout. No false positives. The implementation analysis and cross-engine / security sections are sound. My disagreement is narrow: the characterization of the vacuous-pass finding as a "nit" undersells it, and the COMMENTED verdict leaves both gaps unaddressed. I'm aligning on findings and recommending the changes be applied. Sycophancy
False positivesNone detected. Verified against the diff:
Missed issuesNone of significance. One nuance on A's risk framing: A's phrasing implies the positive spec provides zero protection, but a regression that silently drops the file from the scan entirely would also cause the two negative specs to fail, since they rely on the same scan path. The vacuous-pass gap is specifically that the positive spec does not independently assert "zero issues found for this file." That is still worth fixing; it is not the alarming standalone gap A's wording implies. Verdict alignmentA's COMMENTED state is internally consistent — A listed findings but chose not to block the merge. However, both findings are concrete and actionable: A even supplied the exact fix pattern for the vacuous-pass. A COMMENTED verdict that yields no action is inconsistent with providing a specific fix suggestion. Request-changes would be the appropriate state. ConvergenceAligned on all findings. No false-positive disputes, no missed issues. Upgrading the verdict to request-changes and recommending the two test improvements be applied:
The implementation in |
- AnalysisSpec.cfc: replace conditional expect() inside the CommentedAndReal.cfc loop with an explicit boolean flag pattern so the assertion runs unconditionally after the loop (no longer vacuous when the fix is working correctly). - AnalysisSpec.cfc: add a fifth `it()` covering the tag-style CFML comment path (`<!--- ... --->`), the first substitution in $stripCfmlComments() that previously had no spec. Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Wheels Bot — Address Review (round 1)Applied consensus findings from Reviewer A and Reviewer B's convergence (round 1):
The implementation in Note on local test execution: The new commit will trigger a fresh Reviewer A run on the updated SHA. Convergence loop continues until reviewers align on |
There was a problem hiding this comment.
Wheels Bot - Reviewer A
The round-1 fix commit (a9d2362) addresses both consensus findings from the previous review: the vacuous-pass in the CommentedAndReal.cfc test has been replaced with an explicit boolean-flag pattern that runs the assertion unconditionally, and a fifth it() now covers the tag-style CFML comment path that had no spec before. The core bug fix in Analysis.cfc is unchanged and correct. The PR is in good shape; I have one cosmetic nit remaining and the pre-existing duplicate-function note, both non-blocking.
Conventions
Indentation inconsistency in the new it() blocks (cli/lucli/tests/specs/services/AnalysisSpec.cfc, lines 115-231)
The four pre-existing it() blocks inside describe("validate()") (lines 80-113) each indent their body at five tabs and close with a four-tab closer. The five new it() blocks use a mixed pattern: the first opener (line 115) is at four tabs while its body and closing drop to three or four tabs; the remaining four openers (lines 140, 163, 186, 210) are at three tabs (one level short of the describe container) while their bodies are at four tabs. The code compiles and the tests register correctly because CFML closures are indentation-insensitive, but the inconsistency makes the nesting structure harder to read. A re-indent of lines 115-231 to match the surrounding four-tab opener / five-tab body convention would close this out.
Three copies of stripCfmlComments (pre-existing, noted for tracking)
Module.cfc:5707, Doctor.cfc:569 (two-regex variant), and Analysis.cfc:386 all implement the same three-regex strip. The PR body flags this as a consolidation candidate. No action required here; flagging for a follow-up extraction into Helpers.cfc.
Tests
All five new specs exercise distinct code paths (line comment, block comment, tag-style comment, commented-copy-alongside-real, controller variant). The CommentedAndReal.cfc spec now sets foundBadIssue = false before the loop and asserts expect(foundBadIssue).toBeFalse() after it, eliminating the vacuous-pass from round 1. The tag-style spec mirrors the block-comment spec and provides missing coverage for the first substitution in stripCfmlComments(). Clean.
Correctness
The stripCfmlComments() implementation is byte-for-byte identical to the copy in Module.cfc:5707 (three reReplace calls, same order, same patterns). The regexes handle cross-newline spans correctly via the non-greedy quantifier and prevent over-stripping. No off-by-one or null-deref risks introduced.
Cross-engine
reReplace() with character-class patterns and the all flag behaves identically across Lucee, Adobe CF, and BoxLang. No closures, reserved scope names, bracket-notation calls, struct-member function collisions, or Left(str, 0) patterns in the changed code.
Commits
Three commits on this branch relative to origin/develop:
- a70c02f: fix(cli): wheels validate must strip CFML comments before checking extends - 72 chars, DCO trailer present.
- 5f42850: docs(web/guides): clarify wheels validate checks active (uncommented) extends declaration - 87 chars, DCO trailer present.
- a9d2362: fix(cli): address Reviewer A/B consensus findings (round 1) - 59 chars, DCO trailer present.
All three conform to commitlint (type, optional scope, subject within 100 chars, no ALL-CAPS).
Security
No concerns. The change operates on local filesystem content read by the CLI; no HTTP surface, no SQL, no user-supplied runtime input affected.
Wheels Bot — Reviewer B (round 1)A's review of the round-1 address commit is substantively accurate and I am converging on approve. The two findings A raises are correct (indentation inconsistency, pre-existing duplicate helper), both genuinely non-blocking, and the implementation and test analysis throughout is sound. The only friction is A's choice of COMMENTED state when their own summary is 'PR is in good shape... both non-blocking' — that warrants APPROVE, not COMMENTED. Sycophancy
False positivesNone detected. Verified against the diff:
Missed issuesNone detected.
Verdict alignmentA's findings are two non-blocking items; APPROVE is the consistent verdict. COMMENTED is not wrong about the substance but leaves the review state ambiguous. ConvergenceAligned. A's substantive analysis is correct on every point. No false-positive disputes, no missed issues. The only gap is A's conservative use of COMMENTED rather than APPROVE, which does not change the underlying conclusion that this PR is merge-ready pending human sign-off. Converging on approve. |
…ti-patterns #11-14 (#2740) CLAUDE.md was 1133 lines, with 10 of 17 listed reference subdirectories missing and several broken doc paths (config/services.cfm, docs/src/...). Reorders, dedups, and trims to 714 lines (-37%) with all internal links now resolving. Top-of-file restructured for high-leverage content first: - Code Map: where framework/demo/CLI live and how they relate - Before-Done checklist: which test suite to run for which change type - Cross-Engine Invariants: promoted from buried Docker / Browser-Testing locations - Anti-Patterns extended Top 10 -> Top 14, all new entries sourced from recent PRs: - #11 CFML reserved scopes shadow parameters (#2591) - #12 Empty array in whereIn / whereNotIn (#2736) - #13 Comma-list config != single-value HTTP header (#2725) - #14 Strip CFML comments before source-scanning (#2595) Extracted sections, loaded only when relevant: - .ai/wheels/deploy.md (92 lines) - wheels deploy Kamal port reference - .ai/wheels/wheels-bot.md (34 lines) - bot architecture - .ai/wheels/testing/browser-testing.md (68 lines) - browser DSL Other dedups: t.timestamps() 3-column rule, mixed-argument-style rule, and the WheelsTest-only-for-new-tests reminder each appear in one canonical location now. Reference Docs list at bottom lists only verified-to-exist files. Signed-off-by: Peter Amiri <peter@alurium.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
wheels validatepreviously accepted models and controllers whose onlyextends="Model"/extends="Controller"declaration was inside a CFML comment.validateModel()/validateController()incli/lucli/services/Analysis.cfcperformed a rawfindNoCase('extends="Model"', content)over the whole file content, so a file like// component extends="Model" { component { function config() {} }satisfied the substring match and the validator reported success even though the active
component { … }declaration has no inheritance. This PR strips line comments (// …), block comments (/* … */), and tag-style CFML comments (<!--- … --->) from the file content before running theextends=check, and adds specs to pin the behavior for both the bug case and the legitimate "commented snippet next to a real declaration" case.The fix lives in a small private
$stripCfmlComments()helper insideAnalysis.cfc(mirrors the existingstripCfmlCommentshelper inModule.cfcand$stripCfmlBlockCommentsinDoctor.cfc). The same fix applies tovalidateController()since it had the identical bug shape.Related Issue
Closes #2491
Type of Change
Feature Completeness Checklist
Signed-off-by:trailer matching the configured git author identitycli/lucli/tests/specs/services/AnalysisSpec.cfccovering: (a) commented line// component extends="Model", (b) commented block/* … extends="Model" … */, (c) commented copy + realextends="Model"(must still pass), and (d) commentedextends="Controller".bot-update-docs.ymlhandles MDX follow-up.bot-update-docs.ymlhandles.ai/wheels/follow-up.bot-update-docs.ymlhandles convention-doc follow-up.[Unreleased] → Fixed.Test Plan
The new specs are deterministic and exercise pure string-handling code (no DB, no engine-specific behavior). Reasoning that confirms the failing → passing transition:
findNoCase('extends="Model"', content)returns a positive position for the commented// component extends="Model" {line, so the inheritance check is satisfied and no issue is appended. The new "flags model where extends is only present in a line comment" spec assertsdoes not extend Modelis present in the validator's issue messages — that assertion must fail.$stripCfmlComments()removes the// …line, leaving onlycomponent { … }.findNoCasenow returns 0 for bothextends="Model"andextends='Model', so the validator appends the "does not extend Model" issue — assertion passes.extends="Model"" spec confirms the strip doesn't go too far: the real declaration is preserved and the validator stays silent.cli/lucli/services/Analysis.cfcmodified (implementation)cli/lucli/tests/specs/services/AnalysisSpec.cfcmodified (failing → passing specs)commitlint.config.js,package.json,.github/workflows/pr.yml, or any unrelated branch