From 8e4cec889803cce4ae8654117ab87b6efa669eb8 Mon Sep 17 00:00:00 2001 From: Asyncronize <56194330+Asyncronize@users.noreply.github.com> Date: Fri, 30 Oct 2026 13:21:12 +0800 Subject: [PATCH 01/17] feat(plsql): add Oracle PL/SQL language support (standalone ANTLR4 provider) Adds SupportedLanguages.PlSql as an experimental standalone provider following the COBOL precedent: a `plsql` phase (after structure, before parse) parses PL/SQL with the grammars-v4 PlSql grammar on antlr4ng (pure JS, no native binding) and writes the graph; the scope resolver runs in callable-flow-only mode. - package spec+body -> Module; procedure/function/nested/trigger -> Function - CALLS for local, nested, pkg.proc and schema.pkg.proc calls; overloads narrowed by arity and named arguments, otherwise low-confidence fan-out - table/view -> Record with ACCESSES reason plsql- (select, insert, update, delete, merge, lock, trigger) - SQL*Plus @@/@/START includes -> IMPORTS (exact paths only) - .sql claimed via classifyFileContent only for Oracle sources - line/column-preserving pre-pass (SQL*Plus commands, & substitution, nested conditional compilation), per-segment SLL->LL parse with a deadline, regex fallback for definitions lost to syntax errors - rename: spec+body family, END labels, package qualifiers, cross-file calls; refuses on dynamic SQL, unparsed segments or unproven names - shared plsql/resolve.ts decides both CALLS edges and rename bindings - vendored grammar + scripts/generate-plsql-parser.mjs (reproducible) - tests: resolver integration, unit, rename fixtures (success + refusal), new-language gate registrations; docs/languages/plsql-ingestion-gaps.md Co-Authored-By: Claude Opus 5.5 --- .prettierignore | 2 + ARCHITECTURE.md | 5 +- README.md | 4 +- docs/languages/README.md | 3 +- eslint.config.mjs | 2 + gitnexus-shared/src/language-detection.ts | 14 + gitnexus-shared/src/languages.ts | 2 + .../language-classification.ts | 2 + gitnexus/package-lock.json | 7 + gitnexus/package.json | 1 + gitnexus/scripts/generate-plsql-parser.mjs | 67 + .../src/core/ingestion/languages/index.ts | 2 + .../src/core/ingestion/languages/plsql.ts | 49 + .../ingestion/languages/plsql/captures.ts | 153 + .../core/ingestion/languages/plsql/flow.ts | 15 + .../core/ingestion/languages/plsql/index.ts | 13 + .../ingestion/languages/plsql/interpret.ts | 43 + .../core/ingestion/languages/plsql/rename.ts | 185 + .../languages/plsql/scope-resolver.ts | 157 + .../core/ingestion/pipeline-phases/index.ts | 1 + .../core/ingestion/pipeline-phases/parse.ts | 4 +- .../core/ingestion/pipeline-phases/plsql.ts | 70 + gitnexus/src/core/ingestion/pipeline.ts | 4 +- .../src/core/ingestion/plsql-processor.ts | 361 + gitnexus/src/core/ingestion/plsql/extract.ts | 650 + .../src/core/ingestion/plsql/file-types.ts | 77 + .../ingestion/plsql/generated/PlSqlLexer.ts | 17382 + .../plsql/generated/PlSqlLexerBase.ts | 21 + .../ingestion/plsql/generated/PlSqlParser.ts | 309538 +++++++++++++++ .../plsql/generated/PlSqlParserBase.ts | 117 + .../plsql/generated/PlSqlParserListener.ts | 13489 + .../core/ingestion/plsql/generated/README.md | 18 + .../ingestion/plsql/grammar/PlSqlLexer.g4 | 2593 + .../ingestion/plsql/grammar/PlSqlParser.g4 | 10147 + gitnexus/src/core/ingestion/plsql/parse.ts | 167 + .../src/core/ingestion/plsql/preprocess.ts | 254 + gitnexus/src/core/ingestion/plsql/resolve.ts | 101 + .../core/ingestion/plsql/script-include.ts | 43 + .../scope-resolution/pipeline/registry.ts | 2 + .../src/core/ingestion/tree-sitter-queries.ts | 1 + gitnexus/src/core/parse-cache-identity.ts | 1 + .../lang-resolution/plsql-app/db/install.sql | 13 + .../plsql-app/db/packages/pkg_loan_settle.pkb | 68 + .../plsql-app/db/packages/pkg_loan_settle.pks | 5 + .../plsql-app/db/packages/pkg_log.pkb | 21 + .../plsql-app/db/packages/pkg_log.pks | 6 + .../plsql-app/db/postgres/fn_pg.sql | 5 + .../plsql-app/db/procedures/prc_archive.prc | 8 + .../db/triggers/trg_repay_plan_audit.sql | 9 + .../after/pkg_log.pkb | 16 + .../after/pkg_log.pks | 5 + .../after/prc_settle.prc | 8 + .../before/pkg_log.pkb | 16 + .../before/pkg_log.pks | 5 + .../before/prc_settle.prc | 8 + .../package-procedure-cross-file/case.json | 37 + .../refuse-dynamic-sql/after/pkg_fee.pkb | 15 + .../refuse-dynamic-sql/before/pkg_fee.pkb | 15 + .../plsql/refuse-dynamic-sql/case.json | 16 + .../integration/rename-provider-plsql.test.ts | 3 + .../resolvers/callable-value-flow.test.ts | 3 + .../test/integration/resolvers/plsql.test.ts | 194 + .../ingestion/pipeline-phase-registry.test.ts | 1 + gitnexus/test/unit/plsql-extract.test.ts | 172 + gitnexus/test/unit/plsql-preprocess.test.ts | 106 + .../external-import-conformance.test.ts | 15 + ...import-target-index-reuse.contract.test.ts | 16 + 67 files changed, 356545 insertions(+), 8 deletions(-) create mode 100644 gitnexus/scripts/generate-plsql-parser.mjs create mode 100644 gitnexus/src/core/ingestion/languages/plsql.ts create mode 100644 gitnexus/src/core/ingestion/languages/plsql/captures.ts create mode 100644 gitnexus/src/core/ingestion/languages/plsql/flow.ts create mode 100644 gitnexus/src/core/ingestion/languages/plsql/index.ts create mode 100644 gitnexus/src/core/ingestion/languages/plsql/interpret.ts create mode 100644 gitnexus/src/core/ingestion/languages/plsql/rename.ts create mode 100644 gitnexus/src/core/ingestion/languages/plsql/scope-resolver.ts create mode 100644 gitnexus/src/core/ingestion/pipeline-phases/plsql.ts create mode 100644 gitnexus/src/core/ingestion/plsql-processor.ts create mode 100644 gitnexus/src/core/ingestion/plsql/extract.ts create mode 100644 gitnexus/src/core/ingestion/plsql/file-types.ts create mode 100644 gitnexus/src/core/ingestion/plsql/generated/PlSqlLexer.ts create mode 100644 gitnexus/src/core/ingestion/plsql/generated/PlSqlLexerBase.ts create mode 100644 gitnexus/src/core/ingestion/plsql/generated/PlSqlParser.ts create mode 100644 gitnexus/src/core/ingestion/plsql/generated/PlSqlParserBase.ts create mode 100644 gitnexus/src/core/ingestion/plsql/generated/PlSqlParserListener.ts create mode 100644 gitnexus/src/core/ingestion/plsql/generated/README.md create mode 100644 gitnexus/src/core/ingestion/plsql/grammar/PlSqlLexer.g4 create mode 100644 gitnexus/src/core/ingestion/plsql/grammar/PlSqlParser.g4 create mode 100644 gitnexus/src/core/ingestion/plsql/parse.ts create mode 100644 gitnexus/src/core/ingestion/plsql/preprocess.ts create mode 100644 gitnexus/src/core/ingestion/plsql/resolve.ts create mode 100644 gitnexus/src/core/ingestion/plsql/script-include.ts create mode 100644 gitnexus/test/fixtures/lang-resolution/plsql-app/db/install.sql create mode 100644 gitnexus/test/fixtures/lang-resolution/plsql-app/db/packages/pkg_loan_settle.pkb create mode 100644 gitnexus/test/fixtures/lang-resolution/plsql-app/db/packages/pkg_loan_settle.pks create mode 100644 gitnexus/test/fixtures/lang-resolution/plsql-app/db/packages/pkg_log.pkb create mode 100644 gitnexus/test/fixtures/lang-resolution/plsql-app/db/packages/pkg_log.pks create mode 100644 gitnexus/test/fixtures/lang-resolution/plsql-app/db/postgres/fn_pg.sql create mode 100644 gitnexus/test/fixtures/lang-resolution/plsql-app/db/procedures/prc_archive.prc create mode 100644 gitnexus/test/fixtures/lang-resolution/plsql-app/db/triggers/trg_repay_plan_audit.sql create mode 100644 gitnexus/test/fixtures/rename-integration/plsql/package-procedure-cross-file/after/pkg_log.pkb create mode 100644 gitnexus/test/fixtures/rename-integration/plsql/package-procedure-cross-file/after/pkg_log.pks create mode 100644 gitnexus/test/fixtures/rename-integration/plsql/package-procedure-cross-file/after/prc_settle.prc create mode 100644 gitnexus/test/fixtures/rename-integration/plsql/package-procedure-cross-file/before/pkg_log.pkb create mode 100644 gitnexus/test/fixtures/rename-integration/plsql/package-procedure-cross-file/before/pkg_log.pks create mode 100644 gitnexus/test/fixtures/rename-integration/plsql/package-procedure-cross-file/before/prc_settle.prc create mode 100644 gitnexus/test/fixtures/rename-integration/plsql/package-procedure-cross-file/case.json create mode 100644 gitnexus/test/fixtures/rename-integration/plsql/refuse-dynamic-sql/after/pkg_fee.pkb create mode 100644 gitnexus/test/fixtures/rename-integration/plsql/refuse-dynamic-sql/before/pkg_fee.pkb create mode 100644 gitnexus/test/fixtures/rename-integration/plsql/refuse-dynamic-sql/case.json create mode 100644 gitnexus/test/integration/rename-provider-plsql.test.ts create mode 100644 gitnexus/test/integration/resolvers/plsql.test.ts create mode 100644 gitnexus/test/unit/plsql-extract.test.ts create mode 100644 gitnexus/test/unit/plsql-preprocess.test.ts diff --git a/.prettierignore b/.prettierignore index c85876d08..b067eb682 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,6 +1,8 @@ dist/ coverage/ gitnexus/vendor/ +# ANTLR4-generated PL/SQL lexer/parser (regenerate, never hand-edit). +gitnexus/src/core/ingestion/plsql/generated/ gitnexus/test/fixtures/ # Line/column-sensitive callable identity and taint regression. gitnexus/test/integration/cfg/fixtures/inline-taint-repo/ diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 4635dbff8..f4b9be85b 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -85,7 +85,7 @@ Group-mode `trace` (`gitnexus/src/core/group/cross-trace.ts`) stitches a path th 19 default phases are defined in `gitnexus/src/core/ingestion/pipeline-phases/`, each with explicit `deps` and typed output. `--pdg` adds `taintSummaries` and `callSummaries` (21 total). ``` -scan → structure → [springConfig, markdown, cobol] → parse → [routes, tools, orm] +scan → structure → [springConfig, markdown, cobol, plsql] → parse → [routes, tools, orm] → crossFile → scopeResolution → [springAutoConfiguration, springAop] → pruneLocalSymbols → mro → springAopInheritance → di → communities → processes ``` @@ -97,7 +97,8 @@ scan → structure → [springConfig, markdown, cobol] → parse → [routes, to | `springConfig` | `spring-config.ts` | `structure` | Spring configuration-property nodes and metadata | | `markdown` | `markdown.ts` | `structure` | Section nodes, cross-link edges from .md/.mdx | | `cobol` | `cobol.ts` | `structure` | COBOL program/paragraph/section nodes (regex, no tree-sitter) | -| `parse` | `parse.ts` + `parse-impl.ts` | `structure`, `markdown`, `cobol` | Symbol nodes, IMPORTS/CALLS/EXTENDS edges, extracted routes/tools/ORM queries | +| `plsql` | `plsql.ts` | `structure` | Oracle PL/SQL package/subprogram/trigger/table nodes; CALLS/ACCESSES/IMPORTS edges (ANTLR4) | +| `parse` | `parse.ts` + `parse-impl.ts` | `structure`, `markdown`, `cobol`, `plsql` | Symbol nodes, IMPORTS/CALLS/EXTENDS edges, extracted routes/tools/ORM queries | | `routes` | `routes.ts` | `parse` | Route nodes + HANDLES_ROUTE edges (Next.js, Expo, PHP, decorators, and JS/TS static route sources — see below) | | `tools` | `tools.ts` | `parse` | Tool nodes + HANDLES_TOOL edges | | `orm` | `orm.ts` | `parse` | QUERIES edges (Prisma, Supabase) | diff --git a/README.md b/README.md index 9c0eb862f..ef982e70a 100644 --- a/README.md +++ b/README.md @@ -112,8 +112,8 @@ For Docker, hosted deployments, image verification, or a local frontend, see ## Supported languages -C, C++, C#, COBOL, Dart, Go, Java, JavaScript, Kotlin, Objective-C, PHP, Python, -Ruby, Rust, Swift, TypeScript, Vue and Zig have registered providers. Support +C, C++, C#, COBOL, Dart, Go, Java, JavaScript, Kotlin, Objective-C, Oracle PL/SQL, +PHP, Python, Ruby, Rust, Swift, TypeScript, Vue and Zig have registered providers. Support varies by construct; see the [language inventories](docs/languages/README.md) for measured behavior, exclusions and remaining gaps. diff --git a/docs/languages/README.md b/docs/languages/README.md index 23121f260..3d56f2f14 100644 --- a/docs/languages/README.md +++ b/docs/languages/README.md @@ -1,7 +1,7 @@ # Language Ingestion Gap Inventories These inventories are the starting point for language-specific plans. There is one -`-ingestion-gaps.md` file for each of the 18 languages in +`-ingestion-gaps.md` file for each of the 19 languages in [SCOPE_RESOLVERS](../../gitnexus/src/core/ingestion/scope-resolution/pipeline/registry.ts). Names match provider directories; `jvm` is shared infrastructure, not another language. @@ -27,6 +27,7 @@ receiver examples do not establish whole-language, CFG/PDG or rename completenes | [JavaScript](javascript-ingestion-gaps.md) | Preserve JSDoc container evidence for indexed receivers. | | [Kotlin](kotlin-ingestion-gaps.md) | Inventory companion and inherited-receiver limits. | | [Objective-C](objective-c-ingestion-gaps.md) | Add receiver measurements without weakening dynamic-dispatch boundaries. | +| [PL/SQL](plsql-ingestion-gaps.md) | Measure call and table-access recall on real Oracle package corpora. | | [PHP](php-ingestion-gaps.md) | Retain PHPDoc container types and establish namespace owners. | | [Python](python-ingestion-gaps.md) | Audit constructor-field and module ownership coverage. | | [Ruby](ruby-ingestion-gaps.md) | Isolate YARD parameter bindings before adding recall. | diff --git a/eslint.config.mjs b/eslint.config.mjs index 959560892..0c0ecea8b 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -55,6 +55,8 @@ export default [ '**/.stryker-tmp/**', 'gitnexus/reports/mutation/**', 'gitnexus/vendor/**', + // ANTLR4-generated PL/SQL lexer/parser (regenerate, never hand-edit). + 'gitnexus/src/core/ingestion/plsql/generated/**', 'gitnexus-web/src/vendor/**', 'gitnexus/test/fixtures/**', 'gitnexus-web/test/fixtures/**', diff --git a/gitnexus-shared/src/language-detection.ts b/gitnexus-shared/src/language-detection.ts index 583a0fd5f..88c9a413a 100644 --- a/gitnexus-shared/src/language-detection.ts +++ b/gitnexus-shared/src/language-detection.ts @@ -55,6 +55,19 @@ const EXTENSION_MAP: Record = { [SupportedLanguages.Vue]: ['.vue'], [SupportedLanguages.Cobol]: ['.cbl', '.cob', '.cpy', '.cobol'], [SupportedLanguages.Zig]: ['.zig'], + // `.sql` is claimed by content (provider.classifyFileContent), not by extension. + [SupportedLanguages.PlSql]: [ + '.pks', + '.pkb', + '.pck', + '.pls', + '.plb', + '.prc', + '.fnc', + '.trg', + '.tps', + '.tpb', + ], } satisfies Record; // Ensure exhaustiveness /** Pre-built reverse lookup: extension → language (built once at module load). */ @@ -129,6 +142,7 @@ const SYNTAX_MAP: Record = { [SupportedLanguages.Vue]: 'typescript', [SupportedLanguages.Cobol]: 'cobol', [SupportedLanguages.Zig]: 'zig', + [SupportedLanguages.PlSql]: 'plsql', } satisfies Record; // Ensure exhaustiveness /** Non-code file extensions → Prism-compatible syntax identifiers */ diff --git a/gitnexus-shared/src/languages.ts b/gitnexus-shared/src/languages.ts index eec45e612..925859ff9 100644 --- a/gitnexus-shared/src/languages.ts +++ b/gitnexus-shared/src/languages.ts @@ -24,4 +24,6 @@ export enum SupportedLanguages { /** Standalone regex processor — no tree-sitter, no LanguageProvider. */ Cobol = 'cobol', Zig = 'zig', + /** Oracle PL/SQL — standalone ANTLR4 provider (no tree-sitter). */ + PlSql = 'plsql', } diff --git a/gitnexus-shared/src/scope-resolution/language-classification.ts b/gitnexus-shared/src/scope-resolution/language-classification.ts index 2e635c8e2..df5d9a927 100644 --- a/gitnexus-shared/src/scope-resolution/language-classification.ts +++ b/gitnexus-shared/src/scope-resolution/language-classification.ts @@ -16,6 +16,7 @@ * * Added after Ring 1: zig enters as `experimental` (new language * integration; promotion to `production` is a separate governance PR). + * plsql enters as `experimental` (standalone ANTLR4 provider path). */ import { SupportedLanguages } from '../languages.js'; @@ -47,6 +48,7 @@ export const LanguageClassifications: Readonly; /** Get provider by language enum (always succeeds for SupportedLanguages). */ diff --git a/gitnexus/src/core/ingestion/languages/plsql.ts b/gitnexus/src/core/ingestion/languages/plsql.ts new file mode 100644 index 000000000..d7adc9aa6 --- /dev/null +++ b/gitnexus/src/core/ingestion/languages/plsql.ts @@ -0,0 +1,49 @@ +/** + * Oracle PL/SQL Language Provider + * + * Standalone ANTLR4 provider — no tree-sitter grammar. PL/SQL files are + * parsed with the grammars-v4 PlSql grammar (`../plsql/`) and mapped to the + * graph by `plsql-processor.ts` in the `plsql` pipeline phase, before parse. + * + * Dedicated extensions (.pks, .pkb, .pck, …) are always PL/SQL. `.sql` is + * claimed through `classifyFileContent` only when the file holds an Oracle + * PL/SQL unit, leaving other SQL dialects alone. + */ +import { SupportedLanguages } from 'gitnexus-shared'; +import { defineLanguage } from '../language-provider.js'; +import { PLSQL_EXTENSIONS, isSqlFile, looksLikeOraclePlSql } from '../plsql/file-types.js'; +import { + collectPlSqlSourceCfgs, + emitPlSqlScopeCaptures, + interpretPlSqlImport, + plsqlImportOwningScope, + plsqlReceiverBinding, + plsqlRenameProvider, +} from './plsql/index.js'; + +export const plsqlProvider = defineLanguage({ + id: SupportedLanguages.PlSql, + parseStrategy: 'standalone', + extensions: [...PLSQL_EXTENSIONS], + // Check the extension first: classifiers run before extension lookup for every file. + shouldClassifyFileContent: isSqlFile, + classifyFileContent: (filePath, sourceText) => + isSqlFile(filePath) && looksLikeOraclePlSql(sourceText), + entryPointPatterns: [], + astFrameworkPatterns: [], + treeSitterQueries: '', + typeConfig: { + declarationNodeTypes: new Set(), + extractDeclaration: () => null, + extractParameter: () => null, + }, + exportChecker: () => false, + rename: plsqlRenameProvider, + collectSourceCfgs: collectPlSqlSourceCfgs, + + // ── Scope-resolution hooks ─────────────────────────────────────── + emitScopeCaptures: emitPlSqlScopeCaptures, + interpretImport: interpretPlSqlImport, + importOwningScope: plsqlImportOwningScope, + receiverBinding: plsqlReceiverBinding, +}); diff --git a/gitnexus/src/core/ingestion/languages/plsql/captures.ts b/gitnexus/src/core/ingestion/languages/plsql/captures.ts new file mode 100644 index 000000000..def840910 --- /dev/null +++ b/gitnexus/src/core/ingestion/languages/plsql/captures.ts @@ -0,0 +1,153 @@ +/** + * `emitScopeCaptures` for Oracle PL/SQL. + * + * Wraps the ANTLR fact extractor (`../../plsql/extract.ts`) and emits the + * parser-agnostic RFC §5.1 vocabulary consumed by the central ScopeExtractor: + * + * package → @scope.module + @declaration.program (→ Module) + * procedure/function → @scope.function + @declaration.function (→ Function) + * trigger → @scope.function + @declaration.function + * spec declaration → @declaration.function (no scope: it has no body) + * call (last part) → @reference.call (+ @reference.arity) + * call qualifier part → @reference.read (the package name in `pkg.proc`) + * END