generated-detection.test.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. /**
  2. * Regression coverage for the generated-file detector that drives
  3. * symbol-disambiguation down-ranking. Locked here because the suffix
  4. * list is a contract: if a future edit drops `.pb.go`, the cosmos-sdk
  5. * trace endpoint regresses to the gRPC stub (see
  6. * `project_go_multi_module_audit` memory + the audit in #N/A).
  7. *
  8. * The content-header half (#1500) is a second contract: the marker table is
  9. * precision-first, because a false positive silently demotes hand-written code
  10. * in EVERY ranking path. Measured on a shallow clone of kubernetes/client-go
  11. * (2,453 Go files): the path check flags 0, the content check flags 2,001 —
  12. * exactly the set that greps to the canonical banner, no false positives and
  13. * no misses. Every one of those files has an ordinary name.
  14. */
  15. import { describe, it, expect } from 'vitest';
  16. import * as fs from 'fs';
  17. import * as path from 'path';
  18. import {
  19. isGeneratedFile,
  20. hasGeneratedHeader,
  21. detectGeneratedFile,
  22. } from '../src/extraction/generated-detection';
  23. describe('isGeneratedFile', () => {
  24. it('classifies Go protobuf / gRPC / pulsar / mock outputs as generated', () => {
  25. expect(isGeneratedFile('api/cosmos/bank/v1beta1/tx_grpc.pb.go')).toBe(true);
  26. expect(isGeneratedFile('x/bank/types/tx.pb.go')).toBe(true);
  27. expect(isGeneratedFile('api/cosmos/bank/v1beta1/tx.pulsar.go')).toBe(true);
  28. // cosmos-sdk uses `<base>_mocks.go`; mockgen's default is `mock_<src>.go`;
  29. // many projects use `<base>_mock.go`. All three are mockgen output.
  30. expect(isGeneratedFile('x/auth/testutil/expected_keepers_mocks.go')).toBe(true);
  31. expect(isGeneratedFile('internal/foo_mock.go')).toBe(true);
  32. expect(isGeneratedFile('mock_keeper.go')).toBe(true);
  33. });
  34. it('does not flag the hand-written keeper as generated', () => {
  35. expect(isGeneratedFile('x/bank/keeper/msg_server.go')).toBe(false);
  36. expect(isGeneratedFile('x/bank/keeper/send.go')).toBe(false);
  37. });
  38. it('catches common cross-language codegen suffixes', () => {
  39. expect(isGeneratedFile('app/foo.generated.ts')).toBe(true);
  40. expect(isGeneratedFile('app/foo.generated.tsx')).toBe(true);
  41. expect(isGeneratedFile('proto/bar_pb2.py')).toBe(true);
  42. expect(isGeneratedFile('proto/bar_pb2_grpc.py')).toBe(true);
  43. expect(isGeneratedFile('lib/baz.pb.cc')).toBe(true);
  44. expect(isGeneratedFile('lib/baz.pb.h')).toBe(true);
  45. expect(isGeneratedFile('lib/quux.g.dart')).toBe(true);
  46. expect(isGeneratedFile('lib/quux.freezed.dart')).toBe(true);
  47. });
  48. it('leaves ordinary source files alone', () => {
  49. expect(isGeneratedFile('src/index.ts')).toBe(false);
  50. expect(isGeneratedFile('src/components/Foo.tsx')).toBe(false);
  51. expect(isGeneratedFile('lib/main.dart')).toBe(false);
  52. expect(isGeneratedFile('cmd/server/main.go')).toBe(false);
  53. expect(isGeneratedFile('app/db.py')).toBe(false);
  54. });
  55. });
  56. describe('hasGeneratedHeader — per-marker coverage (#1500)', () => {
  57. // One case per banner the marker table claims to recognize. Each string is
  58. // the real thing a generator emits, not a paraphrase — if a regex is
  59. // narrowed, the case that motivated it fails by name.
  60. const GENERATED: ReadonlyArray<[string, string]> = [
  61. [
  62. 'Go — the #1500 case: ordinary filename, banner below the package clause',
  63. 'package payroll\n\n// Code generated by fkit. DO NOT EDIT.\n\nimport "context"\n\nfunc CreatePayroll(ctx context.Context) error { return nil }\n',
  64. ],
  65. [
  66. 'Go — protoc-gen-go',
  67. '// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// protoc-gen-go v1.28.0\n\npackage pb\n',
  68. ],
  69. [
  70. 'Go — banner under build tags',
  71. '//go:build !windows\n// +build !windows\n\n// Code generated by MockGen. DO NOT EDIT.\npackage mocks\n',
  72. ],
  73. [
  74. 'Go — banner under an Apache-2.0 license preamble',
  75. '// Copyright 2021 The Foo Authors.\n// Licensed under the Apache License, Version 2.0 (the "License");\n// you may not use this file except in compliance with the License.\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an "AS IS" BASIS.\n\n// Code generated by sqlc. DO NOT EDIT.\n// source: query.sql\n\npackage db\n',
  76. ],
  77. [
  78. 'protoc — Java banner ("DO NOT EDIT!")',
  79. '// Generated by the protocol buffer compiler. DO NOT EDIT!\n// source: foo.proto\n\npackage com.example;\n',
  80. ],
  81. [
  82. 'protoc — Python banner behind a coding cookie',
  83. '# -*- coding: utf-8 -*-\n# Generated by the protocol buffer compiler. DO NOT EDIT!\n# source: foo.proto\n',
  84. ],
  85. [
  86. 'C# — Roslyn / designer <auto-generated> block',
  87. '//------------------------------------------------------------------------------\n// <auto-generated>\n// This code was generated by a tool.\n// </auto-generated>\n//------------------------------------------------------------------------------\n',
  88. ],
  89. ['C# — EF self-closing <auto-generated />', '// <auto-generated />\nusing System;\n'],
  90. [
  91. 'JS — Meta/Relay @generated with a SignedSource',
  92. '/**\n * @generated SignedSource<<0123456789abcdef0123456789abcdef>>\n * @flow\n */\n',
  93. ],
  94. [
  95. 'TS — protobuf-es / Buf @generated',
  96. '// @generated by protoc-gen-es v1.2.0 with parameter "target=ts"\n// @generated from file foo.proto (package example, syntax proto3)\n',
  97. ],
  98. [
  99. 'Thrift — "Autogenerated by Thrift Compiler"',
  100. '/**\n * Autogenerated by Thrift Compiler (0.14.1)\n *\n * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING\n */\n',
  101. ],
  102. [
  103. 'OpenAPI Generator — "This class is auto generated by"',
  104. '/*\n * Pet Store API\n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * Do not edit the class manually.\n */\n',
  105. ],
  106. [
  107. 'FlatBuffers — "automatically generated by … do not modify"',
  108. '// automatically generated by the FlatBuffers compiler, do not modify\n\npackage MyGame;\n',
  109. ],
  110. [
  111. 'Rust — bindgen block comment',
  112. '/* automatically generated by rust-bindgen 0.59.2 */\n\npub const FOO: u32 = 1;\n',
  113. ],
  114. ['ANTLR — "Generated from … -- DO NOT EDIT"', '// Generated from Expr.g4 by ANTLR 4.9.2 -- DO NOT EDIT\npackage parser;\n'],
  115. [
  116. 'Wrangler — "Generated by Wrangler by running `wrangler types`" (CG-25)',
  117. '/* eslint-disable */\n// Generated by Wrangler by running `wrangler types` (hash: adcfde101dd7d9077590b6b39d3eaf8d)\n// Runtime types generated with workerd@1.20260708.1 2026-07-12\ndeclare namespace Cloudflare {\n\tinterface Env {}\n}\n',
  118. ],
  119. [
  120. 'the same "regenerate by running" shape from an in-house CLI',
  121. '# Generated by ./scripts/schema-gen.py by running `make schema`\n\nfrom typing import Any\n',
  122. ],
  123. [
  124. 'banner on an unprefixed line INSIDE a block comment',
  125. '/*\n Code generated by ent. DO NOT EDIT.\n*/\npackage ent\n',
  126. ],
  127. [
  128. 'Python — banner inside a module docstring',
  129. '"""Generated by the protocol buffer compiler. DO NOT EDIT!"""\nimport sys\n',
  130. ],
  131. ['YAML/shell — "#" comment leader', '# This file is generated by kustomize. Do not edit.\napiVersion: v1\n'],
  132. ['SQL — "--" comment leader', '-- Code generated by sqlc. DO NOT EDIT.\nCREATE TABLE foo (id INT);\n'],
  133. ['HTML/XML — "<!--" comment leader', '<!-- Autogenerated by docgen. Do not edit. -->\n<html></html>\n'],
  134. ];
  135. it.each(GENERATED)('flags: %s', (_label, source) => {
  136. expect(hasGeneratedHeader(source)).toBe(true);
  137. });
  138. // Precision cases. Each is a shape that a looser marker table WOULD flag.
  139. const HAND_WRITTEN: ReadonlyArray<[string, string]> = [
  140. [
  141. 'ordinary Go source',
  142. 'package keeper\n\nimport "context"\n\n// SendCoins moves coins between accounts.\nfunc (k Keeper) SendCoins(ctx context.Context) error { return nil }\n',
  143. ],
  144. [
  145. 'a generator\'s own source, which merely talks about generating',
  146. '// This package generates SQL migrations from the schema.\n// The generated output lives under db/migrations.\npackage gen\n',
  147. ],
  148. [
  149. 'prose using "automatically generated" without naming a tool',
  150. '"""Report builder.\n\nThe summary table is automatically generated at runtime from the\nrows below; callers should not edit it in place.\n"""\n',
  151. ],
  152. [
  153. 'a generator holding the banner as a string constant in its BODY',
  154. 'package main\n\n// Package main implements the fkit CRUD generator.\n\nimport "fmt"\n\nfunc header() string {\n\treturn "// Code generated by fkit. DO NOT EDIT."\n}\n',
  155. ],
  156. ['an email address that happens to contain "@generated"', '// Contact: build@generated.example.com for issues.\npackage main\n'],
  157. ['"DO NOT EDIT" with no generation claim', '// DO NOT EDIT THIS FILE BY HAND — run `make fmt` instead.\npackage main\n'],
  158. [
  159. 'prose: bare "generated by" naming no tool and no reproduction command (CG-25)',
  160. '// The table below is generated by the build at runtime, so the\n// literal values here are only a fallback.\npackage main\n',
  161. ],
  162. [
  163. 'prose: "generated by running …" — one "by" clause, not the Wrangler shape (CG-25)',
  164. '// The nightly summary is generated by running the ETL job against\n// yesterday\'s partition.\npackage main\n',
  165. ],
  166. ['empty file', ''],
  167. ];
  168. it.each(HAND_WRITTEN)('does not flag: %s', (_label, source) => {
  169. expect(hasGeneratedHeader(source)).toBe(false);
  170. });
  171. it('only looks at the header — a banner buried 80 lines down is not a banner', () => {
  172. const filler = Array.from({ length: 80 }, (_, i) => `// filler line ${i}`).join('\n');
  173. expect(hasGeneratedHeader(`${filler}\n// Code generated by foo. DO NOT EDIT.\npackage main\n`)).toBe(false);
  174. // …but the same banner within the window is caught.
  175. const shortFiller = Array.from({ length: 20 }, (_, i) => `// filler line ${i}`).join('\n');
  176. expect(hasGeneratedHeader(`${shortFiller}\n// Code generated by foo. DO NOT EDIT.\npackage main\n`)).toBe(true);
  177. });
  178. it('requires a comment line — the same words in executable code are not a banner', () => {
  179. // No comment leader, no open block: this is a bare statement.
  180. expect(hasGeneratedHeader('const banner = "Code generated by tool. DO NOT EDIT.";\n')).toBe(false);
  181. });
  182. it('does not classify the detector module itself (the pattern table must stay below the header window)', () => {
  183. const self = fs.readFileSync(
  184. path.join(__dirname, '..', 'src', 'extraction', 'generated-detection.ts'),
  185. 'utf-8'
  186. );
  187. expect(hasGeneratedHeader(self)).toBe(false);
  188. });
  189. });
  190. describe('detectGeneratedFile — the union the indexer persists', () => {
  191. it('is true when only the PATH says so', () => {
  192. expect(detectGeneratedFile('x/bank/types/tx.pb.go', 'package types\n')).toBe(true);
  193. });
  194. it('is true when only the CONTENT says so — the #1500 acceptance case', () => {
  195. // A Go file named `payroll.go` sitting beside hand-written workflow
  196. // use-cases. Nothing in the path gives it away.
  197. expect(
  198. detectGeneratedFile('internal/payroll/payroll.go', 'package payroll\n\n// Code generated by fkit. DO NOT EDIT.\n\nfunc Create() {}\n')
  199. ).toBe(true);
  200. expect(isGeneratedFile('internal/payroll/payroll.go')).toBe(false);
  201. });
  202. it('is false for a hand-written file with an ordinary name', () => {
  203. expect(
  204. detectGeneratedFile('internal/payroll/workflow.go', 'package payroll\n\n// RunPayrollWorkflow drives the monthly run.\nfunc RunPayrollWorkflow() {}\n')
  205. ).toBe(false);
  206. });
  207. });