|
|
@@ -8,18 +8,39 @@
|
|
|
* see project_go_multi_module_audit memory). Generated stubs frequently
|
|
|
* have no body to trace from, so the agent ends up reading source anyway.
|
|
|
*
|
|
|
- * This helper is a pure path-based classifier consulted at disambiguation
|
|
|
- * time (findSymbol / findAllSymbols / codegraph_search formatting), NOT
|
|
|
- * a hard filter — generated nodes are still in the graph and remain
|
|
|
- * reachable; they just rank LAST when there's a real implementation
|
|
|
- * with the same name.
|
|
|
+ * This is a relevance hint consulted at disambiguation time (findSymbol /
|
|
|
+ * findAllSymbols / explore ranking / codegraph_search formatting), NOT a
|
|
|
+ * hard filter — generated nodes are still in the graph and remain
|
|
|
+ * reachable; they just rank LAST when there's a real implementation with
|
|
|
+ * the same name.
|
|
|
*
|
|
|
- * Scope: suffix patterns only. Most generated files follow the
|
|
|
- * `<basename>.<tool>.<ext>` convention (`.pb.go`, `_grpc.pb.go`,
|
|
|
- * `.g.dart`, `_pb2.py`), and that covers ~all of what we saw in the
|
|
|
- * Go audit. A future addition would be scanning for the canonical
|
|
|
- * `// Code generated by` header during extraction, for the rare files
|
|
|
- * that defy the suffix convention.
|
|
|
+ * Two signals, deliberately separate:
|
|
|
+ *
|
|
|
+ * 1. {@link isGeneratedFile} — PATH only, pure and synchronous. Most
|
|
|
+ * generated files follow the `<basename>.<tool>.<ext>` convention
|
|
|
+ * (`.pb.go`, `_grpc.pb.go`, `.g.dart`, `_pb2.py`). Free to call
|
|
|
+ * anywhere, including in a sort comparator.
|
|
|
+ *
|
|
|
+ * 2. {@link hasGeneratedHeader} — CONTENT banner in the file's head. Go's
|
|
|
+ * own convention is a content marker, not a filename one, so a
|
|
|
+ * generated `payroll.go` sitting beside hand-written use-cases is
|
|
|
+ * invisible to (1) — that is issue #1500. Evaluated ONCE at index time
|
|
|
+ * (the file's content is already in memory for parsing) and persisted
|
|
|
+ * on the file record as `files.generated`; readers get it from the DB
|
|
|
+ * rather than re-reading headers per request. See
|
|
|
+ * GENERATED_CONTENT_PATTERNS below for the banners recognized.
|
|
|
+ *
|
|
|
+ * Consumers that have a bounded candidate list should use the DB-backed
|
|
|
+ * union (`QueryBuilder.getGeneratedPathsAmong` /
|
|
|
+ * `CodeGraph.getGeneratedFilePaths`) so both signals apply; the path-only
|
|
|
+ * check remains the fallback for callers with no database in hand and for
|
|
|
+ * indexes built before the flag existed.
|
|
|
+ *
|
|
|
+ * NOTE for future editors: the banner literals quoted in this file sit
|
|
|
+ * BELOW the header window this detector scans, so the module does not
|
|
|
+ * classify itself. `generated-detection.test.ts` pins that — if you move
|
|
|
+ * the pattern table upward, the test fails rather than the repo silently
|
|
|
+ * demoting its own file.
|
|
|
*/
|
|
|
|
|
|
const GENERATED_PATTERNS: ReadonlyArray<RegExp> = [
|
|
|
@@ -79,3 +100,146 @@ const GENERATED_PATTERNS: ReadonlyArray<RegExp> = [
|
|
|
export function isGeneratedFile(filePath: string): boolean {
|
|
|
return GENERATED_PATTERNS.some((p) => p.test(filePath));
|
|
|
}
|
|
|
+
|
|
|
+// =============================================================================
|
|
|
+// Content-header detection (#1500)
|
|
|
+// =============================================================================
|
|
|
+
|
|
|
+/**
|
|
|
+ * How much of a file's head to consider "the header". Generous enough for a
|
|
|
+ * build-tag block + an Apache-2.0 license preamble (~15 lines) sitting above
|
|
|
+ * the banner, tight enough that a `"// Code generated ... DO NOT EDIT."`
|
|
|
+ * string constant in the *body* of a code generator's own source can't
|
|
|
+ * masquerade as a banner.
|
|
|
+ */
|
|
|
+const HEADER_SCAN_CHARS = 8192;
|
|
|
+const HEADER_SCAN_LINES = 60;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Cheap pre-filter run on the header of EVERY indexed file. Every marker
|
|
|
+ * below contains the stem "generat", so one unanchored scan rejects ~all
|
|
|
+ * hand-written source before any line splitting happens — this is what keeps
|
|
|
+ * content detection off the index-time cost budget.
|
|
|
+ */
|
|
|
+const GENERATED_STEM = /generat/i;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Line-comment leaders across the languages we index. A banner must sit on a
|
|
|
+ * comment line (or inside an open block comment, tracked below): generators
|
|
|
+ * always emit theirs as a comment, and requiring it rules out string literals
|
|
|
+ * and identifiers that merely contain the words.
|
|
|
+ *
|
|
|
+ * `--` covers SQL/Haskell/Lua, `%` LaTeX/Erlang/Prolog, `;` Lisp/asm/ini,
|
|
|
+ * `'` VB, `!` Fortran, `*` a continuation line inside a `/* … *\/` block.
|
|
|
+ */
|
|
|
+const COMMENT_LEADER =
|
|
|
+ /^\s*(?:\/\/|\/\*+|\*+\/?|#+|--+|<!--|%+|;+|'|!|\(\*|\{-|"""|'''|=begin|<#|@rem\b|rem\b)/i;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Openers/closers for block comments, so a banner on an unprefixed line
|
|
|
+ * inside `/* … *\/` (or `<!-- … -->`, or a Python module docstring) still
|
|
|
+ * counts. Deliberately naive — it only runs over a file's first few dozen
|
|
|
+ * lines, where a `/*` inside a string literal is vanishingly rare, and the
|
|
|
+ * worst case of a mis-tracked state is a ranking hint, not a wrong answer.
|
|
|
+ */
|
|
|
+const BLOCK_DELIMS: ReadonlyArray<{ open: string; close: string }> = [
|
|
|
+ { open: '/*', close: '*/' },
|
|
|
+ { open: '<!--', close: '-->' },
|
|
|
+ { open: '"""', close: '"""' },
|
|
|
+ { open: "'''", close: "'''" },
|
|
|
+ { open: '=begin', close: '=end' },
|
|
|
+ { open: '<#', close: '#>' },
|
|
|
+];
|
|
|
+
|
|
|
+/**
|
|
|
+ * The banners themselves. Each is a real convention emitted by a widely-used
|
|
|
+ * generator; the list is precision-first, because a false positive silently
|
|
|
+ * demotes hand-written code in every ranking path.
|
|
|
+ */
|
|
|
+const GENERATED_CONTENT_PATTERNS: ReadonlyArray<RegExp> = [
|
|
|
+ // Go's codified convention — `^// Code generated .* DO NOT EDIT\.$`, defined
|
|
|
+ // by `go generate` and honored by gofmt, golangci-lint and GitHub linguist.
|
|
|
+ // Emitted verbatim by protoc-gen-go, mockgen, sqlc, ent, wire, stringer, and
|
|
|
+ // by in-house generators like the FKIT CRUD in #1500 — where the file is
|
|
|
+ // named `payroll.go` and nothing in the PATH gives it away.
|
|
|
+ /\bcode generated\b.{0,200}?\bdo not edit\b/i,
|
|
|
+ // protoc's Java/C#/Python banner ("Generated by the protocol buffer
|
|
|
+ // compiler. DO NOT EDIT!"), ANTLR, Dagger, FlatBuffers, rust-bindgen,
|
|
|
+ // Xcode asset catalogs, Bazel rules.
|
|
|
+ /\b(?:automatically |auto[- ]?)?generated (?:by|from|with)\b.{0,200}?\bdo not (?:edit|modify|change)\b/i,
|
|
|
+ // The `@generated` marker: the JS/TS ecosystem's convention (Relay, GraphQL
|
|
|
+ // codegen, protobuf-es/Buf, Meta's `@generated SignedSource<<…>>`), also
|
|
|
+ // what linguist and `git diff` collapse on. Guarded against `foo@generated`
|
|
|
+ // and `@@generated` so only a standalone tag matches.
|
|
|
+ /(?:^|[^\p{L}\p{N}_@])@generated\b/u,
|
|
|
+ // .NET's `<auto-generated>` / `<auto-generated />` doc tag: Roslyn, the
|
|
|
+ // WinForms designer, T4 templates, protoc-gen-csharp, EF scaffolding.
|
|
|
+ /<auto-?generated\s*\/?>/i,
|
|
|
+ // swagger-codegen / OpenAPI Generator ("NOTE: This class is auto generated
|
|
|
+ // by OpenAPI Generator"), Thrift ("Autogenerated by Thrift Compiler"),
|
|
|
+ // FlatBuffers ("automatically generated by the FlatBuffers compiler").
|
|
|
+ // "by" is required — bare "automatically generated" appears in hand-written
|
|
|
+ // prose ("the table below is automatically generated at runtime").
|
|
|
+ /\b(?:automatically generated|auto[- ]?generated|autogenerated) by\b/i,
|
|
|
+ // Self-declaring in-house banners that name no tool.
|
|
|
+ /\bthis (?:file|class|code|module) (?:is|was) (?:auto[- ]?)?generated\b/i,
|
|
|
+ // The reverse ordering: "DO NOT EDIT — this is a generated file".
|
|
|
+ /\bdo not (?:edit|modify)\b.{0,120}?\b(?:auto[- ]?generated|generated file|generated code)\b/i,
|
|
|
+];
|
|
|
+
|
|
|
+/**
|
|
|
+ * Whether the head of `content` carries a recognized machine-generation
|
|
|
+ * banner. Bounded to {@link HEADER_SCAN_CHARS} / {@link HEADER_SCAN_LINES},
|
|
|
+ * and the marker must sit on a comment line — a generator's own source, which
|
|
|
+ * holds the banner as a string constant in its body, is not flagged.
|
|
|
+ *
|
|
|
+ * Called once per file during extraction (content is already in memory), NOT
|
|
|
+ * per query: the verdict is persisted on the file record.
|
|
|
+ */
|
|
|
+export function hasGeneratedHeader(content: string): boolean {
|
|
|
+ if (!content) return false;
|
|
|
+
|
|
|
+ const head = content.length > HEADER_SCAN_CHARS ? content.slice(0, HEADER_SCAN_CHARS) : content;
|
|
|
+ // Fast reject for ~every hand-written file: no line splitting, no allocation
|
|
|
+ // (V8 keeps `head` as a sliced view of `content`).
|
|
|
+ if (!GENERATED_STEM.test(head)) return false;
|
|
|
+
|
|
|
+ const lines = head.split('\n');
|
|
|
+ const limit = Math.min(lines.length, HEADER_SCAN_LINES);
|
|
|
+ let openBlock: (typeof BLOCK_DELIMS)[number] | null = null;
|
|
|
+
|
|
|
+ for (let i = 0; i < limit; i++) {
|
|
|
+ const line = lines[i]!;
|
|
|
+ const inBlock = openBlock !== null;
|
|
|
+
|
|
|
+ if (inBlock || COMMENT_LEADER.test(line)) {
|
|
|
+ for (const pattern of GENERATED_CONTENT_PATTERNS) {
|
|
|
+ if (pattern.test(line)) return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Advance the block-comment state AFTER testing, so the opening line of a
|
|
|
+ // `/* Code generated … */` block is itself matched by the leader rule.
|
|
|
+ if (openBlock) {
|
|
|
+ if (line.includes(openBlock.close)) openBlock = null;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ for (const delim of BLOCK_DELIMS) {
|
|
|
+ const at = line.indexOf(delim.open);
|
|
|
+ if (at < 0) continue;
|
|
|
+ // Same-line close (`/* … */`, a one-line docstring) leaves no open block.
|
|
|
+ if (line.indexOf(delim.close, at + delim.open.length) < 0) openBlock = delim;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * The union signal: path convention OR content banner. This is what the
|
|
|
+ * indexer persists to `files.generated`.
|
|
|
+ */
|
|
|
+export function detectGeneratedFile(filePath: string, content: string): boolean {
|
|
|
+ return isGeneratedFile(filePath) || hasGeneratedHeader(content);
|
|
|
+}
|