Repository navigation
Comments and strings are never read as code - #4
Merged
Merged
Conversation
Mask, MaskComments, MaskFile and MaskInactivePreprocessor replace the hand-written blankers in grove/astkit that read comments and string literals as code. One linear forward lexer per language family keeps byte offsets and line breaks, keeps interpolation holes as code, and handles the shapes the audit found broken: Python quotes vs Rust lifetimes, `#` in Rust raw strings / JS private fields / PHP attributes, multi-line literals (text blocks, raw strings, templates, heredocs), Go raw strings without escapes, C++ digit separators and `#error` apostrophes, and Kotlin/Swift/PHP/C#/Python interpolation. TestMaskMatchesTreeSitter compares Mask byte by byte with tree-sitter's comment and literal nodes on adversarial testdata; TEXTMASK_CORPUS runs the same check over real repositories. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signatures were sliced from source text, so a comment inside a header stayed in it: `class A(Base): # was Fake`, `fun g(a: Int, // : Thing`, `@interface M : Sup /* <Fake> */ <P>`, and grove parsed those words as types. Comments are tree-sitter extras, so internalast now cuts them from the tree: SignatureBeforeBody drops comment nodes (replacing the hand-written stripHeaderComments lexer), and HeaderText / FirstLineSig give the header or first line without them. Text with nothing to cut is returned unchanged, so comment-free signatures stay byte-identical. Also: - C#/PHP attribute sections are cut from signatures like Java annotations (they stay on Annotations): attribute strings no longer read as types, and a FirstLine signature that was only the attribute line is now the declaration. - A Python def whose parameters wrap gets its whole header up to the colon (`def build( size, ) -> Gadget:`), not `def build(`. - Java, Kotlin and Swift modifier lists skip comment nodes (`@GwtIncompatible // reflection` put the comment in Modifiers). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Rust macro token trees are masked with textmask before the call scan
(comments and char literals threw off the old string-only regex:
`assert_eq!(c, '"', "see bar(1)")` called bar).
- C #define bodies are masked before cMacroCallRe: `printf("value(%d)")`
no longer calls value, nor does a comment on a continued line. Argument
splitting reads the masked body too. A block comment in the middle of
a continued macro body made tree-sitter-c emit an ERROR node and the
macro vanished; cMacroFromError recovers it.
- `#if 0` / `#elif 0` branches are not descended in the C-family walk
(only their alternative), so disabled code declares nothing.
- objcMacroEnums matches on textmask-masked text with `#if 0` branches
masked (blankCComments, which skipped strings without blanking them,
is gone), and directive lines inside an enum body are not members:
`ModeA, #if X ModeB, #endif ModeC` keeps all three.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… from the AST - Java Exported comes from the modifiers node's `public` keyword only; strings.Contains(sig, "public") exported `void publication()` and `String mode = "public"`. - Lombok: isX only for a field whose type node is primitive `boolean` (a "boolean x" initializer string made isKind); `@lombok.Getter` / `@lombok.Setter` (fully qualified) are recognized. - Kotlin `fun interface`: the `fun` token is read from the gap between the interface keyword and the preceding node, so `// just for fun` above a plain interface is not the modifier. - C# `record struct` is the anonymous `struct` token, not a substring of a header that may hold string arguments. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Normalized lines now carry two textmask views aligned with their text: code (comments and literals blanked) and lits (comments blanked). Verbs, section/paragraph/data-item structure and PIC/REDEFINES/OCCURS read code, so `DISPLAY 'PLEASE PERFORM BACKUP'` performs nothing and an inline `*> PERFORM OLD-PARA` (fixed format kept it) is a comment. The CALL verb is found in code and its literal target read at the same offset in lits, so `CALL 'PROG'` still resolves while `DISPLAY 'CALL SUPPORT'` is no call. COPY member scanning uses code instead of its own quote regex. The lines are masked as free format because they are already column-normalized (textmask's own COBOL mode defaults to fixed format and does not detect free format). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cppHeaderMarker matched `class handling for the C API` in a block
comment and the standard `#ifdef __cplusplus / extern "C" { / #endif`
guard, making C headers C++. isCPPHeader masks comments and `#if 0`
branches and blanks the C++-only branches of __cplusplus conditionals
before matching, the same rule as grove's header sniff
(internal/parser sniffHeaderCode), so the layers agree. An unguarded
`extern "C"` is not valid C and still makes the header C++.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Decoys go only where the line ends in code (checked on textmask output), so the program never changes; an index of the decoyed tree must equal the original's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signatures, constructor parameter lists, type alias signatures and the fun-interface check read comments out of the text with textmask instead of relying on comment nodes in the tree, so they stay comment-free on a tree parsed without comments. internalast gains HeaderTextCut and FirstLineSigCut, which take extra byte ranges to cut. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The vendored Kotlin grammar fails on valid code (object : X by y,
f(x).p = v, a primary constructor on the next line), and its recovery
depended on comments: comment tokens are extras, and tree-sitter prices
a recovery by the bytes and lines it skips. A comment line after a {
turned classes into ERROR nodes (okio, Exposed), a class body into a
lambda (okio ByteString), method locals into fields (moshi), and a
string holding /* let an end-of-file block comment surface as a class.
Engine.Parse now blanks Kotlin comments (textmask), cuts comment-only
lines out of the parser's input, and puts them back with tree edits so
node positions are src's. go-tree-sitter's Tree.Edit passes the old end
point as the new one (rows never move), so treeEdit calls ts_tree_edit
through cgo.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
extractObjCNodes only looked at the translation unit's direct children. AFNetworking's UIKit headers wrap the whole @interface in `#if TARGET_OS_IOS`; once the file parsed cleanly the class sat under preproc_if and vanished (it had surfaced only as recovery junk from a broken parse). objcContainers descends into conditional regions (an `#if 0` branch only through its alternative) and `extern "C"` bodies, as extractCNodes does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of the fix for the 2026-10-10 parsing audit: 53 places where grove/astkit read comments, docstrings or string literals as code. The grove PR consumes this.
What changes
textmask. One comment/string masker for every supported language. It replaces seven inconsistent hand-written blankers in grove and astkit.defsignatures include the full header.Exported, Lombok accessors, Kotlinfun interfaceand C#record structcome from the AST, not substring checks.#definecall sites, ObjC macro enums, and COBOL PERFORM/CALL read masked text..hC++ detection ignores comments,#if 0and the__cplusplusguard.#if/extern "C"are found.Tree.EditwritesOldEndPointintonew_end_point, sotreeedit.gocallsts_tree_editthrough cgo. It reaches the native tree by reflection on the unexportedBaseTree.c.TestTreeEditReachesNativeTreeguards it. Please review this part.cmd/decoy. It injects code-shaped comments only where a line ends in code, for the metamorphic decoy invariant in researchci_invariants.py.Testing
go test ./...passes.🤖 Generated with Claude Code
https://claude.ai/code/session_01KXa6Cv1ZUXcdMqXcJfQFhX