Skip to content

Plugin: strip comments and strings before PluginScanner regex match#271

Merged
dfed merged 4 commits intomainfrom
claude/plugin-scanner-ignore-comments
Apr 20, 2026
Merged

Plugin: strip comments and strings before PluginScanner regex match#271
dfed merged 4 commits intomainfrom
claude/plugin-scanner-ignore-comments

Conversation

@dfed
Copy link
Copy Markdown
Owner

@dfed dfed commented Apr 20, 2026

Problem

Xcode builds of the Example Package Integration (and any downstream using sourceBuild trait) failed with:

error: Error opening input file '.../SafeDIOutput/SafeDIToolManifest+SafeDI.swift' (No such file or directory)

swift build succeeded — only Xcode failed.

Root cause

PluginScanner.fileContainsRoot ran its @Instantiable\s*\(...isRoot\s*:\s*true regex against raw Swift source, including comments and string literals. When the sourceBuild trait is active, SafeDICore's sources get transitively scanned, and Sources/SafeDICore/Models/SafeDIToolManifest.swift:42 contains the literal text `@Instantiable(isRoot: true)` inside a doc comment. The scanner marked SafeDIToolManifest.swift as containing a root, registered SafeDIToolManifest+SafeDI.swift as an expected output of every module's SafeDIGenerator, and the downstream compile failed when that file wasn't produced.

Why only Xcode: swift build takes the primary plugin path (runs the real SafeDITool scan during plugin setup). Xcode's context.tool(named:).url returns an unresolved ${BUILD_DIR}/${CONFIGURATION}/SafeDITool path at plugin-setup time, so the setup runSafeDITool call throws and the plugin falls back to createBuildCommandsWithPluginScanner, which uses the regex.

Fix

Strip line comments (//), block comments (/* */, nesting supported), and string literals (single- and triple-quoted) from the source before matching. Newlines are preserved so regex position semantics stay consistent. The real parser in SafeDITool remains authoritative — this scanner only predicts output files.

Added a stripSwiftCommentsAndStrings helper and routed both fileContainsRoot and fileContainsGenerateMockTrue through it.

Regression guard

Added an xcodebuild step to the existing Build Package Integration on Xcode 26 CI job. The pre-existing swift build step continues to cover the primary plugin path; the new step covers the fallback (where this bug lived).

Manual verification

$ rm -rf ~/Library/Developer/Xcode/DerivedData/Example_Package_Integration-*
$ cd "Examples/Example Package Integration"
$ xcodebuild -scheme ExamplePackageIntegration -destination "name=iPhone 17"
...
** BUILD SUCCEEDED **

Test plan

  • swift test --traits sourceBuild — 885 tests pass
  • ./CLI/lint.sh
  • Manual xcodebuild on the example — BUILD SUCCEEDED

🤖 Generated with Claude Code

`PluginScanner.fileContainsRoot` and `fileContainsGenerateMockTrue` ran
their `@Instantiable(...)` regex against raw source, which false-positives
when the pattern appears inside comments or string literals. With the
`sourceBuild` trait, SafeDICore's sources get scanned transitively through
the plugin's `SafeDITool` dependency — and
`Sources/SafeDICore/Models/SafeDIToolManifest.swift` has
"`@Instantiable(isRoot: true)`" in a doc comment explaining the manifest
format. The scanner recorded `SafeDIToolManifest+SafeDI.swift` as an
expected output of every downstream module's `SafeDIGenerator`; at build
time SafeDITool didn't produce that file, and SwiftDriver failed trying
to compile it as an input.

Only Xcode triggered this: `swift build` takes the primary plugin path
(real `SafeDITool scan`). Xcode takes the `createBuildCommandsWithPluginScanner`
fallback because `context.tool(named:).url` returns an unresolved build-
variable path at plugin-setup time.

Strip line comments, block comments (nested), and string literals from
the content before regex matching. Preserves line structure so regex
position semantics are stable. The real parser in SafeDITool is still
authoritative; this scan only predicts output files.

Also add an `xcodebuild` step to the existing Package Integration CI job
so the plugin's fallback path stays exercised. The pre-existing
`swift build` step continues to cover the primary path.

Manually verified: `xcodebuild -scheme ExamplePackageIntegration
-destination "name=iPhone 17"` → BUILD SUCCEEDED after fresh DerivedData
wipe.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d55ebc9668

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Plugins/PluginScanner.swift Outdated
dfed and others added 2 commits April 19, 2026 23:25
Extract `stripSwiftCommentsAndStrings` from `Plugins/PluginScanner.swift`
into its own file `Plugins/PluginScannerStringUtilities.swift`, symlinked
into both `Plugins/SafeDIGenerator/` (so the plugin target still compiles
it) and a new `Tests/SafeDIGeneratorPluginTests/` test target.

SPM plugin targets can't be test dependencies (they can only depend on
executable/binary targets), so the symlink pattern matches the existing
one for `Plugins/Shared.swift` — both targets share the same source file
with no duplication.

12 new tests cover: line comments, triple-slash doc comments, block
comments, nested block comments, multi-line block comments (line-count
preservation), double-quoted strings, triple-quoted strings, escaped
quotes inside strings, and the regression case that motivated PR #271
(the `SafeDIToolManifest.swift` doc-comment false positive).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`stripSwiftCommentsAndStrings` naively treated every `/*` as a block-
comment opener. Swift regex literals can contain `/*` (e.g., `/\/*/`
matches zero or more slashes); a file containing such a regex with no
later `*/` would trip the stripper into block-comment mode and run away
through real `@Instantiable(...)` annotations — causing the plugin's
fallback scanner to miss roots and register wrong build-command outputs.

Two fixes:

1. **Lookahead guard on block-comment entry.** Before entering
   block-comment mode on `/*`, scan ahead for a matching `*/` at
   balanced depth. If none exists, treat the `/*` as regular text.
   Defangs runaway consumption for unterminated `/*` (including the
   simple-regex case — `/.../` forms remain ambiguous with division in
   a tokenless scanner, so we can't detect them directly).

2. **Recognize extended regex literals.** `#/…/#` is delimited
   unambiguously; skip its span whole, preserving newlines.

Three new tests cover: `#/.../#` containing an @INSTANTIABLE mention;
`/\/*/` preceding a real @INSTANTIABLE (preserves it); bare
unterminated `/*` (preserves subsequent code).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: acbeccd5b4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Plugins/PluginScannerStringUtilities.swift
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 20, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (8f3ba53) to head (6bb42af).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff            @@
##              main      #271   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           41        41           
  Lines         6796      6796           
=========================================
  Hits          6796      6796           

see 4 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Follow-up to the previous codex P1 fix. `blockCommentHasCloser`'s
naive scan still counted `*/` tokens that appear inside string literals,
so a simple regex `/\/*/` followed later by `let s = "*/"` would still
enter block-comment mode and eat real `@Instantiable` declarations
between them.

Fix: the lookahead now walks through string literals (single and
triple-quoted), line comments, and extended regex literals the same
way the main stripper does, only counting `*/` when at code-level
depth. This matches codex's reported failing input.

Test added: `preservesInstantiable_whenSimpleRegexContainsSlashStarAndLaterStringContainsStarSlash`
pins the exact shape codex cited.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@dfed dfed merged commit cc10145 into main Apr 20, 2026
17 checks passed
@dfed dfed deleted the claude/plugin-scanner-ignore-comments branch April 20, 2026 16:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant