Skip to content

feat(shader): add neutral IR and standalone diagnostics - #3054

Open
zhuxudong wants to merge 156 commits into
galacean:dev/2.0from
zhuxudong:refactor/shader-analyzer
Open

feat(shader): add neutral IR and standalone diagnostics#3054
zhuxudong wants to merge 156 commits into
galacean:dev/2.0from
zhuxudong:refactor/shader-analyzer

Conversation

@zhuxudong

@zhuxudong zhuxudong commented Jun 26, 2026

Copy link
Copy Markdown
Member
  • 设计依据:RFC RFC: Shader Static Analyzer #3017。核心参考 Naga 的 Frontend → IR → Validator/Info → Backend 分层,让解析、生成和诊断独立消费中立事实;本 PR 只建立未来后端边界,不实现 WGSL。

  • shader-parser 负责 ShaderLab/source-pass 解析并拥有 ShaderClueIRShaderCoreInfo 等中立结构;shader-compiler 只消费这些事实并通过 ShaderBackend 生成 GLES;shader-analyzer 作为同级消费者产出结构化 diagnostics,并提供浏览器 API 与 headless CLI。

  • parser 仅暴露 @galacean/engine-shader-parser/internal 运行时入口和 /internal/analyzer 分析入口,根包拒绝解析;已删除 _VERBOSE、jscc 双构建和 verbose 产物。冷构建通过显式 workspace runtime source 解析完成,不向 npm 包发布源码条件。

  • source parser 统一拥有 source error、entry binding、原始 range/source mapping 与 include canonical path;compiler 消费 typed parse envelope,结构性错误会在 precompile 序列化前失败,不再发布被静默丢弃 RenderState 的部分产物。

  • VariableDeclaratorInfo 是 const、initializer 与 array shape 的唯一事实;ShaderValidator 统一产生 NonConstInitializer 等诊断,局部、全局、数组和逗号 declarator 使用同一规则。

  • 宏分支分析采用三态契约:可证明安全时不报,确定错误时报 error 并保留 witness,无法证明时只报 warning;analyzer 不枚举全部宏组合,也不改变或阻断 runtime codegen。复杂或未知关系不被伪装成确定错误。

  • include 支持 canonical URL、嵌套相对路径、循环检测、双向 root 顺序回归与精确文件定位。默认运行时产物不包含 analyzer proof solver、authoring diagnostic 文案、分析 Lexer 或报告方法。

  • 基线固定为 dev/2.0@bd34daa45612af8b402cd3be916ff181f21ae742。本地完整 Chromium Vitest 为 141/141 个测试文件、445/445 个 suite、2149/2149 个用例;14 个包类型构建通过,22/22 个内置 Shader 在 bootstrap 与最终模块构建中均成功预编译。本次 head 的 GitHub CI 10/10 通过,覆盖三平台 build、四组 e2e、lint 与 codecov。

  • 当前 shipping Shader source 相对上一 PR head 无新增改动。对 dev/2.0 执行 22 个内置 Shader × 25 组宏配置、1300 个 stage 对比:49 个差异全部来自已知的非法 Fog/SSAO/Particle 重叠宏 fallback,unexpected mismatch 为 0;所有受支持配置与基线一致。真实 WebGL 预编译 A/B 为 57/57。

  • 相同 consumer entry 下,当前运行时 bundle 为 345601 B / 63149 B gzip;dev/2.0 基线为 383670 B / 65870 B gzip,分别减少 9.92% / 4.13%。产物与 sourcemap 门禁确认 runtime 未包含 BranchAnalysisAnalyzerLexerAnalyzerSemanticDiagnosticsPassParser 或 analyzer-only diagnostic 文案。

  • 同机交替 A/B 基准每轮包含 200 次 warmup、15 个交替 batch、每 batch 200 次。完整 pipeline 三轮结果为 +1.266% / +0.0368 ms、+2.527% / +0.0727 ms、-0.502% / -0.0146 ms;没有任何一轮同时超过 +3% 和 +0.05 ms 的回归门槛。

  • parser、compiler、analyzer 的 npm dry-run、resolver 与 export-target 门禁通过;parser 包为 80 个条目且不发布 src。Standalone CLI 已验证 clean=0、warning=0、error=1;8 份 parser/compiler/analyzer sourcemap 均包含完整 sourcesContent 且不泄漏绝对路径。

  • 已知边界:branch signature 不是 proof-complete 的 ESSL 宏求解器;不能证明的复杂宏关系保持 warning,最终 GLES driver 仍是运行时可接受性的事实来源。

zhuxudong added 30 commits June 2, 2026 11:07
- move createPosition/createRange + their pools to ShaderCompilerUtils
- move pass-text error context to ShaderCompilerUtils.processingPassText
- add ICodeGenVisitor interface so AST no longer imports concrete CodeGenVisitor
- parser/lexer/codegen now depend on ShaderCompilerUtils, not ShaderCompiler entry

prep for extracting shared shader-parser package (c3); no behavior change, 197 tests green
- copy ClearableObjectPool/IPoolElement into local common/ObjectPool
- add local no-op Logger (engine-core Logger is also disabled by default)
- copy render-state enums into common/enums/RenderStateEnums (values mirror engine-core)
- parser/lexer/lalr/sourceParser now engine-core-free; engine-math (Color) kept as foundation dep

deviates from RFC: Color kept as engine-math dep, render-state enums copied; 197 tests green
…mpiler

- move lexer/preprocessor/parser/lalr/AST/sourceParser + utils into @galacean/engine-shader-parser
- shader-compiler depends on it; cross-package imports go through the package barrel
- shader-parser ships one always-full build (jscc _VERBOSE=true), external to shader-compiler
- shader-parser drops stripInternal so compiler/analyzer can use internal parser APIs

pure relocation; 197 shader-compiler tests green
…gnostics

- new @galacean/engine-shader-analyzer drives the parse + collects diagnostics, skips codegen
- restores diagnostics the runtime compiler discards (parity verified vs verbose compiler)
- harvest approach: checks stay in shader-parser (single source), no visitor duplication
- Phase 1 returns GSError verbatim; structured API + new checks are Phase 2

harvest deviates from RFC's DiagnosticVisitor plan; 199 tests green
- analyzer now runs codegen too, capturing codegen-level diagnostics (struct/MRT/gl_FragData)
- ungate codegen error collection so the single release build always collects them
- remove ShaderCompiler._logErrors + calls: the compiler compiles, never reports
- delete the verbose build variant (/verbose export, rollup push, stub dir)
- shader-compiler drops stripInternal + exports GLES visitors so analyzer can drive codegen

completes Phase 1: diagnostics live in the analyzer; 200 tests green
- drop unused ObjectPool.garbageCollection (pools reuse via clear(), never GC)
- remove dead verboseMode branches from root rollup (no verbose build remains)
- collapse duplicate glslValidate calls left by the verbose→release test switch
- drop an obsolete warning-spy guard (the warning no longer exists; macro asserts cover it)
- tighten comments: drop task-context and a claim of a non-existent sync test
- remove unused abstract ObjectPool base class (only ClearableObjectPool
  extends it; inline the two fields)
- replace indirect ReturnType<typeof ShaderSourceParser.parse> with IShaderSource
- Diagnostic interface (severity, code, range, message, source, relatedSource)
- DiagnosticCode registry: C0 (parser/codegen), A1 (ShaderLab), B1/B2 (RenderState)
- gseErrorToDiagnostic converts GSError to structured Diagnostic
- ShaderAnalyzer.analyze() returns AnalysisResult.diagnostics: Diagnostic[]
- heuristic code mapping from GSErrorName + message content
- tests verify structured output for all 3 diagnostic sources
- reportWarning routed to Logger.warn, a noop since Phase 1 decoupled Logger
- the "declared before used" warning was silently dropped as a result
- now push CompilationWarn to errors[] (gated by _VERBOSE, like reportError)
- analyzer surfaces it as a C0-07 warning diagnostic; drop unused Logger import
- a failed function lookup is signature-keyed, conflating unknown names and wrong-arg calls
- both surfaced as one opaque "No overload function type found" message
- re-probe by name alone (+ builtin registry) to split the two cases
- unknown names now report a distinct "Undefined function" (C0-09); wrong-args keeps C0-06
- shader-parser always builds _VERBOSE=true, so its 88 #if _VERBOSE blocks were dead scaffolding
- the guarded code (diagnostics, line/column tracking) already shipped in every build
- strip all markers + drop the 2 dead #else console.error fallbacks
- dist and behavior identical to before; 202 shader tests stay green
- rollup _VERBOSE jscc is now a no-op (zero #if _VERBOSE left repo-wide) — remove it + the import
- VisitorContext location: any -> BaseToken["location"]; IRenderState drops the pointless | any
- BaseLexer throwError msgs: any[] -> unknown[] (only ever join()'d)
- map the "referenced X not found" codegen error to a dedicated C0-22 instead of the C0-08 fallback
- SymbolTable.insert silently overwrote a same-scope duplicate via a now-noop Logger.warn
- insert() now returns whether it replaced an equal symbol; decl sites surface it as a C0-10 warning
- macro-branch siblings stay exempt (insert skips isInMacroBranch entries) — covered by a test
- applies to local (SingleDeclaration/InitDeclaratorList) and global (VariableDeclaration) vars
- add PostfixExpression.semanticAnalyze: a `.field` on a known vector is validated as a swizzle
- catches out-of-range components (.z on vec2), mixed sets (.xr), bad chars, length > 4
- only fires when the base type is a concrete vecN — struct members and unresolved bases skip
- ParserUtils.swizzleError holds the rule; first C1 (GLSL type) layer check
- AssignmentExpression flags `a = b` when b's type cannot convert to a's
- ParserUtils.isAssignable models GLSL ES3 implicit conversions (int->float, ivecN->vecN)
- so valid coercions (float = int) are NOT flagged; only definite conflicts surface
- fires only when both operand types are concrete; compound RHS / structs are skipped
- JumpStatement.semanticAnalyze checks `return expr` against the function's declared return type
- reuses ParserUtils.isAssignable, so implicit conversions (return int from a float fn) pass
- skips void returns (C0-04 covers those) and unresolved/compound expressions
- ShaderTargetParser singleton: a failed parse (syntax error) left _traceBackStack dirty
- the next parse was then corrupted: a valid shader got a spurious diagnostic
- this hits the runtime compiler too (ShaderCompiler/ShaderAnalyzer share the singleton)
- clear _traceBackStack each parse; reset SymbolTableStack._macroLevel in clear() too
- regression test: a broken analyze() must not corrupt the following valid one
- registerRule(rule) runs user rules after the built-in checks on every analyze()
- rules get source + parsed structure + positionAt(); report() namespaces the code as <name>/<code>
- a throwing rule surfaces a <name>/rule-error warning instead of crashing analysis
- analyze() restructured so rules run even when structure parsing fails (built-in path unchanged)
- src/shader-playground.ts: a live editor + diagnostics panel driven by ShaderAnalyzer.analyze()
- no engine init (analyzer is standalone); the sample shows C0-09/10 and C1-01/02/03
- also demos registerRule via a "demo/no-discard" custom rule
- wire engine-shader-analyzer into examples deps + vite optimizeDeps exclude
- upgrade shader-parser's local Logger to a real controllable one (enable/disable, off by default)
- keeps zero engine-core dependency (local copy mirroring engine-core's API)
- route runtime console.* through it: error prints -> Logger.error, version banner -> Logger.info
- the compiler version banner no longer prints on every import (silent unless logging enabled)
- remove dead debug dumpers printStatePool / _printStack (uncalled) and their console
- bundler CLI keeps console (build-time terminal output); shader-analyzer had no bare console
- local common/Logger.ts existed to avoid an engine-core dep, but core never imports shader pkgs
- core injects shader-compiler (no import), so there was never a cycle to avoid
- depend on engine-core and use its Logger; redirect 4 parser + 1 compiler imports, drop the copy
- logging now unifies with the engine's Logger; 1428 tests pass, compiledShaders byte-identical
- the diagnostic package now logs each diagnostic via the engine Logger, off by default
- severity-mapped: error->error, warning->warn, info->info, hint->debug
- add @galacean/engine-core dep; analyze() logs after collecting all diagnostics
- enable Logger to see every syntax/semantic problem in the console while analyzing
…cal copy

- common/ObjectPool.ts was a local copy of core's pool (made to avoid the now-cycle-free core dep)
- core's ClearableObjectPool/IPoolElement are behavior-identical (same get/clear logic)
- redirect the 6 import sites to @galacean/engine-core; drop the local copy + its re-export
- 213 shader tests pass, compiledShaders byte-identical to dev/2.0
…al copy

- common/enums/RenderStateEnums.ts was a hand-synced local copy of core's 8 render-state enums
- drop it; ShaderSourceParser imports them from @galacean/engine-core (merged into its core import)
- no re-export consumer; design types render state by number, so no enum-identity issue at boundary
- compiledShaders byte-identical (render-state serialization unchanged); 213 tests pass
- rollup.config.js: drop the dead jscc plugin (no #if _VERBOSE left) + its stale verbose comments
- also drop a dangling src/enums/README.md reference in that file's header
- convert.ts: drop the "Phase 2 ... DiagnosticVisitor" promise (no DiagnosticVisitor was built)
- Preprocessor/Lexer: fix comments referencing the removed verbose build / wrong package
- drop ShaderInstructionEncoder's hand-synced local copy of the directive enum

- core now exports ShaderPreprocessorDirective publicly (values unchanged Text=0..Undef=10)

- compiledShaders stay byte-identical to dev/2.0; tsc clean across the 3 shader packages
…e registry

- gSErrorNameToCode returns DiagnosticCode.* refs, dropping 34 duplicated raw "C0-xx" literals

- return type is DiagnosticCodeValue so tsc rejects any code absent from the registry

- remove the never-passed defaultSeverity param (all five callers use the default)
- add SemanticWalker that walks the built AST and derives diagnostics from node type clues

- PostfixExpression still produces the type clue; swizzle check (C1-01) leaves its semanticAnalyze

- establishes parser-produces-clues / analyzer-judges pattern; first step of diagnostics decoupling

- compiledShaders byte-identical; full suite 1429 pass
- IntegerConstantExpressionOperator.compute is now optional; absence = unknown-operator clue

- C0-02 judgment leaves parser semanticAnalyze for the walker

- compiledShaders byte-identical; full suite 1429 pass
- remove SemanticWalker; swizzle (C1-01) and operator (C0-02) judgments go back to parser

- analyzer-side instanceof was a hack; judgment belongs internalized in parser clue computation

- correct model: error-as-clue in parser + analyzer generic collection

- compiledShaders byte-identical; 1429 tests pass
@augmentcode

augmentcode Bot commented Jul 23, 2026

Copy link
Copy Markdown

This pull request is abnormally large and would use a significant amount of tokens to review. If you still wish to review it, comment "augment review" and we will review it.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors the shader toolchain into three decoupled packages—@galacean/engine-shader-parser (typed AST + parse-time semantic clues), @galacean/engine-shader-compiler (pure codegen), and @galacean/engine-shader-analyzer (structured diagnostics)—and wires optional analyzer injection into the engine runtime compile path, with extensive new tests to lock invariants and conformance.

Changes:

  • Split the shader pipeline into parser/compiler/analyzer layers and introduce structured DiagnosticType-based diagnostics that are available in release builds.
  • Remove _VERBOSE/jscc build split for shader compiler outputs and unify preprocessor-condition parsing via a shared parser implementation.
  • Add broad Vitest coverage for analyzer behavior (macro-branch semantics, IO rules, AST reuse, injection gating) and several compiler/codegen invariants.

Reviewed changes

Copilot reviewed 100 out of 123 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/vitest.config.ts Adjust Playwright browser config (headless setting + launch args).
tests/src/shader-compiler/StateIsolation.test.ts Adds regression tests for cross-shader state isolation.
tests/src/shader-compiler/shaders/struct-based-attribute.shader Adds repro shader for stage-scoped struct-variable disambiguation.
tests/src/shader-compiler/ReturnStatementInvariant.test.ts Locks codegen invariant around return; in void frag().
tests/src/shader-compiler/PreprocessorConditionConformance.test.ts End-to-end conformance test across parser/analyzer/encoder/WebGL.
tests/src/shader-compiler/Precompile.test.ts Extends encoder coverage for numeric macro conditions and malformed exprs.
tests/src/shader-compiler/AnalyzerInjection.test.ts Verifies analyzer injection gates codegen and logs diagnostics.
tests/src/shader-analyzer/ShaderPlayground.test.ts UI-smoke test for shader playground diagnostics rendering.
tests/src/shader-analyzer/ShaderIOAnalyzer.test.ts Expectation-driven IO diagnostic coverage (parser IO analysis).
tests/src/shader-analyzer/ReuseAst.test.ts Proves analyze() returns reusable AST and codegen matches fresh parse.
tests/src/shader-analyzer/BuiltinShaderSmoke.test.ts Smoke-tests built-in shader corpus against analyzer regressions.
tests/src/shader-analyzer/BranchResolutionAmbiguity.test.ts Covers ambiguity handling for macro branch resolution.
tests/src/shader-analyzer/BranchAwareLookup.test.ts Covers branch-aware symbol visibility semantics.
tests/package.json Adds workspace deps for new shader-parser/analyzer packages.
rollup.config.js Removes jscc verbose-mode variant outputs at repo root bundling.
pnpm-lock.yaml Adds lock entries for new packages and updated workspace deps.
packages/shader-parser/tsconfig.json Introduces shader-parser package TS build config.
packages/shader-parser/src/sourceParser/SourceLexer.ts Moves range creation + supports diagnostic code on compile errors.
packages/shader-parser/src/sourceParser/ShaderSourceSymbol.ts Adds branchSignature tracking to source symbols.
packages/shader-parser/src/sourceParser/ShaderSourceParser.y Adds bison grammar file for conflict testing.
packages/shader-parser/src/sourceParser/ShaderSourceParser.ts Adds structured diagnostics, entry binding diagnostics, and entry locations.
packages/shader-parser/src/sourceParser/ShaderSourceFactory.ts Centralizes source AST factory creation for ShaderLab structures.
packages/shader-parser/src/sourceParser/index.ts Exposes source parser entry point.
packages/shader-parser/src/ShaderCompilerUtils.ts Centralizes pools + error creation; introduces processingPassText.
packages/shader-parser/src/Preprocessor.ts Uses engine Logger for include-miss reporting.
packages/shader-parser/src/ParserUtils.ts Adds shared parser utilities (macro params, const-expr helpers, swizzle checks).
packages/shader-parser/src/parser/TypeSystem.ts Adds shared type utilities for analyzer/validator rules.
packages/shader-parser/src/parser/types.ts Adds isFlat on struct props for varying rules.
packages/shader-parser/src/parser/symbolTable/VarSymbol.ts Adds isConst and isUniform to support const-expr and assignment validation.
packages/shader-parser/src/parser/symbolTable/SymbolInfo.ts Adds branch-signature stamping for branch-aware lookup.
packages/shader-parser/src/parser/symbolTable/SymbolDataType.ts Introduces symbol datatype wrapper (incl array specifier).
packages/shader-parser/src/parser/symbolTable/StructSymbol.ts Adds struct symbol type.
packages/shader-parser/src/parser/symbolTable/index.ts Exposes parser symbol-table surface.
packages/shader-parser/src/parser/symbolTable/FnSymbol.ts Adds function symbol type.
packages/shader-parser/src/parser/ShaderTargetParser.ts Ensures singleton parser resets traceback stack; uses shared pass text.
packages/shader-parser/src/parser/ShaderInfo.ts Extends shader data with built-in reference locations for IO/validator checks.
packages/shader-parser/src/parser/SemanticAnalyzer.ts Reworks error reporting into structured errors + branch reachability + ambiguity dedupe.
packages/shader-parser/src/parser/PassParser.ts Adds reusable pass parse helper returning AST + parse errors + pass text.
packages/shader-parser/src/parser/index.ts Exposes parser entry points.
packages/shader-parser/src/parser/ICodeGenVisitor.ts Adds interface to decouple AST from concrete codegen visitor.
packages/shader-parser/src/parser/GrammarSymbol.ts Adds shared grammar symbol definitions.
packages/shader-parser/src/parser/Grammar.ts Adds shared Grammar container.
packages/shader-parser/src/parser/builtin/variables.ts Adds built-in variable table.
packages/shader-parser/src/parser/builtin/index.ts Exposes built-in tables.
packages/shader-parser/src/parser/builtin/functions.ts Improves overload resolution conservatism for TypeAny arguments.
packages/shader-parser/src/lexer/index.ts Exposes lexer entry point.
packages/shader-parser/src/lalr/Utils.ts Uses shared range creation and keeps debug helpers.
packages/shader-parser/src/lalr/types.ts Adds shared LALR table types.
packages/shader-parser/src/lalr/StateItem.ts Removes verbose gating; keeps safety check.
packages/shader-parser/src/lalr/State.ts Minor cleanup/comments.
packages/shader-parser/src/lalr/Production.ts Adds production container + pool.
packages/shader-parser/src/lalr/LALR1.ts Keeps conflict warnings without jscc gating.
packages/shader-parser/src/lalr/index.ts Exposes LALR exports.
packages/shader-parser/src/lalr/CFG.ts Removes verbose gating from AST pool bindings.
packages/shader-parser/src/index.ts Exposes shader-parser public surface (common/lexer/parser/diagnostics).
packages/shader-parser/src/GSError.ts Moves GSError into shader-parser with formatted source output.
packages/shader-parser/src/formatDiagnostic.ts Adds shared diagnostic formatting helper.
packages/shader-parser/src/DiagnosticType.ts Defines structured diagnostic codes.
packages/shader-parser/src/common/types.ts Adds shared token/type definitions (incl TypeAny).
packages/shader-parser/src/common/SymbolTableStack.ts Adds branch signature state + branch-aware lookup helpers.
packages/shader-parser/src/common/SymbolTable.ts Implements branch-aware symbol visibility and coexistence logic.
packages/shader-parser/src/common/ShaderRange.ts Adds pooled range structure.
packages/shader-parser/src/common/ShaderPosition.ts Adds pooled position structure with line/column always available.
packages/shader-parser/src/common/PreprocessorCondition.ts Adds shared #if condition parser (used by lexer + encoder).
packages/shader-parser/src/common/index.ts Exposes common exports.
packages/shader-parser/src/common/IBaseSymbol.ts Adds branchSignature contract on symbols.
packages/shader-parser/src/common/enums/ShaderStage.ts Adds shader stage enum.
packages/shader-parser/src/common/enums/Keyword.ts Introduces unified keyword/token enum definitions.
packages/shader-parser/src/common/BaseLexer.ts Always tracks line/column; routes scanner errors through Logger.
packages/shader-parser/package.json Adds new publishable shader-parser package manifest.
packages/shader-compiler/verbose/package.json Removes verbose variant packaging for shader-compiler.
packages/shader-compiler/tsconfig.json Drops stripInternal (verbose split removed).
packages/shader-compiler/src/ShaderInstructionEncoder.ts Uses shared parsePreprocessorCondition + engine directive enum.
packages/shader-compiler/src/ShaderCompiler.ts Consumes shader-parser, adds analyzer injection hook, factors codegen into generate().
packages/shader-compiler/src/ParserUtils.ts Removes duplicated utils (moved to shader-parser).
packages/shader-compiler/src/parser/ShaderInfo.ts Removes duplicated ShaderInfo (moved to shader-parser).
packages/shader-compiler/src/index.ts Routes logging through Logger; re-exports GSError from shader-parser.
packages/shader-compiler/src/GSError.ts Removes duplicated GSError (moved to shader-parser).
packages/shader-compiler/src/common/SymbolTableStack.ts Removes duplicated symbol stack (moved to shader-parser).
packages/shader-compiler/src/common/SymbolTable.ts Removes duplicated symbol table (moved to shader-parser).
packages/shader-compiler/src/common/BaseToken.ts Removes duplicated token implementation (moved to shader-parser).
packages/shader-compiler/src/codeGen/VisitorContext.ts Switches to shader-parser types; splits struct-var maps per stage.
packages/shader-compiler/src/codeGen/types.ts Tightens render state record typing.
packages/shader-compiler/src/codeGen/GLES300.ts Updates fragment built-in rewriting behavior under MRT.
packages/shader-compiler/src/codeGen/GLES100.ts Avoids re-reporting parser-validated MRT prop misses.
packages/shader-compiler/src/codeGen/CodeGenVisitor.ts Uses stage-aware struct-var lookup and removes codegen-side diagnostics reporting.
packages/shader-compiler/rollup.config.js Removes jscc stripping; keeps self-contained build behavior.
packages/shader-compiler/package.json Adds dependency on shader-parser; removes ./verbose export and packaging.
packages/shader-analyzer/tsconfig.json Introduces shader-analyzer package TS build config.
packages/shader-analyzer/src/ShaderAnalyzer.ts Adds analyze()/diagnose() flows producing structured diagnostics and reusable ASTs.
packages/shader-analyzer/src/index.ts Exposes analyzer and diagnostic types/formatting.
packages/shader-analyzer/src/DiagnosticCategory.ts Adds category mapping for diagnostic types.
packages/shader-analyzer/src/Diagnostic.ts Defines structured diagnostics and formatting wrapper.
packages/shader-analyzer/src/convert.ts Converts GSError to structured diagnostics with ranges and source context.
packages/shader-analyzer/package.json Adds new publishable shader-analyzer package manifest.
packages/design/src/shader-compiler/shaderSource/IShaderPassSource.ts Adds entry-name source ranges for better diagnostics pinpointing.
packages/design/src/shader-compiler/IShaderProgram.ts Adds opaque program interface shared by compiler/analyzer.
packages/design/src/shader-compiler/IShaderCompiler.ts Adds _setAnalyzer() hook to compiler interface.
packages/design/src/shader-compiler/IShaderAnalyzer.ts Adds analyzer interface for compiler injection.
packages/design/src/shader-compiler/index.ts Exposes new analyzer/program types.
packages/core/src/shader/index.ts Exposes ShaderPreprocessorDirective.
packages/core/src/Engine.ts Wires optional shaderAnalyzer into Engine initialization.
packages/core/src/animation/AnimatorController.ts Minor loop style change (letconst).
packages/core/src/animation/Animator.ts Minor variable destructuring change (letconst).
packages/core/src/animation/AnimationClip.ts Tightens Objectobject typing in overloads.
examples/vite.config.js Adds shader-analyzer to optimized deps include list.
examples/package.json Adds shader-analyzer dependency for examples workspace.
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file
Comments suppressed due to low confidence (3)

packages/shader-parser/src/Preprocessor.ts:64

  • Typo in error message: "not founded" → "not found".
    packages/shader-parser/src/ShaderCompilerUtils.ts:24
  • ShaderCompilerUtils.createPosition accepts optional line/column, but ShaderPosition.set requires numbers. Allowing undefined here risks producing diagnostics with invalid line/column (and makes the API contract inconsistent). Make line/column required (or supply defaults).
    packages/shader-parser/src/sourceParser/ShaderSourceParser.ts:524
  • On duplicate VertexShader/FragmentShader entry assignment, the code says the first binding is kept, but vertexEntryLocation/fragmentEntryLocation is overwritten before the duplicate check. This leaves the stored location pointing at the second (rejected) assignment, which can mis-highlight later diagnostics that rely on these locations.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/shader-compiler/src/codeGen/GLES300.ts
@zhuxudong zhuxudong changed the title refactor(shader): split parser/compiler/analyzer + structured diagnostics (RFC #3017) refactor(shader): split parser/compiler/analyzer + diagnostics + macro analysis Jul 23, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/shader-compiler/src/ShaderInstructionEncoder.ts (1)

66-84: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Handle malformed #if/#elif conditions in the encode path.

parsePreprocessorCondition throws when the condition has trailing/unconsumed input, and ShaderInstructionEncoder.parse does not catch it, so malformed conditionals can crash encoding. The PreprocessorCondition tree shape matches the design Condition runtime shape, so no type change is needed here. Consider moving this into a central validation/error result type, or catching malformed conditionals during encoding.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shader-compiler/src/ShaderInstructionEncoder.ts` around lines 66 -
84, Update ShaderInstructionEncoder.parse and its conditional-directive handling
to catch errors from parsePreprocessorCondition for malformed `#if` and `#elif`
expressions, returning the encoder’s established validation/error result instead
of throwing. Preserve the existing instruction and backfill behavior for valid
conditions, and keep the PreprocessorCondition type unchanged.
🧹 Nitpick comments (2)
packages/shader-compiler/src/codeGen/GLESVisitor.ts (1)

86-86: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Align the optional chaining for fnSymbols.

getSymbols(symbol, true, []) returns the out array by contract, so fnSymbols.length is safe, but for stylistic consistency with _fragmentMain use optional chaining here as well.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shader-compiler/src/codeGen/GLESVisitor.ts` at line 86, Update the
fnSymbols emptiness check in the surrounding visitor method to use optional
chaining on fnSymbols.length, matching the style used by _fragmentMain. Preserve
the existing _softMissEntry(false) behavior when no function symbols are
available.
packages/shader-parser/src/parser/TypeSystem.ts (1)

14-20: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Redundant branch in isAssignable. Lines 18 and 19 both evaluate to return target === source, so the typeof … === "string" guard has no distinct effect. The struct-vs-primitive/struct-vs-struct reasoning in the comment is already fully captured by the strict equality on line 19. You can collapse the tail to a single return target === source;.

♻️ Optional simplification
     if (target == undefined || source == undefined || target === TypeAny || source === TypeAny) return true;
     // Struct types compare by name (§4.1.8: types are equal only if they are the same struct).
     // Mixed struct-vs-primitive is a conflict; struct-vs-struct with different names is a conflict.
-    if (typeof target === "string" || typeof source === "string") return target === source;
     return target === source;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shader-parser/src/parser/TypeSystem.ts` around lines 14 - 20, Remove
the redundant typeof guard in TypeSystem.isAssignable and collapse the final
comparison logic to a single return target === source after the existing
undefined and TypeAny checks. Preserve the current assignability behavior and
surrounding comments as appropriate.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/animation/AnimationClip.ts`:
- Around line 58-66: Update the addEvent overload and implementation in
AnimationClip to preserve the existing animation-event payload contract for
deserialized primitive JSON values. Align the parameter type with
AnimationEvent.parameter by restoring Object, or consistently update
AnimationEvent and all consumers if primitives are intentionally disallowed.

In `@packages/core/src/Engine.ts`:
- Around line 647-648: Update the Engine initialization flow around
Shader._shaderCompiler and the existing shaderCompiler so shaderAnalyzer is
attached to the compiler used by Shader.create, including when that compiler was
initialized previously. Reuse the existing _setAnalyzer path or enforce an
explicit lifecycle error; do not silently discard the provided analyzer.

In `@packages/shader-analyzer/src/ShaderValidator.ts`:
- Around line 416-444: Update the constructor argument-count validation around
the existing matrix-constructor short-circuit to also accept a single vector
argument whose component count is at least the target vector size, allowing
truncation for cases such as vec3(vec4(...)). Preserve exact-count validation
for smaller vectors, multi-argument constructors, and other existing cases.

In `@packages/shader-parser/src/GSError.ts`:
- Around line 29-35: Update GSError.toString() to detect location objects
structurally before accessing location.start and location.end, treating plain
position objects with index, line, and column fields as a single-point range
like ShaderPosition instances. Preserve the existing range handling for
locations that already provide start and end positions.

In `@packages/shader-parser/src/parser/PassParser.ts`:
- Around line 17-24: Update parseShaderPass to accept a basePathForIncludeKey
parameter and pass it to Preprocessor.parse instead of the hard-coded empty
string. Locate the analyzer flow that invokes parseShaderPass and propagate the
same include base path used by compiler parsing, preserving relative include
resolution for analyzer-only passes.

In `@packages/shader-parser/src/Preprocessor.ts`:
- Around line 63-64: Update the missing-include handling in the Preprocessor
method containing Logger.error so it creates and propagates a structured
Diagnostic/preprocessing error to both callers instead of only logging and
returning an empty string; ensure downstream compiler/analyzer generation stops
whenever that error is present.

In `@packages/shader-parser/src/ShaderCompilerUtils.ts`:
- Around line 12-13: Make shader error source optional throughout the
error-reporting flow: update GSError.source, createGSError, and SemanticAnalyzer
reporting to accept undefined, and ensure GSError.toString handles missing
source safely. Preserve existing diagnostics when processingPassText is
available.
- Around line 8-10: The pooled ShaderRange and ShaderPosition instances must not
remain attached to published parse results. Update the AST publication/return
path around ShaderAnalyzer.analyze() and TreeNode.location to clone or
materialize independent location objects before exposing program, rather than
relying on clearAllShaderCompilerObjectPool() or pooled dispose behavior.

In `@packages/shader-parser/src/sourceParser/ShaderSourceParser.ts`:
- Around line 520-537: In the entry-assignment flow around the isVertex and
passSource[key] checks, only assign vertexEntryLocation or fragmentEntryLocation
after confirming the binding is not already present. Keep the original location
paired with the retained first entry, while duplicate assignments should still
emit DuplicateEntryAssignment without overwriting that location.
- Around line 400-408: Move the RenderQueueType variableMap assignment in the
surrounding parsing logic so it occurs only after the lookup successfully
resolves `sm`. On the invalid path that emits
`DiagnosticType.InvalidRenderQueueVariable` and returns, remove or avoid
retaining the unresolved token mapping, preserving inherited valid values.

In `@packages/shader-parser/src/sourceParser/ShaderSourceParser.y`:
- Line 22: Fix the grammar productions in the shader parser: rename the
VertextShader token to VertexShader and update its references, add | separators
between alternatives in main_shader_assignment and render_state_prop_assignment,
and make pass_statements recursive so a pass accepts multiple declarations or
assignments.

In `@tests/vitest.config.ts`:
- Line 27: Remove the duplicate --use-gl entry from the args configuration in
the Vitest setup, retaining one intentional GL backend value so Chromium uses an
unambiguous backend for WebGL tests.

---

Outside diff comments:
In `@packages/shader-compiler/src/ShaderInstructionEncoder.ts`:
- Around line 66-84: Update ShaderInstructionEncoder.parse and its
conditional-directive handling to catch errors from parsePreprocessorCondition
for malformed `#if` and `#elif` expressions, returning the encoder’s established
validation/error result instead of throwing. Preserve the existing instruction
and backfill behavior for valid conditions, and keep the PreprocessorCondition
type unchanged.

---

Nitpick comments:
In `@packages/shader-compiler/src/codeGen/GLESVisitor.ts`:
- Line 86: Update the fnSymbols emptiness check in the surrounding visitor
method to use optional chaining on fnSymbols.length, matching the style used by
_fragmentMain. Preserve the existing _softMissEntry(false) behavior when no
function symbols are available.

In `@packages/shader-parser/src/parser/TypeSystem.ts`:
- Around line 14-20: Remove the redundant typeof guard in
TypeSystem.isAssignable and collapse the final comparison logic to a single
return target === source after the existing undefined and TypeAny checks.
Preserve the current assignability behavior and surrounding comments as
appropriate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 51869212-62ba-44f4-83e4-bed53ac4b1e1

📥 Commits

Reviewing files that changed from the base of the PR and between 9fe48c6 and 4e33d49.

⛔ Files ignored due to path filters (2)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • tests/src/shader-compiler/shaders/struct-based-attribute.shader is excluded by !**/*.shader
📒 Files selected for processing (121)
  • examples/package.json
  • examples/src/shader-playground.ts
  • examples/vite.config.js
  • packages/core/src/Engine.ts
  • packages/core/src/animation/AnimationClip.ts
  • packages/core/src/animation/Animator.ts
  • packages/core/src/animation/AnimatorController.ts
  • packages/core/src/shader/index.ts
  • packages/design/src/shader-compiler/IShaderAnalyzer.ts
  • packages/design/src/shader-compiler/IShaderCompiler.ts
  • packages/design/src/shader-compiler/IShaderProgram.ts
  • packages/design/src/shader-compiler/index.ts
  • packages/design/src/shader-compiler/shaderSource/IShaderPassSource.ts
  • packages/shader-analyzer/package.json
  • packages/shader-analyzer/src/Diagnostic.ts
  • packages/shader-analyzer/src/DiagnosticCategory.ts
  • packages/shader-analyzer/src/ShaderAnalyzer.ts
  • packages/shader-analyzer/src/ShaderValidator.ts
  • packages/shader-analyzer/src/convert.ts
  • packages/shader-analyzer/src/index.ts
  • packages/shader-analyzer/tsconfig.json
  • packages/shader-compiler/package.json
  • packages/shader-compiler/rollup.config.js
  • packages/shader-compiler/src/GSError.ts
  • packages/shader-compiler/src/ParserUtils.ts
  • packages/shader-compiler/src/ShaderCompiler.ts
  • packages/shader-compiler/src/ShaderInstructionEncoder.ts
  • packages/shader-compiler/src/codeGen/CodeGenVisitor.ts
  • packages/shader-compiler/src/codeGen/GLES100.ts
  • packages/shader-compiler/src/codeGen/GLES300.ts
  • packages/shader-compiler/src/codeGen/GLESVisitor.ts
  • packages/shader-compiler/src/codeGen/VisitorContext.ts
  • packages/shader-compiler/src/codeGen/types.ts
  • packages/shader-compiler/src/common/BaseToken.ts
  • packages/shader-compiler/src/common/SymbolTable.ts
  • packages/shader-compiler/src/common/SymbolTableStack.ts
  • packages/shader-compiler/src/index.ts
  • packages/shader-compiler/src/parser/ShaderInfo.ts
  • packages/shader-compiler/tsconfig.json
  • packages/shader-compiler/verbose/package.json
  • packages/shader-parser/package.json
  • packages/shader-parser/src/DiagnosticType.ts
  • packages/shader-parser/src/GSError.ts
  • packages/shader-parser/src/ParserUtils.ts
  • packages/shader-parser/src/Preprocessor.ts
  • packages/shader-parser/src/ShaderCompilerUtils.ts
  • packages/shader-parser/src/common/BaseLexer.ts
  • packages/shader-parser/src/common/BaseToken.ts
  • packages/shader-parser/src/common/IBaseSymbol.ts
  • packages/shader-parser/src/common/PreprocessorCondition.ts
  • packages/shader-parser/src/common/ShaderPosition.ts
  • packages/shader-parser/src/common/ShaderRange.ts
  • packages/shader-parser/src/common/SymbolTable.ts
  • packages/shader-parser/src/common/SymbolTableStack.ts
  • packages/shader-parser/src/common/enums/Keyword.ts
  • packages/shader-parser/src/common/enums/ShaderStage.ts
  • packages/shader-parser/src/common/index.ts
  • packages/shader-parser/src/common/types.ts
  • packages/shader-parser/src/formatDiagnostic.ts
  • packages/shader-parser/src/index.ts
  • packages/shader-parser/src/lalr/CFG.ts
  • packages/shader-parser/src/lalr/LALR1.ts
  • packages/shader-parser/src/lalr/Production.ts
  • packages/shader-parser/src/lalr/State.ts
  • packages/shader-parser/src/lalr/StateItem.ts
  • packages/shader-parser/src/lalr/Utils.ts
  • packages/shader-parser/src/lalr/index.ts
  • packages/shader-parser/src/lalr/types.ts
  • packages/shader-parser/src/lexer/Lexer.ts
  • packages/shader-parser/src/lexer/index.ts
  • packages/shader-parser/src/parser/AST.ts
  • packages/shader-parser/src/parser/Grammar.ts
  • packages/shader-parser/src/parser/GrammarSymbol.ts
  • packages/shader-parser/src/parser/ICodeGenVisitor.ts
  • packages/shader-parser/src/parser/PassParser.ts
  • packages/shader-parser/src/parser/SemanticAnalyzer.ts
  • packages/shader-parser/src/parser/ShaderIOAnalyzer.ts
  • packages/shader-parser/src/parser/ShaderInfo.ts
  • packages/shader-parser/src/parser/ShaderTargetParser.ts
  • packages/shader-parser/src/parser/TargetParser.y
  • packages/shader-parser/src/parser/TypeSystem.ts
  • packages/shader-parser/src/parser/builtin/functions.ts
  • packages/shader-parser/src/parser/builtin/index.ts
  • packages/shader-parser/src/parser/builtin/variables.ts
  • packages/shader-parser/src/parser/index.ts
  • packages/shader-parser/src/parser/symbolTable/FnSymbol.ts
  • packages/shader-parser/src/parser/symbolTable/StructSymbol.ts
  • packages/shader-parser/src/parser/symbolTable/SymbolDataType.ts
  • packages/shader-parser/src/parser/symbolTable/SymbolInfo.ts
  • packages/shader-parser/src/parser/symbolTable/VarSymbol.ts
  • packages/shader-parser/src/parser/symbolTable/index.ts
  • packages/shader-parser/src/parser/types.ts
  • packages/shader-parser/src/sourceParser/ShaderSourceFactory.ts
  • packages/shader-parser/src/sourceParser/ShaderSourceParser.ts
  • packages/shader-parser/src/sourceParser/ShaderSourceParser.y
  • packages/shader-parser/src/sourceParser/ShaderSourceSymbol.ts
  • packages/shader-parser/src/sourceParser/SourceLexer.ts
  • packages/shader-parser/src/sourceParser/index.ts
  • packages/shader-parser/tsconfig.json
  • rollup.config.js
  • tests/package.json
  • tests/src/shader-analyzer/BranchAwareLookup.test.ts
  • tests/src/shader-analyzer/BranchDeclarationConflict.test.ts
  • tests/src/shader-analyzer/BranchResolutionAmbiguity.test.ts
  • tests/src/shader-analyzer/BuiltinShaderSmoke.test.ts
  • tests/src/shader-analyzer/DiagnosticCoverage.test.ts
  • tests/src/shader-analyzer/DiagnosticSmoke.test.ts
  • tests/src/shader-analyzer/MacroBranchMatrix.test.ts
  • tests/src/shader-analyzer/ReuseAst.test.ts
  • tests/src/shader-analyzer/ShaderAnalyzer.test.ts
  • tests/src/shader-analyzer/ShaderIOAnalyzer.test.ts
  • tests/src/shader-analyzer/ShaderPlayground.test.ts
  • tests/src/shader-compiler/AnalyzerInjection.test.ts
  • tests/src/shader-compiler/DiagnosticDriverConsistency.test.ts
  • tests/src/shader-compiler/MacroBranchRuntime.test.ts
  • tests/src/shader-compiler/Precompile.test.ts
  • tests/src/shader-compiler/PreprocessorConditionConformance.test.ts
  • tests/src/shader-compiler/ReturnStatementInvariant.test.ts
  • tests/src/shader-compiler/ShaderCompiler.test.ts
  • tests/src/shader-compiler/StateIsolation.test.ts
  • tests/vitest.config.ts
💤 Files with no reviewable changes (11)
  • packages/shader-compiler/verbose/package.json
  • packages/shader-parser/src/lalr/LALR1.ts
  • packages/shader-compiler/src/common/SymbolTableStack.ts
  • packages/shader-compiler/src/common/BaseToken.ts
  • packages/shader-compiler/src/parser/ShaderInfo.ts
  • packages/shader-compiler/src/common/SymbolTable.ts
  • packages/shader-compiler/src/GSError.ts
  • packages/shader-parser/src/lalr/State.ts
  • packages/shader-parser/src/lalr/StateItem.ts
  • packages/shader-compiler/src/ParserUtils.ts
  • packages/shader-parser/src/lalr/CFG.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/shader-compiler/src/ShaderInstructionEncoder.ts (1)

66-84: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Handle malformed #if/#elif conditions in the encode path.

parsePreprocessorCondition throws when the condition has trailing/unconsumed input, and ShaderInstructionEncoder.parse does not catch it, so malformed conditionals can crash encoding. The PreprocessorCondition tree shape matches the design Condition runtime shape, so no type change is needed here. Consider moving this into a central validation/error result type, or catching malformed conditionals during encoding.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shader-compiler/src/ShaderInstructionEncoder.ts` around lines 66 -
84, Update ShaderInstructionEncoder.parse and its conditional-directive handling
to catch errors from parsePreprocessorCondition for malformed `#if` and `#elif`
expressions, returning the encoder’s established validation/error result instead
of throwing. Preserve the existing instruction and backfill behavior for valid
conditions, and keep the PreprocessorCondition type unchanged.
🧹 Nitpick comments (2)
packages/shader-compiler/src/codeGen/GLESVisitor.ts (1)

86-86: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Align the optional chaining for fnSymbols.

getSymbols(symbol, true, []) returns the out array by contract, so fnSymbols.length is safe, but for stylistic consistency with _fragmentMain use optional chaining here as well.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shader-compiler/src/codeGen/GLESVisitor.ts` at line 86, Update the
fnSymbols emptiness check in the surrounding visitor method to use optional
chaining on fnSymbols.length, matching the style used by _fragmentMain. Preserve
the existing _softMissEntry(false) behavior when no function symbols are
available.
packages/shader-parser/src/parser/TypeSystem.ts (1)

14-20: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Redundant branch in isAssignable. Lines 18 and 19 both evaluate to return target === source, so the typeof … === "string" guard has no distinct effect. The struct-vs-primitive/struct-vs-struct reasoning in the comment is already fully captured by the strict equality on line 19. You can collapse the tail to a single return target === source;.

♻️ Optional simplification
     if (target == undefined || source == undefined || target === TypeAny || source === TypeAny) return true;
     // Struct types compare by name (§4.1.8: types are equal only if they are the same struct).
     // Mixed struct-vs-primitive is a conflict; struct-vs-struct with different names is a conflict.
-    if (typeof target === "string" || typeof source === "string") return target === source;
     return target === source;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shader-parser/src/parser/TypeSystem.ts` around lines 14 - 20, Remove
the redundant typeof guard in TypeSystem.isAssignable and collapse the final
comparison logic to a single return target === source after the existing
undefined and TypeAny checks. Preserve the current assignability behavior and
surrounding comments as appropriate.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/animation/AnimationClip.ts`:
- Around line 58-66: Update the addEvent overload and implementation in
AnimationClip to preserve the existing animation-event payload contract for
deserialized primitive JSON values. Align the parameter type with
AnimationEvent.parameter by restoring Object, or consistently update
AnimationEvent and all consumers if primitives are intentionally disallowed.

In `@packages/core/src/Engine.ts`:
- Around line 647-648: Update the Engine initialization flow around
Shader._shaderCompiler and the existing shaderCompiler so shaderAnalyzer is
attached to the compiler used by Shader.create, including when that compiler was
initialized previously. Reuse the existing _setAnalyzer path or enforce an
explicit lifecycle error; do not silently discard the provided analyzer.

In `@packages/shader-analyzer/src/ShaderValidator.ts`:
- Around line 416-444: Update the constructor argument-count validation around
the existing matrix-constructor short-circuit to also accept a single vector
argument whose component count is at least the target vector size, allowing
truncation for cases such as vec3(vec4(...)). Preserve exact-count validation
for smaller vectors, multi-argument constructors, and other existing cases.

In `@packages/shader-parser/src/GSError.ts`:
- Around line 29-35: Update GSError.toString() to detect location objects
structurally before accessing location.start and location.end, treating plain
position objects with index, line, and column fields as a single-point range
like ShaderPosition instances. Preserve the existing range handling for
locations that already provide start and end positions.

In `@packages/shader-parser/src/parser/PassParser.ts`:
- Around line 17-24: Update parseShaderPass to accept a basePathForIncludeKey
parameter and pass it to Preprocessor.parse instead of the hard-coded empty
string. Locate the analyzer flow that invokes parseShaderPass and propagate the
same include base path used by compiler parsing, preserving relative include
resolution for analyzer-only passes.

In `@packages/shader-parser/src/Preprocessor.ts`:
- Around line 63-64: Update the missing-include handling in the Preprocessor
method containing Logger.error so it creates and propagates a structured
Diagnostic/preprocessing error to both callers instead of only logging and
returning an empty string; ensure downstream compiler/analyzer generation stops
whenever that error is present.

In `@packages/shader-parser/src/ShaderCompilerUtils.ts`:
- Around line 12-13: Make shader error source optional throughout the
error-reporting flow: update GSError.source, createGSError, and SemanticAnalyzer
reporting to accept undefined, and ensure GSError.toString handles missing
source safely. Preserve existing diagnostics when processingPassText is
available.
- Around line 8-10: The pooled ShaderRange and ShaderPosition instances must not
remain attached to published parse results. Update the AST publication/return
path around ShaderAnalyzer.analyze() and TreeNode.location to clone or
materialize independent location objects before exposing program, rather than
relying on clearAllShaderCompilerObjectPool() or pooled dispose behavior.

In `@packages/shader-parser/src/sourceParser/ShaderSourceParser.ts`:
- Around line 520-537: In the entry-assignment flow around the isVertex and
passSource[key] checks, only assign vertexEntryLocation or fragmentEntryLocation
after confirming the binding is not already present. Keep the original location
paired with the retained first entry, while duplicate assignments should still
emit DuplicateEntryAssignment without overwriting that location.
- Around line 400-408: Move the RenderQueueType variableMap assignment in the
surrounding parsing logic so it occurs only after the lookup successfully
resolves `sm`. On the invalid path that emits
`DiagnosticType.InvalidRenderQueueVariable` and returns, remove or avoid
retaining the unresolved token mapping, preserving inherited valid values.

In `@packages/shader-parser/src/sourceParser/ShaderSourceParser.y`:
- Line 22: Fix the grammar productions in the shader parser: rename the
VertextShader token to VertexShader and update its references, add | separators
between alternatives in main_shader_assignment and render_state_prop_assignment,
and make pass_statements recursive so a pass accepts multiple declarations or
assignments.

In `@tests/vitest.config.ts`:
- Line 27: Remove the duplicate --use-gl entry from the args configuration in
the Vitest setup, retaining one intentional GL backend value so Chromium uses an
unambiguous backend for WebGL tests.

---

Outside diff comments:
In `@packages/shader-compiler/src/ShaderInstructionEncoder.ts`:
- Around line 66-84: Update ShaderInstructionEncoder.parse and its
conditional-directive handling to catch errors from parsePreprocessorCondition
for malformed `#if` and `#elif` expressions, returning the encoder’s established
validation/error result instead of throwing. Preserve the existing instruction
and backfill behavior for valid conditions, and keep the PreprocessorCondition
type unchanged.

---

Nitpick comments:
In `@packages/shader-compiler/src/codeGen/GLESVisitor.ts`:
- Line 86: Update the fnSymbols emptiness check in the surrounding visitor
method to use optional chaining on fnSymbols.length, matching the style used by
_fragmentMain. Preserve the existing _softMissEntry(false) behavior when no
function symbols are available.

In `@packages/shader-parser/src/parser/TypeSystem.ts`:
- Around line 14-20: Remove the redundant typeof guard in
TypeSystem.isAssignable and collapse the final comparison logic to a single
return target === source after the existing undefined and TypeAny checks.
Preserve the current assignability behavior and surrounding comments as
appropriate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 51869212-62ba-44f4-83e4-bed53ac4b1e1

📥 Commits

Reviewing files that changed from the base of the PR and between 9fe48c6 and 4e33d49.

⛔ Files ignored due to path filters (2)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • tests/src/shader-compiler/shaders/struct-based-attribute.shader is excluded by !**/*.shader
📒 Files selected for processing (121)
  • examples/package.json
  • examples/src/shader-playground.ts
  • examples/vite.config.js
  • packages/core/src/Engine.ts
  • packages/core/src/animation/AnimationClip.ts
  • packages/core/src/animation/Animator.ts
  • packages/core/src/animation/AnimatorController.ts
  • packages/core/src/shader/index.ts
  • packages/design/src/shader-compiler/IShaderAnalyzer.ts
  • packages/design/src/shader-compiler/IShaderCompiler.ts
  • packages/design/src/shader-compiler/IShaderProgram.ts
  • packages/design/src/shader-compiler/index.ts
  • packages/design/src/shader-compiler/shaderSource/IShaderPassSource.ts
  • packages/shader-analyzer/package.json
  • packages/shader-analyzer/src/Diagnostic.ts
  • packages/shader-analyzer/src/DiagnosticCategory.ts
  • packages/shader-analyzer/src/ShaderAnalyzer.ts
  • packages/shader-analyzer/src/ShaderValidator.ts
  • packages/shader-analyzer/src/convert.ts
  • packages/shader-analyzer/src/index.ts
  • packages/shader-analyzer/tsconfig.json
  • packages/shader-compiler/package.json
  • packages/shader-compiler/rollup.config.js
  • packages/shader-compiler/src/GSError.ts
  • packages/shader-compiler/src/ParserUtils.ts
  • packages/shader-compiler/src/ShaderCompiler.ts
  • packages/shader-compiler/src/ShaderInstructionEncoder.ts
  • packages/shader-compiler/src/codeGen/CodeGenVisitor.ts
  • packages/shader-compiler/src/codeGen/GLES100.ts
  • packages/shader-compiler/src/codeGen/GLES300.ts
  • packages/shader-compiler/src/codeGen/GLESVisitor.ts
  • packages/shader-compiler/src/codeGen/VisitorContext.ts
  • packages/shader-compiler/src/codeGen/types.ts
  • packages/shader-compiler/src/common/BaseToken.ts
  • packages/shader-compiler/src/common/SymbolTable.ts
  • packages/shader-compiler/src/common/SymbolTableStack.ts
  • packages/shader-compiler/src/index.ts
  • packages/shader-compiler/src/parser/ShaderInfo.ts
  • packages/shader-compiler/tsconfig.json
  • packages/shader-compiler/verbose/package.json
  • packages/shader-parser/package.json
  • packages/shader-parser/src/DiagnosticType.ts
  • packages/shader-parser/src/GSError.ts
  • packages/shader-parser/src/ParserUtils.ts
  • packages/shader-parser/src/Preprocessor.ts
  • packages/shader-parser/src/ShaderCompilerUtils.ts
  • packages/shader-parser/src/common/BaseLexer.ts
  • packages/shader-parser/src/common/BaseToken.ts
  • packages/shader-parser/src/common/IBaseSymbol.ts
  • packages/shader-parser/src/common/PreprocessorCondition.ts
  • packages/shader-parser/src/common/ShaderPosition.ts
  • packages/shader-parser/src/common/ShaderRange.ts
  • packages/shader-parser/src/common/SymbolTable.ts
  • packages/shader-parser/src/common/SymbolTableStack.ts
  • packages/shader-parser/src/common/enums/Keyword.ts
  • packages/shader-parser/src/common/enums/ShaderStage.ts
  • packages/shader-parser/src/common/index.ts
  • packages/shader-parser/src/common/types.ts
  • packages/shader-parser/src/formatDiagnostic.ts
  • packages/shader-parser/src/index.ts
  • packages/shader-parser/src/lalr/CFG.ts
  • packages/shader-parser/src/lalr/LALR1.ts
  • packages/shader-parser/src/lalr/Production.ts
  • packages/shader-parser/src/lalr/State.ts
  • packages/shader-parser/src/lalr/StateItem.ts
  • packages/shader-parser/src/lalr/Utils.ts
  • packages/shader-parser/src/lalr/index.ts
  • packages/shader-parser/src/lalr/types.ts
  • packages/shader-parser/src/lexer/Lexer.ts
  • packages/shader-parser/src/lexer/index.ts
  • packages/shader-parser/src/parser/AST.ts
  • packages/shader-parser/src/parser/Grammar.ts
  • packages/shader-parser/src/parser/GrammarSymbol.ts
  • packages/shader-parser/src/parser/ICodeGenVisitor.ts
  • packages/shader-parser/src/parser/PassParser.ts
  • packages/shader-parser/src/parser/SemanticAnalyzer.ts
  • packages/shader-parser/src/parser/ShaderIOAnalyzer.ts
  • packages/shader-parser/src/parser/ShaderInfo.ts
  • packages/shader-parser/src/parser/ShaderTargetParser.ts
  • packages/shader-parser/src/parser/TargetParser.y
  • packages/shader-parser/src/parser/TypeSystem.ts
  • packages/shader-parser/src/parser/builtin/functions.ts
  • packages/shader-parser/src/parser/builtin/index.ts
  • packages/shader-parser/src/parser/builtin/variables.ts
  • packages/shader-parser/src/parser/index.ts
  • packages/shader-parser/src/parser/symbolTable/FnSymbol.ts
  • packages/shader-parser/src/parser/symbolTable/StructSymbol.ts
  • packages/shader-parser/src/parser/symbolTable/SymbolDataType.ts
  • packages/shader-parser/src/parser/symbolTable/SymbolInfo.ts
  • packages/shader-parser/src/parser/symbolTable/VarSymbol.ts
  • packages/shader-parser/src/parser/symbolTable/index.ts
  • packages/shader-parser/src/parser/types.ts
  • packages/shader-parser/src/sourceParser/ShaderSourceFactory.ts
  • packages/shader-parser/src/sourceParser/ShaderSourceParser.ts
  • packages/shader-parser/src/sourceParser/ShaderSourceParser.y
  • packages/shader-parser/src/sourceParser/ShaderSourceSymbol.ts
  • packages/shader-parser/src/sourceParser/SourceLexer.ts
  • packages/shader-parser/src/sourceParser/index.ts
  • packages/shader-parser/tsconfig.json
  • rollup.config.js
  • tests/package.json
  • tests/src/shader-analyzer/BranchAwareLookup.test.ts
  • tests/src/shader-analyzer/BranchDeclarationConflict.test.ts
  • tests/src/shader-analyzer/BranchResolutionAmbiguity.test.ts
  • tests/src/shader-analyzer/BuiltinShaderSmoke.test.ts
  • tests/src/shader-analyzer/DiagnosticCoverage.test.ts
  • tests/src/shader-analyzer/DiagnosticSmoke.test.ts
  • tests/src/shader-analyzer/MacroBranchMatrix.test.ts
  • tests/src/shader-analyzer/ReuseAst.test.ts
  • tests/src/shader-analyzer/ShaderAnalyzer.test.ts
  • tests/src/shader-analyzer/ShaderIOAnalyzer.test.ts
  • tests/src/shader-analyzer/ShaderPlayground.test.ts
  • tests/src/shader-compiler/AnalyzerInjection.test.ts
  • tests/src/shader-compiler/DiagnosticDriverConsistency.test.ts
  • tests/src/shader-compiler/MacroBranchRuntime.test.ts
  • tests/src/shader-compiler/Precompile.test.ts
  • tests/src/shader-compiler/PreprocessorConditionConformance.test.ts
  • tests/src/shader-compiler/ReturnStatementInvariant.test.ts
  • tests/src/shader-compiler/ShaderCompiler.test.ts
  • tests/src/shader-compiler/StateIsolation.test.ts
  • tests/vitest.config.ts
💤 Files with no reviewable changes (11)
  • packages/shader-compiler/verbose/package.json
  • packages/shader-parser/src/lalr/LALR1.ts
  • packages/shader-compiler/src/common/SymbolTableStack.ts
  • packages/shader-compiler/src/common/BaseToken.ts
  • packages/shader-compiler/src/parser/ShaderInfo.ts
  • packages/shader-compiler/src/common/SymbolTable.ts
  • packages/shader-compiler/src/GSError.ts
  • packages/shader-parser/src/lalr/State.ts
  • packages/shader-parser/src/lalr/StateItem.ts
  • packages/shader-compiler/src/ParserUtils.ts
  • packages/shader-parser/src/lalr/CFG.ts
🛑 Comments failed to post (12)
packages/core/src/animation/AnimationClip.ts (1)

58-66: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -nP '\.addEvent\s*\(' packages tests examples --glob '*.{ts,tsx}'
rg -nP 'parameter\s*:\s*(Object|object)' packages/core/src/animation

Repository: galacean/engine

Length of output: 1410


🏁 Script executed:

#!/bin/bash
set -eu

echo "AnimationClip.ts relevant lines:"
sed -n '45,72p' packages/core/src/animation/AnimationClip.ts

echo
echo "AnimationEvent.ts relevant lines:"
sed -n '1,40p' packages/core/src/animation/AnimationEvent.ts

echo
echo "AnimationClip addEvent implementation:"
rg -n -A 45 'addEvent\(param: AnimationEvent | string' packages/core/src/animation/AnimationClip.ts packages/loader/src gts/src tests/src 2>/dev/null || true

echo
echo "Primitive payload usages in addEvent call sites:"
sed -n '540,680p' tests/src/core/Animator.test.ts
sed -n '720,750p' tests/src/core/Animator.test.ts

echo
echo "TypeScript availability/version clues:"
fd -a 'tsconfig.json|package.json' . | sed 's#^\./##' | head -50
rg -n '"typescript"|`@types/node`' package.json packages/*/package.json 2>/dev/null || true

Repository: galacean/engine

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -eu

echo "Focused AnimationClip.ts:"
sed -n '1,100p' packages/core/src/animation/AnimationClip.ts

echo
echo "Focused AnimationEvent.ts:"
sed -n '1,80p' packages/core/src/animation/AnimationEvent.ts

echo
echo "Focused AnimationClip addEvent implementation:"
rg -n -A 60 "addEvent\\(param: AnimationEvent|function addEvent|private addEvent" packages/core/src/animation/AnimationClip.ts

echo
echo "Focused primitive payload usages in tests:"
sed -n '540,685p' tests/src/core/Animator.test.ts
sed -n '720,752p' tests/src/core/Animator.test.ts
sed -n '1685,1715p' tests/src/core/Animator.test.ts

echo
echo "Focused addEvent call sites:"
python3 - <<'PY'
from pathlib import Path
patterns = ["packages/*/**/*.ts", "tests/**/*.ts"]
for path in Path(".").glob("packages/core/src/animation/AnimationClip.ts"):
    print(path)
PY
rg -n "\.addEvent\s*\(" packages/core/src/animation packages/loader/src packages/shader-optimizer/src tests/src/core/Animator.test.ts 2>/dev/null || true

echo
echo "TypeScript availability/version clues:"
for f in package.json packages/core/package.json tsconfig.json; do
  [ -f "$f" ] && echo "--- $f" && sed -n '1,120p' "$f"
done
rg -n '"typescript"|`@types/node`|typeRoots' package.json packages/core/package.json packages/loader/package.json tsconfig.json 2>/dev/null || true

echo
echo "Any any/object/Object type usage in animation package:"
rg -n '\bparameter\b|animation event|functionName|addEvent' packages/core/src/animation packages/loader/src/gltf/extensions/GALACEAN_animation_event.ts packages/loader/src/resource-deserialize/resources/animationClip/AnimationClipDecoder.ts tests/src/core/Animator.test.ts

Repository: galacean/engine

Length of output: 27616


🌐 Web query:

TypeScript object type excludes primitives non-primitive type documentation

💡 Result:

In TypeScript, the object type (introduced in version 2.2) is specifically designed to represent any non-primitive type [1][2][3]. It excludes all JavaScript primitives, which include string, number, boolean, bigint, symbol, null, and undefined [4][2][5]. Key characteristics and distinctions: 1. The object type (lowercase): This is the type that excludes primitives [4][2]. It is useful for APIs that require non-primitive values, such as Object.create or Proxy, where passing a primitive would be inappropriate [4][6]. 2. Confusion with other types: - Object (uppercase): This refers to the global Object interface in JavaScript, which describes functionality common to all objects (e.g., toString, hasOwnProperty). It is generally recommended to avoid using the uppercase Object type [4][2]. - {} (the empty object type): This is often confused with object, but it is much broader. The {} type represents any non-nullish value, meaning it includes objects but also accepts primitives because they are auto-boxed (coerced) into objects when members are accessed [1][7]. Consequently, {} does not exclude primitives [1][5][7]. Usage Recommendation: - To enforce at compile time that a value must be a non-primitive, use the object type [2][3]. - If you are creating a generic constraint to ensure a type parameter is an object, use extends object (e.g., function process(arg: T) {... }) [3]. - If you need to distinguish between "plain" objects (e.g., {} literal) and other non-primitives like arrays or functions, you must combine the object type with runtime checks (type predicates), as TypeScript's type system does not natively distinguish these categories solely through static types [3].

Citations:


Preserve the existing animation-event payload contract.

AnimationClip.addEvent(..., parameter: object) rejects primitive payloads at compile time, but AnimationEvent.parameter is still Object and deserialization forwards primitive JSON values. Restore Object here, or update the event model and consumers together if primitives are intentionally being disallowed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/animation/AnimationClip.ts` around lines 58 - 66, Update
the addEvent overload and implementation in AnimationClip to preserve the
existing animation-event payload contract for deserialized primitive JSON
values. Align the parameter type with AnimationEvent.parameter by restoring
Object, or consistently update AnimationEvent and all consumers if primitives
are intentionally disallowed.
packages/core/src/Engine.ts (1)

647-648: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate Engine.ts and Shader/ShaderCompiler references =="
git ls-files | rg '(^|/)Engine\.ts$|shaderCompiler|ShaderCompiler|shaderAnalyzer' || true

echo
echo "== Engine.ts outline around shader compiler/analyzer section =="
if [ -f packages/core/src/Engine.ts ]; then
  wc -l packages/core/src/Engine.ts
  sed -n '580,680p' packages/core/src/Engine.ts | nl -ba -v580
fi

echo
echo "== all _setAnalyzer references =="
rg -n "_setAnalyzer|_shaderCompiler|shaderAnalyzer" packages core src . 2>/dev/null || true

Repository: galacean/engine

Length of output: 580


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Engine.ts lines 580-680 =="
sed -n '580,680p' packages/core/src/Engine.ts

echo
echo "== Shader._shaderCompiler and _setAnalyzer references with context =="
rg -n -C 4 '_setAnalyzer|_shaderCompiler|shaderAnalyzer|setAnalyzer' packages/shader-compiler/src/ShaderCompiler.ts packages/core/src/Engine.ts packages/design/src/shader-compiler/IShaderCompiler.ts 2>/dev/null || true

echo
echo "== Engine outline =="
ast-grep outline packages/core/src/Engine.ts --view compact 2>/dev/null | sed -n '1,200p' || true

Repository: galacean/engine

Length of output: 7498


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== ShaderClass and ShaderCompiler static initialization =="
rg -n -C 5 'class Shader|_shaderCompiler|ShaderCompiler|constructor|initialize' packages/core/src packages/shader-compiler/src packages/design/src 2>/dev/null | sed -n '1,240p'

echo
echo "== Engine constructor/instances/_initialize callers =="
rg -n -C 4 'new Engine|Engine\(|_initialize\(|shaderCompiler|shaderAnalyzer' packages/core/src packages tests 2>/dev/null | sed -n '1,260p'

echo
echo "== source files of Shader and Engine relevant snippets =="
python3 - <<'PY'
from pathlib import Path
for p in ["packages/core/src/Shader.ts","packages/core/src/Engine.ts","packages/shader-compiler/src/ShaderCompiler.ts"]:
    x=Path(p)
    if not x.exists(): continue
    print(f"\n--- {p} ({len(x.read_text(encoding='utf-8',errors='replace').splitlines())} lines) ---")
    txt=x.read_text(encoding='utf-8')
    for token in ["class Shader","_shaderCompiler","constructor","static initialize","public async _createShader"]:
        if token in txt:
            idx=txt.find(token)
            print(f"\n### FIRST occurrence of {token} at char {idx}")
            line=txt.count("\n",0,max(0,idx))+1
            start=max(0,txt.rfind("\n",0,max(0,idx-1)))+1
            end=min(len(txt),txt.find("\n}",idx))+1000
            print("\n".join(f"{i+1:4d}: {l}" for i,l in enumerate(txt[start:end].splitlines(),start), lines=120))
PY

Repository: galacean/engine

Length of output: 37178


Bind the analyzer through the existing shader compiler.

new Engine({ shaderCompiler, shaderAnalyzer }) after Shader._shaderCompiler is set skips attaching shaderAnalyzer, while Shader.create still uses that initialized compiler. Apply the analyzer to the existing compiler or enforce a clear lifecycle error instead of dropping the configuration silently.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/Engine.ts` around lines 647 - 648, Update the Engine
initialization flow around Shader._shaderCompiler and the existing
shaderCompiler so shaderAnalyzer is attached to the compiler used by
Shader.create, including when that compiler was initialized previously. Reuse
the existing _setAnalyzer path or enforce an explicit lifecycle error; do not
silently discard the provided analyzer.
packages/shader-analyzer/src/ShaderValidator.ts (1)

416-444: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

GLSL ES vec3 constructor from vec4 truncation allowed

💡 Result:

Yes, truncation of a vec4 to a vec3 using the constructor is allowed in GLSL ES [1][2][3]. You can explicitly convert a higher-dimensional vector to a lower-dimensional one by passing the longer vector into the constructor of the shorter one; the constructor will truncate the vector to the required length [1][2][3]. For example: vec4 myVec4 = vec4(1.0, 2.0, 3.0, 4.0); vec3 myVec3 = vec3(myVec4); // Result: vec3(1.0, 2.0, 3.0) This is an explicit operation defined by the GLSL ES specifications regarding vector constructors [4][5][6]. Note that this is distinct from an implicit cast; if your code is generating an error (such as "implicit cast from vec4 to vec3"), ensure you are explicitly using the constructor as shown above [7]. If you need more granular control over which components are kept, swizzling (e.g., myVec4.xyz) is the standard alternative approach [7][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== repository files matching ShaderValidator/TypeSystem =="
fd -a 'ShaderValidator\.ts|TypeSystem\.ts' . | sed 's#^\./##'

echo "== search constructor arg check and TypeSystem functions =="
rg -n "_checkConstructorArgs|ConstructorArgCount|matrixComponentCount|vectorComponentCount|isScalarType" packages/shader-analyzer/src -S

echo "== outline ShaderValidator =="
ast-grep outline packages/shader-analyzer/src/ShaderValidator.ts --view compact | sed -n '1,220p'

echo "== relevant lines ShaderValidator =="
cat -n packages/shader-analyzer/src/ShaderValidator.ts | sed -n '360,460p'

echo "== candidate TypeSystem lines =="
file="$(fd 'TypeSystem\.ts' packages/shader-analyzer/src | head -n1)"
if [ -n "${file:-}" ]; then
  wc -l "$file"
  rg -n "matrixComponentCount|vectorComponentCount|isScalarType|typeName|component" "$file"
  cat -n "$file" | sed -n '1,220p'
fi

echo "== tests mentioning constructor/vectors/matrices =="
rg -n "vec3\\(|vec4\\(|mat|ConstructorArgCount|constructor" -S --glob '!node_modules' --glob '!dist' --glob '!build' .

Repository: galacean/engine

Length of output: 2657


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
# Read-only behavioral probe mirroring the component-count logic shown in the reviewed code.
from pathlib import Path

src = Path("packages/shader-analyzer/src/ShaderValidator.ts").read_text()
check = src[src.index("private _checkConstructorArgs"):src.index("private _checkCastArgs")]
print("_checkConstructorArgs present:", "_checkConstructorArgs" in check)
print("single-vector short-circuit present:", 'TypeSystem.vectorComponentCount(list.paramSig[0]) >= need' in check)

# Map constructor signatures from TypeSystem.ts if present.
p = next(Path(".").rglob("TypeSystem.ts"), None)
if p:
    print("TypeSystem.ts path:", p)
    ts = p.read_text()
    print("has matrixComponentCount:", "matrixComponentCount" in ts)
    print("has vectorComponentCount:", "vectorComponentCount" in ts)
PY

Repository: galacean/engine

Length of output: 258


Allow single larger-vector constructor truncation.

GLSL ES vector constructors permit vecN(vecM) when M >= N; the extra components are truncated, e.g. vec3(vec4(...)). The current exact-count path flags this as ConstructorArgCount because total is computed from the argument vector components, not the constructor target size. Add the same single-argument short-circuit used for matrix constructors.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shader-analyzer/src/ShaderValidator.ts` around lines 416 - 444,
Update the constructor argument-count validation around the existing
matrix-constructor short-circuit to also accept a single vector argument whose
component count is at least the target vector size, allowing truncation for
cases such as vec3(vec4(...)). Preserve exact-count validation for smaller
vectors, multi-argument constructors, and other existing cases.
packages/shader-parser/src/GSError.ts (1)

29-35: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files matching GSError.ts and ShaderIOAnalyzer:"
fd -a 'GSError\.ts|ShaderIOAnalyzer\.(ts|js)$' . || true

echo
echo "Search relevant symbols:"
rg -n "class GSError|function.*formatDiagnosticSource|_reportEntryNotFound|new ShaderPosition|interface ShaderPosition|type ShaderPosition" . --glob '!node_modules' --glob '!dist' --glob '!build' || true

echo
echo "Outline GSError:"
file=$(fd 'GSError\.ts' . | head -n1)
if [ -n "${file:-}" ]; then
  wc -l "$file"
  cat -n "$file"
fi

Repository: galacean/engine

Length of output: 2780


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate ShaderPosition and GSError/Formatter implementation, plus caller.
echo "Candidate files:"
git ls-files | rg '(^|/)GSError\.ts$|ShaderIOAnalyzer|Diagnostic|formatDiagnosticSource|ShaderPosition' || true

echo
echo "Targeted source excerpts:"
for f in $(git ls-files | rg 'packages/shader-parser/src/(GSError|.*Analyzer|.*Diagnostic).*\.ts$|packages/shader-parser/src/.*\.ts$'); do
  if rg -q 'formatDiagnosticSource|interface ShaderPosition|type ShaderPosition|ShaderPosition|class GSError|_reportEntryNotFound' "$f"; then
    echo "===== $f ====="
    ast-grep outline "$f" --match 'ShaderPosition|GSError|_reportEntryNotFound|formatDiagnosticSource' --view compact || true
    echo "-- excerpts --"
    rg -n -C 4 'formatDiagnosticSource|interface ShaderPosition|type ShaderPosition|class ShaderPosition|class GSError|_reportEntryNotFound|formatDiagnostic' "$f" || true
  fi
done

Repository: galacean/engine

Length of output: 5973


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "===== ShaderPosition ====="
cat -n packages/shader-parser/src/common/ShaderPosition.ts

echo
echo "===== ShaderRange ====="
cat -n packages/shader-parser/src/common/ShaderRange.ts

echo
echo "===== formatDiagnosticSource ====="
cat -n packages/shader-parser/src/formatDiagnostic.ts

echo
echo "===== ShaderCompilerUtils ====="
cat -n packages/shader-parser/src/ShaderCompilerUtils.ts

echo
echo "===== ShaderIOAnalyzer createGSError/_reportEntryNotFound area ====="
sed -n '1,230p' packages/shader-parser/src/parser/ShaderIOAnalyzer.ts | cat -n

echo
echo "===== createGSError usages ====="
rg -n "createGSError\\(|new GSError\\(" packages/shader-parser src tests --glob '!node_modules' --glob '!dist' --glob '!build' || true

Repository: galacean/engine

Length of output: 16407


Handle plain fallback shader positions in GSError.toString().

_reportEntryNotFound() can supply { index: 0, line: 0, column: 0 } as ShaderPosition, which is not a ShaderPosition instance, so toString() falls through to location.start and throws when formatting errors. Use a structural location-check before assuming a range.

Proposed fix
-    const range =
-      location instanceof ShaderPosition
-        ? { start: location, end: location }
-        : { start: location.start, end: location.end };
+    const range =
+      "start" in location && "end" in location
+        ? { start: location.start, end: location.end }
+        : { start: location, end: location };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  override toString(): string {
    const { location } = this;
    const range =
      "start" in location && "end" in location
        ? { start: location.start, end: location.end }
        : { start: location, end: location };
    return formatDiagnosticSource(this.source || undefined, range, `${this.name}: ${this.message}`);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shader-parser/src/GSError.ts` around lines 29 - 35, Update
GSError.toString() to detect location objects structurally before accessing
location.start and location.end, treating plain position objects with index,
line, and column fields as a single-point range like ShaderPosition instances.
Preserve the existing range handling for locations that already provide start
and end positions.
packages/shader-parser/src/parser/PassParser.ts (1)

17-24: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Preserve the include base path for analyzer parsing.

Line 24 hard-codes "", while compiler parsing accepts basePathForIncludeKey. A pass containing #include "./chunk.glsl" will make new URL(..., "") fail in analyzer-only flows, despite compiling with a valid base path. Add the base-path parameter here and propagate the same value from the analyzer’s pass source.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shader-parser/src/parser/PassParser.ts` around lines 17 - 24, Update
parseShaderPass to accept a basePathForIncludeKey parameter and pass it to
Preprocessor.parse instead of the hard-coded empty string. Locate the analyzer
flow that invokes parseShaderPass and propagate the same include base path used
by compiler parsing, preserving relative include resolution for analyzer-only
passes.
packages/shader-parser/src/Preprocessor.ts (1)

63-64: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Propagate missing includes as structured diagnostics.

Line 63 only logs, then Line 64 erases the directive. The compiler/analyzer receives no failure object, so a pass can continue without reporting the missing include through Diagnostic. Return preprocessing errors to both callers and block generation when one exists.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shader-parser/src/Preprocessor.ts` around lines 63 - 64, Update the
missing-include handling in the Preprocessor method containing Logger.error so
it creates and propagates a structured Diagnostic/preprocessing error to both
callers instead of only logging and returning an empty string; ensure downstream
compiler/analyzer generation stops whenever that error is present.
packages/shader-parser/src/ShaderCompilerUtils.ts (2)

8-10: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate files =="
fd -a 'ShaderCompilerUtils\.ts|ShaderPosition|ShaderRange|ClearableObjectPool' . | sed 's#^\./##'

echo "== relevant source outline =="
ast-grep outline packages/shader-parser/src/ShaderCompilerUtils.ts --view expanded || true

echo "== relevant source =="
cat -n packages/shader-parser/src/ShaderCompilerUtils.ts

Repository: galacean/engine

Length of output: 3511


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== ClearableObjectPool =="
cat -n packages/core/src/utils/ClearableObjectPool.ts

echo "== ShaderPosition and ShaderRange =="
cat -n packages/shader-parser/src/common/ShaderPosition.ts
cat -n packages/shader-parser/src/common/ShaderRange.ts

echo "== parser/ast location and pass.program usages =="
rg -n "createPosition|createRange|program:|pass\.program|\\.program|ShaderRange|ShaderPosition|parse|Compiler|analyze|analyzer" packages/shader-parser packages -g '!**/dist/**' -g '!**/build/**' | head -n 300

Repository: galacean/engine

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== AST relevant lines =="
sed -n '45,165p' packages/shader-parser/src/parser/AST.ts | cat -n | sed 's/^/AST: /'

echo "== parser shared/shared reset lines =="
sed -n '1,45p' packages/shader-parser/src/parser/PassParser.ts | cat -n | sed 's/^/PassParser: /'
sed -n '60,95p' packages/shader-parser/src/parser/ShaderTargetParser.ts | cat -n | sed 's/^/ShaderTargetParser: /'

echo "== package exports and public parse APIs =="
rg -n "export .*parse|function parse|parseShader|^export|clearAllShaderCompilerObjectPool|ShaderCompilerUtils" packages/shader-parser/src packages -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**' | head -n 250

Repository: galacean/engine

Length of output: 35254


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== ShaderAnalyzer relevant lines =="
sed -n '1,155p' packages/shader-analyzer/src/ShaderAnalyzer.ts | cat -n | sed 's/^/ShaderAnalyzer: /'

echo "== ShaderAnalyzer pass/result relevant lines =="
sed -n '155,240p' packages/shader-analyzer/src/ShaderAnalyzer.ts | cat -n | sed 's/^/ShaderAnalyzer: /'

echo "== read-only pool reuse and location mutation probe =="
python3 - <<'PY'
class Position:
    def __init__(self):
        self.index = self.line = self.column = 0
    def set(self, index, line = 0, column = 0):
        self.index, self.line, self.column = index, line, column
    def __repr__(self):
        return f"({self.index},{self.line},{self.column})"

class Range:
    def __init__(self):
        self.start = self.end = None
    def set(self, start, end):
        self.start, self.end = start, end
    def __repr__(self):
        return f"{self.start}..{self.end}"

class Pool:
    def __init__(self, cls):
        self._type = cls
        self._elements = []
        self._used = 0
    def get(self):
        if self._used == len(self._elements):
            self._elements.append(self._type())
        self._used += 1
        return self._elements[self._used - 1]
    def clear(self):
        self._used = 0

position_pool = Pool(Position)
range_pool = Pool(Range)

ast1_loc = range_pool.get()
ast1_loc.set(range_pool.get().set(0, 1, 1), range_pool.get().set(9, 1, 7))
ast2_output = {"program": ast1_loc}

range_pool.clear()
position_pool.clear()

next_range = range_pool.get()
next_range.set(range_pool.get().set(20, 2, 2), range_pool.get().set(25, 2, 6))

print("Before second parse:", ast2_output["program"])
print("After second parse:", ast2_output["program"])
print("next_range equals ast2_output.program:", next_range is ast2_output["program"])
PY

Repository: galacean/engine

Length of output: 9528


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== read-only pool reuse and location mutation probe =="
python3 - <<'PY'
class Position:
    index = line = column = 0
    def set(self, index, line=0, column=0):
        self.index, self.line, self.column = index, line, column
    def dispose(self):
        self.index = self.line = self.column = 0
    def __repr__(self):
        return f"({self.index},{self.line},{self.column})"

class Range:
    start = end = None
    def set(self, start, end):
        self.start, self.end = start, end
    def dispose(self):
        self.start.dispose()
        self.end.dispose()
    def __repr__(self):
        return f"{self.start}..{self.end}"

class Pool:
    def __init__(self, cls):
        self.__dict__["_cls"] = cls
        self.__dict__["_elements"] = []
        self.__dict__["_used"] = 0
    def get(self):
        if self._used == len(self._elements):
            self._elements.append(self._cls())
        self._used += 1
        return self._elements[self._used - 1]
    def clear(self):
        self._used = 0

position_pool = Pool(Position)
range_pool = Pool(Range)

# Simulate ast.location = ShaderCompilerUtils.createRange(start, end).
ast1_location = range_pool.get()
ast1_location.start = range_pool.get()
ast1_location.start.set(0, 1, 1)
ast1_location.end = range_pool.get()
ast1_location.end.set(9, 1, 7)

retained_result = {"program": ast1_location}

print("before second parse:", retained_result["program"])

# Simulate clearAllShaderCompilerObjectPool() rewinding the same pool without disposing.
range_pool.clear()
position_pool.clear()

# Simulate next parse's reuse of the same pooled location/start/end objects.
next_range = range_pool.get()
next_range.start = range_pool.get()
next_range.start.set(20, 2, 2)
next_range.end = range_pool.get()
next_range.end.set(25, 2, 6)

print("after second parse:", retained_result["program"])
print("reused exact object:", next_range is ast1_location)
print("start reused exact object:", next_range.start is retained_result["program"].start)
PY

Repository: galacean/engine

Length of output: 345


Publish parse results with independent location objects.

clearAllShaderCompilerObjectPool() just resets counter, and ShaderPosition.dispose() zeroes position fields while ShaderRange.dispose() also clears its start/end references. Since TreeNode.location stores the pooled ShaderRange and ShaderAnalyzer.analyze() exposes program directly, a later parser call can reuse the same range object and mutate diagnostics/results from an earlier parse. Use parse result-owned locations or copy them before returning/publishing the AST.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shader-parser/src/ShaderCompilerUtils.ts` around lines 8 - 10, The
pooled ShaderRange and ShaderPosition instances must not remain attached to
published parse results. Update the AST publication/return path around
ShaderAnalyzer.analyze() and TreeNode.location to clone or materialize
independent location objects before exposing program, rather than relying on
clearAllShaderCompilerObjectPool() or pooled dispose behavior.

12-13: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Repository files around shader parser:"
git ls-files | rg 'packages/shader-parser|ShaderCompilerUtils|ShaderTargetParser|GSError|tsconfig|package.json' | head -200

echo
echo "ShaderCompilerUtils outline:"
file="$(git ls-files | rg 'packages/shader-parser/src/ShaderCompilerUtils\.ts$' || true)"
echo "FILE=$file"
if [ -n "$file" ]; then
  wc -l "$file"
  ast-grep outline "$file" || true
  echo
  cat -n "$file"
fi

echo
echo "Search relevant symbols:"
rg -n "processingPassText|createGSError|source parameter|GSError|ShaderTargetParser" packages || true

Repository: galacean/engine

Length of output: 13770


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "GSError.ts:"
cat -n packages/shader-parser/src/GSError.ts

echo
echo "ShaderCompilerUtils.ts:"
cat -n packages/shader-parser/src/ShaderCompilerUtils.ts

echo
echo "PassParser.ts:"
cat -n packages/shader-parser/src/parser/PassParser.ts

echo
echo "ShaderTargetParser.ts parse/createGSError area:"
sed -n '100,140p' packages/shader-parser/src/parser/ShaderTargetParser.ts | cat -n

echo
echo "ShaderCompiler.ts:"
sed -n '50,85p' packages/shader-parser/src/../../shader-compiler/src/ShaderCompiler.ts | cat -n

echo
echo "TS strict null config references:"
rg -n '"strictNullChecks"|strictNullChecks|noImplicitAny' tsconfig*.json packages/shader-parser/tsconfig.json packages/shader-parser/package.json || true

echo
echo "Try TypeScript compiler availability and relevant type assignments:"
node - <<'JS'
const fs = require('fs');
const path = require.resolve('typescript', { paths: [process.cwd()] });
console.log('typescript path', path);
JS

Repository: galacean/engine

Length of output: 8952


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "All createGSError calls:"
rg -n "createGSError\\(" packages/shader-parser packages/shader packages/shader-compiler -C 3 || true

echo
echo "SemanticAnalyzer createGSError exact:"
rg -n "ShaderCompilerUtils\\.processingPassText" packages/shader-parser/src/parser/SemanticAnalyzer.ts -C 4 || true

echo
echo "Compile only package with available TS (read-only):"
tmp="$(mktemp -d)"
if command -v npx >/dev/null 2>&1; then
  npx -- tsconfig@packages/shader-parser/tsconfig.json --noEmit --project packages/shader-parser/tsconfig.json || true
elif command -v tsc >/dev/null 2>&1; then
  (cd packages/shader-parser && tsc --noEmit) || true
fi

Repository: galacean/engine

Length of output: 5150


Make GSError source optional end to end.

processingPassText is optional, but parseShaderPass/compiler set it to undefined in a finally block, then ShaderTargetParser.parse and SemanticAnalyzer pass it into createGSError / GSError. Either make GSError.source, createGSError source, and semantic error reporting optional and handle undefined in toString, or guarantee processingPassText is always set before these paths run.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shader-parser/src/ShaderCompilerUtils.ts` around lines 12 - 13, Make
shader error source optional throughout the error-reporting flow: update
GSError.source, createGSError, and SemanticAnalyzer reporting to accept
undefined, and ensure GSError.toString handles missing source safely. Preserve
existing diagnostics when processingPassText is available.
packages/shader-parser/src/sourceParser/ShaderSourceParser.ts (2)

400-408: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not retain an unresolved RenderQueueType binding.

renderStates.variableMap[key] is assigned before this failed lookup. The new early return leaves that invalid mapping serialized, overriding any inherited valid value despite the diagnostic saying it will not be applied. Assign variableMap[key] only after sm is found.

Proposed fix
     if (value == undefined) {
-      renderStates.variableMap[key] = word.lexeme;
       const lookupSymbol = this._lookupSymbol;
       lookupSymbol.set(word.lexeme, Keyword.GSRenderQueueType);
       const sm = this._symbolTableStack.lookup(lookupSymbol);
       if (!sm) {
         this._createCompileError(
           `Invalid RenderQueueType variable: ${word.lexeme} — property will not be applied at runtime.`,
           word.location,
           DiagnosticType.InvalidRenderQueueVariable
         );
         return;
       }
+      renderStates.variableMap[key] = word.lexeme;
     } else {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shader-parser/src/sourceParser/ShaderSourceParser.ts` around lines
400 - 408, Move the RenderQueueType variableMap assignment in the surrounding
parsing logic so it occurs only after the lookup successfully resolves `sm`. On
the invalid path that emits `DiagnosticType.InvalidRenderQueueVariable` and
returns, remove or avoid retaining the unresolved token mapping, preserving
inherited valid values.

520-537: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep the range paired with the retained entry binding.

Line 522 records the duplicate token’s location before Line 523 preserves the first entry name. ShaderIOAnalyzer therefore reports entry diagnostics at the second assignment while analyzing the first one. Store the location only after accepting the entry.

🧰 Tools
🪛 Biome (2.5.3)

[error] 520-520: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

(lint/correctness/noSwitchDeclarations)


[error] 521-521: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

(lint/correctness/noSwitchDeclarations)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shader-parser/src/sourceParser/ShaderSourceParser.ts` around lines
520 - 537, In the entry-assignment flow around the isVertex and passSource[key]
checks, only assign vertexEntryLocation or fragmentEntryLocation after
confirming the binding is not already present. Keep the original location paired
with the retained first entry, while duplicate assignments should still emit
DuplicateEntryAssignment without overwriting that location.
packages/shader-parser/src/sourceParser/ShaderSourceParser.y (1)

22-22: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
bison -Werror -o /tmp/shader-source-parser.c \
  packages/shader-parser/src/sourceParser/ShaderSourceParser.y

Repository: galacean/engine

Length of output: 196


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate grammar file"
fd -a 'ShaderSourceParser\.y$' . || true

echo
echo "Relevant source excerpt"
if [ -f packages/shader-parser/src/sourceParser/ShaderSourceParser.y ]; then
  nl -ba packages/shader-parser/src/sourceParser/ShaderSourceParser.y | sed -n '1,180p'
fi

echo
echo "References to VertextShader/VertexShader and pass_statements"
rg -n "VertextShader|VertexShader|pass_statements|main_shader_assignment|render_state_prop_assignment" packages/shader-parser/src/sourceParser/ShaderSourceParser.y . || true

echo
echo "Available parser tools"
for prog in bison yacc byacc ocamlyacc jison node python3; do
  echo -n "$prog: "
  command -v "$prog" || echo "not found"
done

Repository: galacean/engine

Length of output: 319


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Relevant source excerpt"
if [ -f packages/shader-parser/src/sourceParser/ShaderSourceParser.y ]; then
  awk '{printf "%6d  %s\n", NR, $0}' packages/shader-parser/src/sourceParser/ShaderSourceParser.y | sed -n '1,180p'
fi

echo
echo "References to VertextShader/VertexShader and key productions"
grep -n "VertextShader\|VertexShader\|pass_statements\|main_shader_assignment\|render_state_prop_assignment" packages/shader-parser/src/sourceParser/ShaderSourceParser.y || true

echo
echo "Lexer/token references"
find packages/shader-parser/src -type f \( -name '*.lex' -o -name '*.l' -o -name '*.ts' -o -name '*.js' \) -print -0 | xargs -0 grep -n "VertextShader\|VertexShader" || true

Repository: galacean/engine

Length of output: 4561


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Find source parser files"
find packages/shader-parser/src -type f \( -name '*.js' -o -name '*.ts' -o -name '*.mjs' -o -name '*.cjs' -o -name '*.g4' -o -name '*.antlr' \) -print

echo
echo "References to main productions and VertextShader"
grep -RIn "main_shader_assignment\|render_state_prop_assignment\|pass_statements\|mainShaderAssignment\|renderStatePropAssignment\|passStatements\|VertextShader" packages/shader-parser/src packages/shader-parser -g '!node_modules' -g '!dist' -g '!build' || true

echo
echo "Check for bison/yacc availability and parse table if generated"
find . -name '*Parser.js' -o -name '*Lexer.js' -o -name '*Parser.c' -o -name '*Parser.js' | sed -n '1,80p'

Repository: galacean/engine

Length of output: 3351


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Source parser files"
for f in packages/shader-parser/src/sourceParser/SourceLexer.ts packages/shader-parser/src/sourceParser/ShaderSourceParser.ts packages/shader-parser/src/sourceParser/ShaderSourceFactory.ts packages/shader-parser/src/sourceParser/index.ts packages/shader-parser/src/index.ts packages/shader-parser/src/formatDiagnostic.ts; do
  echo "===== $f ====="
  if [ -f "$f" ]; then wc -l "$f"; grep -nE "VertextShader|VertexShader|main_shader_assignment|render_state_prop_assignment|pass_statements|mainShader|renderState|passStatement" "$f" || true; fi
done

echo
echo "All references with accepted option"
grep -RIn "main_shader_assignment\|render_state_prop_assignment\|pass_statements\|mainShaderAssignment\|renderStatePropAssignment\|passStatements\|VertextShader|VertexShader" --exclude-dir=node_modules --exclude-dir=dist --exclude-dir=build packages/shader-parser/src packages/shader-parser || true

Repository: galacean/engine

Length of output: 4653


Fix the malformed Bison productions.

main_shader_assignment and render_state_prop_assignment need | between alternatives so Bison parses the grammar. Rename VertextShader to match the parser/runtime token (VertexShader), and make pass_statements recursive if a pass should allow multiple declarations/assignments.

Also applies to: 55-64, 138-146

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shader-parser/src/sourceParser/ShaderSourceParser.y` at line 22, Fix
the grammar productions in the shader parser: rename the VertextShader token to
VertexShader and update its references, add | separators between alternatives in
main_shader_assignment and render_state_prop_assignment, and make
pass_statements recursive so a pass accepts multiple declarations or
assignments.
tests/vitest.config.ts (1)

27-27: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Conflicting duplicate --use-gl flags.

--use-gl=egl and --use-gl=angle are both passed; Chromium honors only the last occurrence, so --use-gl=egl is silently ignored. Keep a single, intentional value so the GL backend used by the WebGL tests is unambiguous.

♻️ Keep one GL backend flag
-            args: ["--use-gl=egl", "--ignore-gpu-blocklist", "--use-gl=angle"]
+            args: ["--ignore-gpu-blocklist", "--use-gl=angle"]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

            args: ["--ignore-gpu-blocklist", "--use-gl=angle"]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/vitest.config.ts` at line 27, Remove the duplicate --use-gl entry from
the args configuration in the Vitest setup, retaining one intentional GL backend
value so Chromium uses an unambiguous backend for WebGL tests.

@GuoLei1990 GuoLei1990 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🫧 尘小沫

结论

Request changes。基于 dev/2.0...4e33d492 的完整增量 diff,并沿 source parser → pass parser/analyzer → codegen/precompile/runtime 各追了一层。当前 checks 虽然全绿,但仍有会误拦合法 shader、放行非法 shader、静默生成错误源码的阻塞问题。下面只列本轮新增结论,不重复现有 Copilot / CodeRabbit 评论。

阻塞问题

  1. [P1] source 阶段的错误与 entry 契约没有进入 compiler/analyzer envelope,missing entry 会被静默编成空 stage。 ShaderSourceParser 已在 packages/shader-parser/src/sourceParser/ShaderSourceParser.ts:38,520-552 收集 MissingEntryDuplicateEntryAssignment 和 RenderState 错误,但 packages/shader-compiler/src/ShaderCompiler.ts:35-39 只返回 IShaderSourceIShaderAnalyzer._diagnose 又只接收 pass parser errors 和 entry 字符串。因此缺少 FragmentShader 时,即使注入 analyzer 也看不到 source error;未注入 analyzer 的 bundler 路径更会在 GLESVisitor.ts:83-86,126-128,162-172 得到 "",随后把 truthy 的 programSource 序列化为空 instruction 数组。与此同时,standalone analyzer 把 ShaderLab 原始 entry range 配给拼接后的 passText,runtime 路径则退化为 0:0,位置协议两边都不成立。请保留 ShaderSourceParser 作为 source error 与 source/range 配对的唯一 owner,用一个 typed parse envelope 把 errors、binding source/range 和一次性 IO facts 传到 analyzer/codegen;compiler 依据其中的 blocking 状态失败,删除 _softMissEntry 空源码兼容路径,并删除 _diagnoseGLESVisitorShaderIOAnalyzer 的第二次平行扫描。

  2. [P1] 跨 stage 的 bare-name role fallback 会错误改写合法局部变量。 packages/shader-compiler/src/codeGen/VisitorContext.ts:103-108 在当前 stage map 未命中时回退另一 stage,CodeGenVisitor.ts:57-65 一旦命中就把 input.x 展平为 x。例如 fragment entry 参数叫 input 且是 Varying,而 vertex 内有普通 vec4 input;vertex map 不记录这个非 struct local,于是会偷用 fragment 的 role,生成未声明的 x。字符串 map 也无法表达 lexical shadowing。应保留 parser 已解析的 SymbolInfo / AST symbol identity 为 role 的权威 owner,删除 cross-stage fallback 和平行的 bare-name ownership;若全局 macro 确需跨 stage,必须由显式 global symbol identity 机械派生。

  3. [P1] 现有 pooled-location 评论只覆盖了 range,实际公开的整个 AST 都会被任意下一次 compiler parse 改写。 ShaderAnalyzer.ts:23-29 承诺 program 仅在下一次同 analyzer 调用前有效,但 analyzer 与 ShaderCompiler.ts:35-37 都调用全局 ShaderCompilerUtils.clearAllShaderCompilerObjectPool()ClearableObjectPool.clear() 只是把 used count 归零,下一次 parse 会复用并重写同一批 AST node。于是 analyze A → compiler parse B → generate(A.program) 会静默按被 B 覆盖的节点生成,复制 ShaderRange 并不能修复。parser/arena 应拥有 AST lifetime:删除裸 pooled program 的公共契约,改为独立 arena 或 parser-owned session/callback,并增加 analyzer/compiler 交错解析回归。

  4. [P1] call graph 按函数名合并 overload,合法 shader 会被误判为递归或 vertex derivative。 packages/shader-analyzer/src/ShaderValidator.ts:1038-1045,1123-1144,1167-1176 的 graph、site 和 location 都以 name 为 key;但 parser 已在 AST.ts:856,983 解析出精确 FunctionCallGeneric.fnSymbol。例如 vertex 只调用安全的 helper(float),另一个未被调用的 helper(vec2) 内含 dFdx,当前图仍会把 derivative 传播到 vertex 并阻断 codegen;不同 overload 之间也能拼出不存在的 mutual-recursion cycle。请以 FnSymbol / FunctionDefinition identity 为唯一 owner,删除 name-only graph 和二次按名字解析。

  5. [P1] arithmetic validator 只比较 primitive family,漏掉同 family 的非法 shape。 ShaderValidator.ts:606-620lf === rf 时直接返回,因此 vec2 + vec3mat2 + vec2 等不会产生 diagnostic;TypeSystem.arithmeticResultType 又把它们降为 TypeAny,后续没有任何约束,最终由 WebGL 才拒绝。per-operator compatibility/result 应由 TypeSystem 单一拥有,parser inference 与 validator 共同消费;删除 _arithmeticFamily 这套较弱的平行规则,并补 shape mismatch 反例。

  6. [P1] 非 void 的裸 return; 被当作有效 value return,fragment rewrite 会生成损坏 GLSL。 packages/shader-parser/src/parser/AST.ts:190-194,827-833 对任意 return 记录 returnStatementShaderValidator.ts:883-887,995-1015 又把裸 return 当作保证返回且不报 InvalidReturnType。随后 GLES100.ts:43-50 / GLES300.ts:100-108children[1] 当 Expression 重写;vec4 frag(){ return; } 因而可能生成 gl_FragColor = ;;。请以 AST 子节点形状作为是否有返回值的唯一事实:非 void 裸 return 必须报错,只有 children.length === 3 才能成为 fragment value-return clue,删除“任意 RETURN 都可记录”的分支。

  7. [P1] 数值 branch implication 在等值边界把 strict/inclusive 方向写反。 packages/shader-parser/src/common/BaseToken.ts:622-637 使用 actual.inclusive || !required.inclusive;正确的集合蕴含关系应为 required.inclusive || !actual.inclusive。当前 MODE > 1 错误地不蕴含 MODE >= 1,而 MODE >= 1 又错误地蕴含 MODE > 1,经 isBranchVisibleFrom 导致前者误报 UseBeforeDeclaration、后者在 MODE == 1 变体漏报。继续保留 BaseToken 的 branch implication engine 为唯一 owner,机械修正两个 bound predicate,并补 strict→inclusive 与 inclusive→strict 双向用例,不要在 SymbolTable 再加补偿逻辑。

  8. [P1] branch reachability 与 built-in IO facts 出现了平行 owner,dead 或不可达代码会改变诊断。 AST.ts:91-103 会跳过 unconditional child,取整棵子树第一个 non-empty branch;无条件函数只要函数体首先出现 #if 0,整个 FunctionDefinition 的 report branch 就可能变成 dead,连真正的 Redefinition 都被 SemanticAnalyzer.ts:84-115 抑制。反向上,ShaderValidator.ts:99-179 遍历所有节点却完全不消费 branch;AST.ts:1111-1117,1812-1819 还把 gl_Position / gl_FragColor 收进全局 range bag,ShaderIOAnalyzer.ts:90-119 不区分 branch、entry stage 或 call reachability。于是 dead/unused helper 的一次写入能掩盖 MissingVertexPosition,dead helper 的 gl_FragColor 也能伪造 MRT 冲突。请保留 lexer token 的 BranchSignature + isBranchReachable 为唯一可达性 owner:先让 TreeNode 机械继承真正首终结符的 branch,再让 validator dispatch 与 IO clue 消费它;IO clue 还应按 FunctionDefinition/stage 归属并从 entry call graph 派生,删除全局 range-only bags。

  9. [P1] 合法 bool literal 被 const-expression classifier 判成非常量。 packages/shader-parser/src/ParserUtils.ts:119-146,169-213 的 leaf 只接受 int/float,而 AST.ts:1176-1186 已把 true/false 正确推断为 bool。结果 const bool enabled = true; 会产生 NonConstInitializer 并在 analyzer 注入时阻断 codegen。请让 ParserUtils.isConstExpr 成为统一 literal classifier,纳入 bool literal,不要另加调用点特判。

  10. [P1] declarator 的 const/initializer 事实在三条 reduction 中重复维护并已漂移。 SingleDeclarationAST.ts:266-332 传播 isConst 且校验 initializer;逗号声明的 InitDeclaratorList:552-585 用默认 isConst=false 创建 symbol,也不校验 initializer;global VariableDeclaration:1668-1726 保存 const,却遗漏 NonConstInitializer。因此 const int A=1,B=2; const int C=B; 会误报 C,float u; const float A=1.0,B=u;const float G=u; 又会漏报。请以 FullySpecifiedType.isConst + 每个 declarator initializer 为唯一事实,抽出统一 normalize/validate owner,删除三份平行的 symbol/validation 逻辑。

验证与非阻塞项

  • [P2] 当前 conformance oracle 会产生系统性 false green。 DiagnosticDriverConsistency.test.ts:873-889 给 compiler 注入同一个 analyzer,error case 在送 WebGL 前就返回;MacroBranchRuntime.test.ts:201-324 又把完整 ShaderLab DSL 直接传给只接受 raw pass GLSL 的 _parseShaderPass,所以 undefined 可能只是第一 token Shader 的 parse failure。请把 analyzer gate、Logger 断言与无 analyzer 的 raw codegen/driver oracle 拆开,并先从 source parser 提取 pass contents/entries。

  • [P2] 新公开包声明了不会生成的 browser entry。 packages/shader-parser/package.json:14packages/shader-analyzer/package.json:14 指向 dist/browser.js,但 rollup.config.js:198-224 只为带 umd 配置的包生成该文件,而两个 manifest 都没有 umd。发布后优先读取 browser field 的消费者会解析到不存在的文件。请让 build output 成为 manifest 的唯一 owner:要么增加真实 UMD output,要么删除虚假的 browser field。

  • [P2] PR 元数据与注释规范需要收口。 该变更新增两个公开 package、ShaderAnalyzer API、Engine 配置与结构化诊断,title 应使用 feat(shader): ... 而不是 refactor(shader): ...。此外新增代码中仍有大量以句号结尾的单行 // 注释,例如 GLESVisitor.ts:44ShaderValidator.ts 多处;请按仓库规范机械清理,保留多行 JSDoc 的句号。

修复时请同时保留现有 Copilot / CodeRabbit 未解决评论;上面没有重复展开它们。

- Block source and include failures before shader code generation.

- Preserve macro branch, function identity, and AST ownership invariants.

- Add regression coverage for review findings.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/shader-analyzer/src/ShaderAnalyzer.ts (1)

121-155: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Deep-clone every pass unconditionally, even when the pass (or a sibling pass) will be discarded.

_analyzePass always calls this._cloneProgram(program) before the caller knows whether analyze() will end up zeroing passes (line 84: if (diagnostics.some(...error)) passes.length = 0;). For shaders with validation errors, every erroring pass — and any earlier successful pass in the same source — pays the full recursive clone cost for a result that's thrown away.

♻️ Defer cloning until after the error check
-  analyze(source: string, options?: AnalyzerOptions): AnalysisResult {
+  analyze(source: string, options?: AnalyzerOptions): AnalysisResult {
     ...
     const diagnostics: Diagnostic[] = [];
-    const passes: AnalyzedPass[] = [];
+    const rawPasses: AnalyzedPass[] = [];
     ...
           const analyzed = this._analyzePass(pass, diagnostics, options?.basePathForIncludeKey);
-          if (analyzed) passes.push(analyzed);
+          if (analyzed) rawPasses.push(analyzed);
     ...
-    if (diagnostics.some((diagnostic) => diagnostic.severity === DiagnosticSeverity.Error)) passes.length = 0;
+    const passes = diagnostics.some((d) => d.severity === DiagnosticSeverity.Error)
+      ? []
+      : rawPasses.map((p) => ({ ...p, program: this._cloneProgram(p.program) }));
     this._logDiagnostics(diagnostics);
     return { diagnostics, passes };
   }

and drop the clone call inside _analyzePass's success return (return the original program).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shader-analyzer/src/ShaderAnalyzer.ts` around lines 121 - 155, Defer
cloning until after analyze() determines the final pass set: update _analyzePass
to return the original program on success, then clone retained passes only after
the diagnostics-based passes.length reset in analyze(). Ensure discarded
erroring passes and sibling passes are not cloned, while successful passes
retained in the final result remain isolated through _cloneProgram.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/shader-analyzer/src/ShaderAnalyzer.ts`:
- Around line 121-155: Defer cloning until after analyze() determines the final
pass set: update _analyzePass to return the original program on success, then
clone retained passes only after the diagnostics-based passes.length reset in
analyze(). Ensure discarded erroring passes and sibling passes are not cloned,
while successful passes retained in the final result remain isolated through
_cloneProgram.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 86510004-d317-4af6-83fb-483aee114b39

📥 Commits

Reviewing files that changed from the base of the PR and between 4e33d49 and 6cb9760.

⛔ Files ignored due to path filters (2)
  • tests/src/shader-compiler/expected/define-struct-access-global.frag.glsl is excluded by !**/*.glsl
  • tests/src/shader-compiler/expected/define-struct-access-global.vert.glsl is excluded by !**/*.glsl
📒 Files selected for processing (26)
  • packages/core/src/Engine.ts
  • packages/core/src/animation/AnimationClip.ts
  • packages/shader-analyzer/package.json
  • packages/shader-analyzer/src/ShaderAnalyzer.ts
  • packages/shader-analyzer/src/ShaderValidator.ts
  • packages/shader-compiler/package.json
  • packages/shader-compiler/src/ShaderCompiler.ts
  • packages/shader-compiler/src/codeGen/GLES300.ts
  • packages/shader-compiler/src/codeGen/VisitorContext.ts
  • packages/shader-parser/package.json
  • packages/shader-parser/src/GSError.ts
  • packages/shader-parser/src/ParserUtils.ts
  • packages/shader-parser/src/Preprocessor.ts
  • packages/shader-parser/src/ShaderCompilerUtils.ts
  • packages/shader-parser/src/common/BaseToken.ts
  • packages/shader-parser/src/parser/AST.ts
  • packages/shader-parser/src/parser/PassParser.ts
  • packages/shader-parser/src/parser/TypeSystem.ts
  • packages/shader-parser/src/sourceParser/ShaderSourceParser.ts
  • packages/shader-parser/src/sourceParser/ShaderSourceParser.y
  • tests/src/shader-analyzer/ReuseAst.test.ts
  • tests/src/shader-analyzer/ReviewRegression.test.ts
  • tests/src/shader-compiler/AnalyzerInjection.test.ts
  • tests/src/shader-compiler/MacroBranchRuntime.test.ts
  • tests/src/shader-compiler/ShaderCompiler.test.ts
  • tests/vitest.config.ts
💤 Files with no reviewable changes (3)
  • packages/shader-parser/package.json
  • packages/shader-analyzer/package.json
  • packages/shader-compiler/package.json
🚧 Files skipped from review as they are similar to previous changes (20)
  • packages/core/src/animation/AnimationClip.ts
  • packages/shader-parser/src/parser/PassParser.ts
  • packages/shader-parser/src/Preprocessor.ts
  • tests/vitest.config.ts
  • tests/src/shader-analyzer/ReuseAst.test.ts
  • packages/core/src/Engine.ts
  • packages/shader-parser/src/GSError.ts
  • packages/shader-compiler/src/codeGen/GLES300.ts
  • packages/shader-parser/src/sourceParser/ShaderSourceParser.y
  • packages/shader-parser/src/ShaderCompilerUtils.ts
  • packages/shader-parser/src/ParserUtils.ts
  • tests/src/shader-compiler/MacroBranchRuntime.test.ts
  • packages/shader-compiler/src/ShaderCompiler.ts
  • packages/shader-parser/src/parser/TypeSystem.ts
  • packages/shader-compiler/src/codeGen/VisitorContext.ts
  • packages/shader-parser/src/common/BaseToken.ts
  • packages/shader-parser/src/sourceParser/ShaderSourceParser.ts
  • packages/shader-analyzer/src/ShaderValidator.ts
  • tests/src/shader-compiler/ShaderCompiler.test.ts
  • packages/shader-parser/src/parser/AST.ts

@GuoLei1990 GuoLei1990 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🫧 尘小沫

结论

Request changes。已按 4e33d492...6cb9760a 审完本轮 28 个增量文件,并重新沿 Shader.create/Engine → compiler/preprocessor → source/pass parser → analyzer/IO → codegen/precompile 各追一层。当前 checks 全绿;本轮已合理关闭 overload call graph、pooled AST lifetime、bare return、bool const、strict bound、cross-stage fallback、missing include 阻断、browser entry 以及 Copilot 指出的 undefined 输出等问题。但 source/diagnostic ownership、可达性和 declarator/type validation 仍存在平行 owner,另有一个会静默复用错误 include 展开结果的新阻塞问题。

阻塞问题

  1. [P1] packages/shader-parser/src/Preprocessor.ts:114-118 — include cache 的 key 少了会改变展开结果的 base path。 _replace 只用解析后的 path 命中 ChunkOutputCache,但 miss 时又用调用方的 basePathForIncludeKey 递归展开 chunk;而 ShaderCompiler.ts:18-19,55-60 的 cache 会跨多个 Shader.create 调用长期复用。于是两个不同目录的 shader 引用同一个 chunk、且该 chunk 内再 #include "./local.glsl" 时,第二个 shader 会直接拿到第一个 shader 的 nested include 内容(或错误),静默编译成错误源码。应让“当前被 include 文件的 canonical URL/path”成为唯一 owner:递归时以该文件 URL 作为下一层 base,使展开只依赖 canonical path,再按 canonical path 缓存;删除“同一 cache entry 仍依赖 root shader base”的双源,并补两个 root path 顺序互换的回归。

  2. [P1] packages/shader-compiler/src/ShaderCompiler.ts:21,37-53 / packages/shader-analyzer/src/ShaderAnalyzer.ts:94-102,140-147 — source diagnostics 仍未进入 parse result,而是新增了跨调用隐藏状态,range/source 协议也仍有两套。 _parseShaderSource 只返回 IShaderSource,却把错误挂到 compiler 实例的 _sourceErrors;因此 _parseShaderPass 的结果取决于“上一次 parse source”,一个 bad source 后直接编译独立 valid pass 也会被无条件拒绝。analyzer 侧又把原始 ShaderLab 的 entry range 配给重建后的 passText,compiler 注入路径则不传 range、退化到 0:0;GLESVisitor 仍第二次运行 ShaderIOAnalyzer 并保留 _softMissEntry 空 stage 路径。请保留 ShaderSourceParser 作为 source error、entry binding 与原始 source mapping 的唯一 owner,返回 typed parse envelope 并让 Shader.create/_precompile/analyzer/codegen 机械消费;删除 _sourceErrors 这条平行状态、_softMissEntry,以及 codegen 对 IO facts 的第二次扫描。

  3. [P1] packages/shader-parser/src/parser/AST.ts:92-104,1115,1818-1825 / ShaderIOAnalyzer.ts:90-118 — branch/reachability 修复只过滤了静态 dead IO token,没有修复控制流 owner。 TreeNode.set 仍会跳过 unconditional child、把任意后代第一个 non-empty branch 当成父节点 branch;ShaderValidator._walk 也仍遍历并诊断不可达节点,所以 vertex 中 #if 0 下的 dFdx 仍会产生阻塞错误。反向上,gl_Position/gl_FragColor 仍是 program-global range bag:未被 entry 调用的 helper 写一次 gl_Position 就能掩盖 MissingVertexPosition,unused helper 的 gl_FragColor 也能伪造 MRT 冲突。请保留 lexer token 的 BranchSignature + isBranchReachable 为唯一 branch owner,让节点机械继承真实首终结符、validator dispatch 跳过不可达节点;IO facts 按 FunctionDefinition/stage 归属并由 entry call graph 派生,删除三个 global range bag。

  4. [P1] packages/shader-parser/src/parser/AST.ts:553-585,1671-1729 — declarator 的 const/initializer/array 事实仍由三条 reduction 分别维护且继续漂移。 逗号声明的 InitDeclaratorList 仍用默认 isConst=false 创建 symbol、完全不校验 initializer,并在 array 分支直接修改共享的 this.typeInfo,会让后续 declarator 继承前一个 declarator 的 array shape;global VariableDeclaration 仍只校验 assignability,不校验 const expression。因此 const int A=1,B=2; const int C=B; 仍会误报,const float A=1.0,B=u; / const float G=u; 仍会漏报,float a,b[2],c; 还会把 c 记成 array。应以 FullySpecifiedType + 每个 declarator 自己的 initializer/arraySpecifier 为唯一 owner,统一 normalize/validate 后再建 VarSymbol,删除三份平行的 symbol/validation 逻辑和共享 SymbolType mutation。

  5. [P1] packages/shader-analyzer/src/ShaderValidator.ts:608-674 / packages/shader-parser/src/parser/TypeSystem.ts:89-113 — arithmetic 修复继续维护第二套 operator/type system。 新增的 _arithmeticFamily/_areArithmeticShapesCompatible 在 validator 解释 operator legality,而 AST inference 仍调用不接收 operator 的 TypeSystem.arithmeticResultType(a,b);例如合法 mat2x3 * mat3x2 在 TypeSystem 变成 TypeAny,非法同型 non-square matrix multiply 又先被推成具体原类型,直到另一个 package 的 validator 才给相反结论。请让 TypeSystem 单一返回 per-operator 的 compatibility/result(含 invalid reason),parser inference 与 validator 共用;删除 validator 里的 family/shape 平行规则,而不是继续靠两边同步 case。

非阻塞项

  • [P2] packages/shader-analyzer/src/ShaderAnalyzer.ts:25-30 的公开 TSDoc 仍承诺 AST “仅在下一次 analyze 前有效”,但本轮已深拷贝整个 program;当前文档会让调用者为不存在的生命周期限制额外复制或避免缓存。请把公开契约改成与实际 ownership 一致。CodeRabbit 已单独指出 error path 的无效深拷贝,这里不重复其性能项。
  • [P2] tests/src/shader-compiler/DiagnosticDriverConsistency.test.ts:873-907 仍给 compiler 注入 analyzer;error case 在 WebGL oracle 前返回,因此无法证明“无 analyzer 的 codegen 与真实 driver 一致”。本轮已修正 MacroBranchRuntime 的 DSL/raw-pass 混用,剩余 oracle 应拆成 analyzer diagnostic、无 analyzer raw codegen、driver 三条独立断言。
  • [P2] PR 元数据与注释规范仍未收口。 该 PR 新增两个公开 package、Analyzer API 和 Engine 配置,title 仍应从 refactor(shader) 改为 feat(shader);本轮新增 ShaderValidator.ts:431-432 等单行 // 仍以句号结尾,新增公开 TypeSystem.matrixDimensions 也没有按同族 public API 补完整多行 TSDoc、@param@returns

- reject custom types missing from reachable macro branches

- cover variable, parameter, return, and struct-member declarations

@GuoLei1990 GuoLei1990 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🫧 尘小沫

结论

Request changes。已按 6cb9760a...3aa34798 审完本轮 1 个 commit / 2 个文件,并沿 Lexer BranchSignature → TypeSpecifier/StructSymbol → SymbolTable coverage → analyzer gate → codegen/driver 各追一层。当前 checks 全绿;本轮没有关闭上一轮开放项,新增的 struct type 校验还复制了一个会错误放行宏变体的 self-guard shortcut。上一轮已关闭的 overload call graph、pooled AST lifetime、bare return、bool const、strict bound、cross-stage fallback、missing include 阻断、browser entry 和 Copilot 的 undefined 输出继续保持关闭,不重复提出。

阻塞问题

  1. [P1] packages/shader-parser/src/parser/AST.ts:442-448 — 任意内层 self-guard 会抹掉外层 feature branch,缺失的 struct 仍被当成必然可见。 例如 struct Data 位于 #ifdef FEATURE 内的标准 #ifndef DATA_INCLUDED / #define DATA_INCLUDED guard 中,而 Data value; 在外部;FEATURE 关闭时预处理结果没有 Data,但 candidate signature 只要含一个 selfGuarding constraint,:447 就绕过整个 canBranchesCoverCallsite 结果,analyzer 放行并把错误推迟到真实 driver。相同整分支 shortcut 还存在于 function/variable consumer(:967:2029),这是同根因的平行 coverage owner。请保留 BaseToken.canBranchesCoverCallsite 作为唯一 branch-coverage owner:只机械消解已证明成立的 self-guard constraint,同时继续检查 signature 中的 FEATURE 等其余约束;删除三个 consumer 的 some(isSelfGuardingBranch) fallback,并补“outer feature off/on + inner canonical guard”的 struct(以及共享 owner 的 function/variable)回归。

  2. [P1] packages/shader-parser/src/Preprocessor.ts:114-118 — include cache 的 key 少了会改变展开结果的 base path。 cache 仅以解析后的 path 命中,miss 时却继续用 root shader 的 basePathForIncludeKey 展开 chunk;同一 compiler cache 跨多次创建复用后,两个目录引用同一 chunk、chunk 再相对 include 时,后一次会静默复用前一个目录的内容。请让当前 include 文件的 canonical URL/path 成为唯一 owner,递归以它作为下一层 base,再按 canonical path 缓存;删除 cache entry 对 root base 的隐式依赖,并补两个 root path 顺序互换的回归。

  3. [P1] packages/shader-compiler/src/ShaderCompiler.ts:21,37-53 / packages/shader-analyzer/src/ShaderAnalyzer.ts:94-102,140-147 — source diagnostics、entry binding 与 source mapping 仍由隐藏状态和平行协议维护。 _parseShaderSource 只返回 IShaderSource,却把错误存入实例级 _sourceErrors,使独立 valid pass 也会受上一次 bad source 污染;standalone analyzer 又把原始 ShaderLab range 配给重建后的 passText,注入路径则没有 range,codegen 仍二次运行 ShaderIOAnalyzer 并保留 _softMissEntry 空 stage。请保留 ShaderSourceParser 为唯一 owner,返回携带 errors、binding source/range 与一次性 IO facts 的 typed envelope;让 compiler/analyzer/codegen 机械消费,删除 _sourceErrors_softMissEntry 和 codegen 的第二次 IO 扫描。

  4. [P1] packages/shader-parser/src/parser/AST.ts:92-104 / packages/shader-analyzer/src/ShaderValidator.ts:100-182 / ShaderIOAnalyzer.ts:90-118 — branch/reachability 与 IO facts 仍有平行 owner。 TreeNode.set 会跳过 unconditional child、把后代首个 non-empty branch 赋给父节点;validator 仍遍历不可达节点,IO 又使用 program-global gl_Position/gl_FragColor range bag。因此 #if 0 内 derivative 仍能阻断 vertex,unused helper 的 gl_Position 能掩盖 MissingVertexPosition,unused gl_FragColor 能伪造 MRT 冲突。请保留 lexer 的 BranchSignature + isBranchReachable 为唯一 owner,让节点继承真实首终结符、validator 跳过不可达节点;IO facts 按 FunctionDefinition/stage 归属并由 entry call graph 派生,删除三个 global range bag。

  5. [P1] packages/shader-parser/src/parser/AST.ts:588-620,1710-1778 — declarator 的 const/initializer/array 事实仍由多条 reduction 分别维护且漂移。 逗号声明仍丢 isConst、不校验 initializer,并直接修改共享 typeInfo.arraySpecifier,使后续 declarator 继承前一个 array shape;global path 仍漏 NonConstInitializer。现状仍会让 const int A=1,B=2; const int C=B; 误报,让 const float A=1.0,B=u; / const float G=u; 漏报,并把 float a,b[2],c;c 记成 array。请以 FullySpecifiedType + 每个 declarator 自己的 initializer/arraySpecifier 为唯一 owner,统一 normalize/validate 后建 VarSymbol,删除三份平行逻辑和共享 SymbolType mutation。

  6. [P1] packages/shader-analyzer/src/ShaderValidator.ts:608-674 / packages/shader-parser/src/parser/TypeSystem.ts:93-113 — operator legality/result 仍维护两套 type system。 validator 的 _arithmeticFamily/_areArithmeticShapesCompatible 接收 operator 并判断 legality,AST inference 却调用不接收 operator 的 arithmeticResultType(a,b);合法 mat2x3 * mat3x2 被降为 TypeAny,非法同型 non-square matrix multiply 又先被推成具体原类型,直到另一个 package 才给相反结论。请让 TypeSystem 单一返回 per-operator compatibility/result(含 invalid reason),parser inference 与 validator 共用,并删除 validator 的平行 family/shape 规则。

非阻塞项

  • [P2] packages/shader-parser/src/parser/AST.ts:425-455,1723,1789-1812 — 同一个 type-reference validation 被 5 个父级 reduction 手工触发,并已产生重复诊断。 TypeSpecifier 本身已经拥有 type token 与 branch,但 SingleDeclaration、FunctionHeader、ParameterDeclarator、StructDeclaration、VariableDeclaration 各自调用 helper;global Data a,b,c; 又为后续 identifier 复用同一个 FullySpecifiedType 构造 synthetic VariableDeclaration,每次都会对同一 type token 再报一次错误。请让 TypeSpecifier.semanticAnalyze 在该 type occurrence 上校验一次,删除 5 个父级调用点;下游 declaration 只消费已解析结果。
  • [P2] packages/shader-analyzer/src/ShaderAnalyzer.ts:25-30 的公开 TSDoc 仍称 AST 只在下一次 analyze 前有效,但返回前已深拷贝 program;请让契约与实际 ownership 一致。
  • [P2] tests/src/shader-compiler/DiagnosticDriverConsistency.test.ts:873-907 仍给 compiler 注入 analyzer,error case 在 WebGL oracle 前返回,不能证明无 analyzer codegen 与真实 driver 一致;请拆成 analyzer diagnostic、无 analyzer raw codegen、driver 三条独立断言。
  • [P2] PR 元数据与注释规范仍未收口。 该 PR 新增两个公开 package、Analyzer API 与 Engine 配置,title 仍应从 refactor(shader) 改为 feat(shader);新增单行 // 仍有句尾句号,TypeSystem.matrixDimensions 也缺同族 public API 的完整多行 TSDoc、@param@returns

shensi.zxd added 3 commits August 4, 2026 16:19
- add backend-neutral IR and core info consumed by GLES backends

- move diagnostics, reachability, IO checks, and macro proofs into analyzer ownership

- preserve runtime codegen with branch, include, type, and shader-library regressions covered
- retain upstream animation, clone, particle, and package changes

- keep verbose parser ownership and remove the obsolete compiler verbose subpackage
- reject missing shader entries before backend generation and reuse neutral entry facts

- compact the default parser artifact without compressing or mangling runtime control flow

- align analyzer/codegen/driver consistency tests with structural compiler failures
@zhuxudong zhuxudong changed the title refactor(shader): split parser/compiler/analyzer + diagnostics + macro analysis refactor(shader): add neutral IR and standalone diagnostics Aug 4, 2026

@GuoLei1990 GuoLei1990 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🫧 尘小沫

结论

Request changes(P1)。已基于 3aa34798137e2b882a5f08b9b940578a4af682fa...8fd6abc06fd3c7e2c39dc48a15ce1b11b24b7682 的 11 个增量 commit 审查实际 diff,并沿 ShaderLab source → preprocess/neutral IR → analyzer/core info → codegen/precompile 各追一层;当前 GitHub checks 全绿。实际 review 动作为 REQUEST_CHANGES,目标 HEAD 为 8fd6abc06fd3c7e2c39dc48a15ce1b11b24b7682。自动 CR 不替代人工 Reviewer 的合入门禁(APPROVE)。

已关闭问题清单

  • include cache 已改为以 canonical include path 展开和缓存,且有双 root 顺序回归(Preprocessor.ts:139-143ReviewRegression.test.ts)。
  • canonical self-guard 不再覆盖外层 feature 约束;struct/function/variable 的 branch coverage 统一由 branch engine 消费,并补了 outer feature 回归(BranchAwareLookup.test.ts:432-550)。
  • 跨调用 _sourceErrors、codegen 二次 IO 扫描与 missing-entry 空 stage 路径已删除;ShaderCoreInfo 产出 backend facts,ShaderAnalysisInfo 产出 analyzer-only call graph/IO facts,8fd6abc 在 backend 前拒绝缺失 entry。
  • 节点可达性、entry call graph 和 IO write facts 已统一为 isBranchReachable + ShaderAnalysisInfo,死分支/未调用 helper 不再伪造 IO 事实。
  • overload call graph、AST lifetime、bare return、bool const、strict comparison bound、cross-stage fallback、browser entry 和 GLES300 的 undefined 输出均已关闭。
  • operator legality/result 已收敛到 TypeSystem.arithmeticOperation,parser inference、普通算术和 compound assignment 共用;逗号 declarator 的 const 传播与 array shape 泄漏也已有回归保护。
  • driver oracle 现在独立调用 compiler,不再向 compiler 注入 analyzer;source mapping 的 include diagnostics 也已按 source map 回写。

问题

  1. [P1] packages/shader-parser/src/parser/AST.ts:316-371,580-660,1697-1737 / packages/shader-analyzer/src/ShaderValidator.ts:333-351 — 全局 const 初始化绕过了已经抽出的 declarator 契约,非法源码不会得到 NonConstInitializer VariableDeclaration 已建立含 isConst/initializer 的 VariableDeclaratorInfo,但只设置 isStaticShaderValidator._checkVariableDeclarator 也只检查 void 和 assignability。相反,局部 SingleDeclarationInitDeclaratorList 仍各自在 parser 中调用 ParserUtils.isConstExpr。所以 float runtimeValue; const float bad = runtimeValue; 在全局既不会被 analyzer 报错,又会被 compiler 作为静态声明输出,最终交给 driver 拒绝。请保留 VariableDeclaratorInfo 为每个 declarator 的唯一事实、ShaderValidator 为唯一诊断 owner:把 const-expression 检查迁到 validator,删除两个 parser-local _validateInitializer 分支,并补 global scalar/array、local/逗号 declarator 的同一矩阵回归;不要为了旧局部测试保留平行 parser validation。

  2. [P1] packages/shader-compiler/src/ShaderCompiler.ts:28-35,112-145 — source parser 的结构性错误仍被 logging-only 路径吞掉,precompile 可把已丢弃的 RenderState 当成成功产物发布。 ShaderSourceParser.parseWithErrors 已是 source structure 和 errors 的权威 owner,但 _parseShaderSource 只逐条 Logger.error 后丢弃 errors_precompile 随即继续序列化 shaderSource。例如 InvalidRenderStateProperty 在 source parser 中明确不写入 constantMap/variableMap,这条路径会得到默认 RenderState 的 precompiled shader,而不是失败;MissingEntry 恰好被下游 entry lookup 拦住,不能代表全部 source error 已封口。请让 _parseShaderSource 传递同一个 typed parse result,_precompile 在 source error 时直接失败,analyzer 仅机械消费同一 envelope 做诊断;删除 compiler 的 logging-only source-error side path,并补 invalid RenderState 和 duplicate entry 的 precompile rejection 测试,不能把失效 fixture 反过来变成 runtime compatibility fallback。

  3. [P2] 公共契约和 PR 元数据仍未收口。 PR title 仍是 refactor(shader): add neutral IR and standalone diagnostics,但实际新增并发布 parser/analyzer public package、standalone diagnostics 与 CLI,应改为 feat(shader): ...。同时 AnalyzerOptions/AnalysisResult 等新增 public surface 仍使用单行 TSDoc(如 ShaderAnalyzer.ts:20-34),偏离仓库的多行 public TSDoc + 参数/返回说明规范;请随公开契约一次性修正,而不要继续把 API 说明分散到实现注释。

架构、熵增与测试治理

本轮 neutral IR 划分本身是净减熵:backend 只消费 ShaderCoreInfo,analyzer-only 的 call graph/reachability/IO facts 留在 ShaderAnalysisInfo,替代了旧的 parser/codegen 双重 IO 判断。仍有两处未完成收口:declarator 的事实已缩为一份,但 const 校验仍由两个 parser reduction 和零个 global validator 分担;source parse errors 已缩为 parseWithErrors 一份,却在 compiler 被再降级为日志副作用。应分别保留 VariableDeclaratorInfo → ShaderValidatorShaderSourceParser.parseWithErrors 两条权威链路,删除局部 parser validation 和 compiler logging-only 分支。现有测试覆盖局部/逗号 const、source-map 及 missing entry,但缺全局 const 和 RenderState/duplicate-entry precompile 失败,无法守住替换后的公开契约。

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 16

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/shader-parser/src/common/PreprocessorCondition.ts (1)

120-130: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

| 0 silently rewrites out-of-range and fractional literals.

scanNumber accepts any finite number and then truncates it with | 0. Two cases change meaning without a diagnostic:

  • #if X == 4294967296 becomes X == 0.
  • #if X == 1.5 becomes X == 1.

The documented contract at Line 31 states that the function throws when an expression cannot be represented by this reasoning model. Truncation contradicts that contract, and the resulting value then drives branch coverage and runtime instruction encoding. Reject the literal instead of reshaping it.

🐛 Proposed fix
   const parsed = Number(value);
-  if (!Number.isFinite(parsed)) throwMalformedPreprocessorCondition(source);
+  // `#if` arithmetic is signed-integer only, so reject anything this layer would have to reshape.
+  if (!Number.isInteger(parsed) || parsed < -2147483648 || parsed > 2147483647) {
+    throwMalformedPreprocessorCondition(source);
+  }
   context.index += value.length;
-  return parsed | 0;
+  return parsed;
 }

If the preprocessor must tolerate such literals instead of rejecting them, keep the truncation and record the wrap-around behavior in the function comment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shader-parser/src/common/PreprocessorCondition.ts` around lines 120
- 130, Update scanNumber so literals that cannot be represented exactly by its
integer reasoning model are rejected rather than coerced: after parsing,
validate that the value is an integer within the supported signed 32-bit range,
and call throwMalformedPreprocessorCondition for fractional or out-of-range
values. Remove the | 0 conversion while preserving normal integer parsing and
index advancement.
packages/shader-parser/src/sourceParser/SourceLexer.ts (1)

154-159: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Stop recovery at } and EOF.

Line 158 always advances after the scan, even when no semicolon exists. If an invalid render-state property is followed by }, recovery consumes the structural braces, advances past EOF, and the next property parse dereferences an undefined token. Return a recovery status at } or EOF, and end the enclosing property or state parse without consuming the delimiter. This preserves the typed diagnostic instead of throwing a TypeError.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shader-parser/src/sourceParser/SourceLexer.ts` around lines 154 -
159, Update SourceLexer.scanToCharacter to stop recovery at either `}` or EOF
and return a status indicating whether the target character was found, without
advancing past those delimiters. Adjust the enclosing render-state/property
parsing flow to honor this status, end parsing safely, and leave `}` or EOF
unconsumed so subsequent token access cannot dereference undefined values.
🧹 Nitpick comments (22)
rollup.config.js (1)

27-29: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Guard the verbose shader-parser push when the package is missing.

pkgs.find returns undefined when @galacean/engine-shader-parser is not in pkgs. Spreading undefined produces { verboseMode: true }, so config() then reads pkgJson.name on undefined and the whole build fails with an opaque TypeError.

The same file already guards optional packages: if (shaderPkg) at Line 251 and if (analyzerPkg) at Line 256. Apply the same guard here.

♻️ Proposed refactor
 const shaderParserPkg = pkgs.find((item) => item.pkgJson.name === "`@galacean/engine-shader-parser`");
-pkgs.push({ ...shaderParserPkg, verboseMode: true });
+if (shaderParserPkg) {
+  pkgs.push({ ...shaderParserPkg, verboseMode: true });
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rollup.config.js` around lines 27 - 29, Guard the `pkgs.push` call for
`shaderParserPkg` with a presence check, matching the existing `shaderPkg` and
`analyzerPkg` guards, so the package is only pushed with `verboseMode: true`
when `@galacean/engine-shader-parser` is found.
packages/shader-analyzer/src/ShaderAnalysisInfo.ts (1)

75-82: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Return a re-iterable Iterable from functions().

functions() captures this._functionsByName.values() once, before returning. A Map values iterator is single-use. The returned object therefore yields the functions on the first for...of and yields nothing on every later iteration of the same value.

The only current caller, ShaderValidator._reportMutualRecursion, iterates once, so behavior is correct today. The declared return type Iterable<ASTNode.FunctionDefinition> promises repeatable iteration, so a second consumer would silently observe an empty sequence.

Make the method a generator so each iteration starts a fresh map iterator.

♻️ Proposed refactor
   /**
    * Returns every parsed function declaration.
    * `@returns` Function identities retained by the neutral IR.
    */
-  functions(): Iterable<ASTNode.FunctionDefinition> {
-    const groups = this._functionsByName.values();
-    return {
-      *[Symbol.iterator]() {
-        for (const functions of groups) yield* functions;
-      }
-    };
-  }
+  *functions(): Generator<ASTNode.FunctionDefinition> {
+    for (const functions of this._functionsByName.values()) yield* functions;
+  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shader-analyzer/src/ShaderAnalysisInfo.ts` around lines 75 - 82,
Update ShaderAnalysisInfo.functions() to be a generator that obtains a fresh
this._functionsByName.values() iterator on each invocation/iteration, rather
than capturing one iterator in the returned object. Preserve the existing
behavior of yielding every function definition across all name groups while
ensuring the returned Iterable can be iterated repeatedly.
packages/shader-parser/src/parser/SemanticAnalyzer.ts (1)

124-128: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use a local redefinition conflict type for reportRedefinition.

SymbolTableStack.insert already declares Exclude<DeclarationCoexistence, "exclusive"> | "none", so SemanticAnalyzer.reportRedefinition should use a local type alias instead of exposing the DeclarationCoexistence union in its signature.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shader-parser/src/parser/SemanticAnalyzer.ts` around lines 124 -
128, Define a local type alias for the redefinition conflict values near
reportRedefinition, matching Exclude<DeclarationCoexistence, "exclusive"> |
"none", and update SemanticAnalyzer.reportRedefinition to accept that alias
instead of exposing the DeclarationCoexistence expression directly. Keep the
behavior and accepted values unchanged.
tests/src/shader-analyzer/ReviewRegression.test.ts (1)

394-395: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Include the diagnostics in the failure message.

Line 395 asserts an empty array without a message. If a diagnostic appears, the failure output shows only a length mismatch. Lines 49 and 66 in this file already pass JSON.stringify(diagnostics) as the assertion message. Apply the same pattern here so a regression names the offending diagnostic.

♻️ Proposed change
     const result = new ShaderAnalyzer().analyze(source);
-    expect(result.diagnostics).to.be.empty;
+    expect(result.diagnostics, JSON.stringify(result.diagnostics)).to.be.empty;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/src/shader-analyzer/ReviewRegression.test.ts` around lines 394 - 395,
Update the diagnostics assertion in ReviewRegression.test.ts to pass
JSON.stringify(result.diagnostics) as its failure message, matching the existing
assertions near lines 49 and 66 so any unexpected diagnostic is shown.
packages/shader-analyzer/src/ShaderAnalyzer.ts (2)

61-79: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the skipSemanticValidation predicate into a named local.

The tenth argument is a multi-line diagnostics.some(...) expression nested inside the call. The reader must decode it before the call itself becomes readable. _analyzePass also takes ten positional parameters, so the argument list is easy to misorder in later edits.

Bind the predicate to a named constant, and consider grouping the invariant parameters into a single context object.

♻️ Proposed refactor of the call site
           const statements = shaderSource.pendingContents.concat(subShader.pendingContents, pass.pendingContents);
+          const hasPreprocessorErrorInStatements = diagnostics.some(
+            (diagnostic) =>
+              diagnostic.code === DiagnosticType.PreprocessorError &&
+              statements.some(
+                (statement) =>
+                  diagnostic.range.start.offset >= statement.range.start.index &&
+                  diagnostic.range.start.offset <= statement.range.end.index
+              )
+          );
           this._analyzePass(
             pass,
             statements,
             source,
             diagnostics,
             includeMap,
             chunkOutputCache,
             options?.basePathForIncludeKey,
             options?.file,
-            diagnostics.some(
-              (diagnostic) =>
-                diagnostic.code === DiagnosticType.PreprocessorError &&
-                statements.some(
-                  (statement) =>
-                    diagnostic.range.start.offset >= statement.range.start.index &&
-                    diagnostic.range.start.offset <= statement.range.end.index
-                )
-            )
+            hasPreprocessorErrorInStatements
           );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shader-analyzer/src/ShaderAnalyzer.ts` around lines 61 - 79, Extract
the final diagnostics.some predicate passed to _analyzePass into a clearly named
local constant, such as skipSemanticValidation, before the call, then pass that
constant as the tenth argument. Preserve the predicate’s existing diagnostic and
statement range conditions; leave broader parameter grouping unchanged unless
needed for this refactor.

165-166: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Do not overwrite diagnostic.file with an undefined segment file.

Line 166 assigns startSegment.file unconditionally. If a segment that originates from the pass itself carries no file, this erases the file that gseErrorToDiagnostic already copied from GSError.file. Line 146 and line 87 later restore a value with ??=, so the result is usually the same, but the intermediate state loses parser-provided attribution.

Assign only when the segment supplies a file.

♻️ Proposed change
   diagnostic.relatedSource = startSegment.source;
-  diagnostic.file = startSegment.file;
+  if (startSegment.file !== undefined) diagnostic.file = startSegment.file;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shader-analyzer/src/ShaderAnalyzer.ts` around lines 165 - 166, The
assignment of diagnostic.file at line 166 unconditionally overwrites the file
value that was previously set by gseErrorToDiagnostic from GSError.file. When
startSegment.file is undefined, this erases the parser-provided attribution.
Update the assignment to only set diagnostic.file when startSegment.file is
defined, preserving the original value from gseErrorToDiagnostic in cases where
the segment carries no file.
packages/shader-analyzer/src/PreprocessorExpressionValidator.ts (2)

225-227: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Throw an Error subclass instead of a bare object.

_fail throws an object literal. The catch in parse then casts anything it catches to ParseFailure. If an unexpected TypeError is raised inside _parseConditional or tokenize, parse returns it as a ParseFailure, and line 57 dereferences failure.token, which is undefined on that value. A dedicated error class lets parse re-throw values it does not own.

♻️ Proposed change
+class ParseFailureError extends Error {
+  constructor(readonly failure: ParseFailure) {
+    super(failure.message);
+  }
+}
+
 class ExpressionParser {
   parse(): ParseFailure | undefined {
     try {
       this._parseConditional();
       const token = this._current();
       if (token.kind !== "end") {
         const certain = token.kind !== "identifier" || token.text === "defined";
         this._fail(`Unexpected token '${token.text}' in preprocessor expression.`, token, certain);
       }
-    } catch (failure) {
-      return failure as ParseFailure;
+    } catch (error) {
+      if (error instanceof ParseFailureError) return error.failure;
+      throw error;
     }
   }
   private _fail(message: string, token: Token, certain: boolean): never {
-    throw { message, token, certain } satisfies ParseFailure;
+    throw new ParseFailureError({ message, token, certain });
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shader-analyzer/src/PreprocessorExpressionValidator.ts` around lines
225 - 227, Update _fail to throw a dedicated Error subclass carrying the
ParseFailure fields, and adjust parse to handle only that subclass as a
validation failure while re-throwing unexpected errors from _parseConditional or
tokenize. Preserve the existing failure message, token, and certain values for
owned parse failures.

278-290: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Share one positionAt helper across the package.

positionAt is duplicated verbatim in packages/shader-analyzer/src/ShaderAnalyzer.ts at lines 229-240. Both copies convert a character offset to a 1-based line and column and both count only \n. Move the function into a shared module and import it in both files. A single copy keeps the line and column convention identical if the newline handling changes later.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shader-analyzer/src/PreprocessorExpressionValidator.ts` around lines
278 - 290, Move the duplicated positionAt helper into a shared package module,
then import and use that single helper from both
PreprocessorExpressionValidator.ts and ShaderAnalyzer.ts. Preserve its current
1-based line/column behavior and newline handling while removing both local
duplicate definitions.
packages/shader-analyzer/package.json (1)

16-23: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move the types condition to the start of the . exports entry.

With node16 or nodenext module resolution, TypeScript preserves package.json exports order and matches the first enabled condition. A runtime condition before types can select ./dist/module.js as the declaration target instead of ./types/index.d.ts.

♻️ Proposed reordering
   "exports": {
     ".": {
+      "types": "./types/index.d.ts",
       "import": "./dist/module.js",
-      "require": "./dist/main.js",
-      "types": "./types/index.d.ts"
+      "require": "./dist/main.js"
     },
     "./package.json": "./package.json"
   },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shader-analyzer/package.json` around lines 16 - 23, Reorder the
conditions in the root "." export so the "types" entry precedes "import" and
"require", while preserving all existing targets and the "./package.json" export
unchanged.
packages/shader-analyzer/src/ShaderIOValidator.ts (1)

198-211: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider reusing a shared zero position constant.

_entryNotFound builds a new object literal cast to ShaderPosition on every missing entry. A module-level frozen fallback keeps the diagnostic location shape in one place and avoids the inline cast.

♻️ Proposed refactor
 export class ShaderIOValidator {
   private static readonly _lookup = new SymbolInfo("", null);
+  private static readonly _originPosition = <ShaderPosition>{ index: 0, line: 0, column: 0 };
-      location ?? <ShaderPosition>{ index: 0, line: 0, column: 0 },
+      location ?? this._originPosition,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shader-analyzer/src/ShaderIOValidator.ts` around lines 198 - 211,
Update ShaderIOValidator._entryNotFound to use a shared module-level frozen
zero-position ShaderPosition constant instead of constructing and casting an
inline fallback object. Keep the existing location value when provided and
preserve the current default index, line, and column values.
tests/src/shader-analyzer/BranchAwareLookup.test.ts (3)

450-453: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert an exact diagnostic count.

expect(errors.length).to.be.greaterThan(0) passes on any number of UseBeforeDeclaration errors. The shader references data.value, guardedValue, and guardedHelper(), so the expected count is knowable. An unrelated regression that adds or removes one error would not fail this test.

💚 Proposed change
-    expect(errors.length).to.be.greaterThan(0);
+    expect(errors).to.have.lengthOf(3);

Confirm the actual count before pinning it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/src/shader-analyzer/BranchAwareLookup.test.ts` around lines 450 - 453,
Update the assertion in the BranchAwareLookup test to require the exact number
of UseBeforeDeclaration diagnostics produced by references to data.value,
guardedValue, and guardedHelper(). First confirm the current diagnostic count,
then replace the greaterThan(0) check with an exact-count assertion while
preserving the existing filtering criteria.

357-374: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use the shared analyzer instance.

This test constructs new ShaderAnalyzer() at Line 372 while every other test in the file uses the module-level analyzer and the errorsOf helper. If the fresh instance is deliberate, state why in a comment. If it is not, use errorsOf for consistency.

♻️ Proposed change
-    const result = new ShaderAnalyzer().analyze(src);
-    expect(result.diagnostics.filter((diagnostic) => diagnostic.code === "UseBeforeDeclaration")).to.have.lengthOf(1);
+    expect(errorsOf(src, "UseBeforeDeclaration")).to.have.lengthOf(1);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/src/shader-analyzer/BranchAwareLookup.test.ts` around lines 357 - 374,
Update the test around the module-level analyzer to use the shared analyzer
instance and the existing errorsOf helper when asserting the
UseBeforeDeclaration diagnostic; only retain a fresh ShaderAnalyzer construction
if it is deliberate and document the reason inline.

337-355: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Record that this case has no real coverage gap.

MODE <= 0, MODE == 1, and MODE >= 2 together cover every integer, so branchValue is always declared. The test asserts one warning, which means the solver returns "unknown" rather than "covered". The interval solver can refute the counterexample here, but canCandidateSetCoverCallsite does not prove the positive direction across three candidates, so coverage stays unresolved.

The current classification is the safe one. Add a comment so a future improvement that turns this warning into "covered" reads as an intended change and not as a regression.

♻️ Proposed comment
+  // These three ranges are exhaustive over the integers, so no real gap exists. The solver only
+  // refutes the counterexample and does not prove positive coverage across three candidates, so
+  // it degrades to `unknown` and warns. Update this expectation if coverage proving improves.
   it("does not report an integer-only coverage gap as an error", () => {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/src/shader-analyzer/BranchAwareLookup.test.ts` around lines 337 - 355,
Add an explanatory comment to the test case in “does not report an integer-only
coverage gap as an error,” documenting that MODE <= 0, MODE == 1, and MODE >= 2
cover all integers, while the solver currently leaves coverage unknown and
therefore intentionally emits one warning. Keep the existing assertions
unchanged so future classification as covered is recognized as an intended
improvement.
packages/shader-parser/src/common/BaseToken.ts (1)

354-368: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the greedy counterexample search as incomplete.

The loop picks the first negation that stays satisfiable and never backtracks. When a different negation choice would exclude a later candidate, the function returns false and getBranchCoverage degrades to "unknown". That direction is safe, because the analyzer then warns instead of failing the build. Add one sentence to the function so a future reader does not treat false as proof that no counterexample exists.

♻️ Proposed comment
+/**
+ * Search for one macro configuration that reaches the callsite and excludes every candidate.
+ *
+ * The search is greedy and never backtracks over the choice of negated condition, so `false`
+ * means "no counterexample was found", not "no counterexample exists". Callers therefore map
+ * `false` to `unknown` coverage rather than to proven coverage.
+ */
 function hasAtomicCoverageCounterexample(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shader-parser/src/common/BaseToken.ts` around lines 354 - 368, Add a
single sentence to the function containing the greedy candidate loop,
documenting that its first-satisfiable-negation search does not backtrack and
therefore a false result is not proof that no counterexample exists. Do not
change the loop or related coverage behavior.
packages/shader-parser/src/lalr/LALR1.ts (1)

66-71: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Guard symbolByOffset against undefined instead of truthiness.

Token derives from the Keyword enum, and Keyword.CONST = 0. A grammar item such as type_qualifier → CONST . SEMICOLON would make item.symbolByOffset(1) return Keyword.CONST, while nextSymbol is falsy, so _extendStateItem skips adding the SEMICOLON lookahead. Use nextSymbol !== undefined so zero-valued terminals still participate in the lookahead scan.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shader-parser/src/lalr/LALR1.ts` around lines 66 - 71, In the for
loop that calls item.symbolByOffset, change the loop condition from checking
nextSymbol for truthiness to explicitly checking nextSymbol !== undefined. This
ensures that zero-valued terminals like Keyword.CONST (which equals 0) are not
skipped due to being falsy, allowing the lookahead scan to correctly process all
symbols including those at position 0 in the grammar.
tests/src/shader-compiler/StateIsolation.test.ts (2)

24-25: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Correct the comment: a missing entry now throws.

_generate throws Vertex entry function 'vert' not found. for this fixture. _parseShaderPass catches the error, calls Logger.error, and returns undefined. The path is no longer a soft return, and the finally block is what restores processingPassText. Update the comment so it describes the throw-and-catch path this test actually exercises.

♻️ Proposed comment fix
-// Missing entries take the soft-return path; compiling it must not leak visitor state.
+// Missing entries make `_generate` throw; `_parseShaderPass` catches, logs, and returns
+// undefined. That path must not leak visitor state.
 const broken = `struct Attributes { vec3 POSITION; }; void notAnEntry() {}`;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/src/shader-compiler/StateIsolation.test.ts` around lines 24 - 25,
Update the comment above the broken shader fixture to describe that compiling a
shader without the required vertex entry causes _generate to throw,
_parseShaderPass to catch and log the error, and the finally block to restore
processingPassText without leaking visitor state.

41-49: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert that the failed compile reports the error.

The test proves the degraded compile returns undefined, but not that the failure is reported. Logger.error is a noop until logging is enabled, so a silent regression in the catch block would pass. Spy on Logger.error and assert one call, as StandaloneAnalyzer.test.ts does at Line 63.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/src/shader-compiler/StateIsolation.test.ts` around lines 41 - 49,
Update the degraded-compile test around ShaderCompiler and compile so it spies
on Logger.error before compiling broken, then asserts it was called exactly once
while preserving the existing undefined-result and subsequent valid-compile
assertions; follow the established spy pattern in StandaloneAnalyzer.test.ts.
packages/shader-compiler/src/ShaderCompiler.ts (1)

87-96: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make the pass source an explicit parameter of generate.

generate reads ShaderCompilerUtils.processingPassText, which _parseShaderPass sets and clears in its finally block. Any caller that invokes generate outside an active _parseShaderPass call receives "" as the IR source, so ShaderClueIR.source becomes empty and error locations lose their text. The public method also silently depends on hidden global state.

Add a source parameter, or keep the method internal and document the required call order.

♻️ Proposed signature change
   generate(
     program: ASTNode.GLShaderProgram,
     vertexEntry: string,
     fragmentEntry: string,
-    backend: ShaderLanguage
+    backend: ShaderLanguage,
+    passSource = ShaderCompilerUtils.processingPassText ?? ""
   ): IShaderProgramSource {
-    const ir = new ShaderClueIR(program, ShaderCompilerUtils.processingPassText ?? "");
+    const ir = new ShaderClueIR(program, passSource);
     const coreInfo = ShaderCoreInfo.create(ir, vertexEntry, fragmentEntry);
     return this._generate(ir, coreInfo, backend);
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shader-compiler/src/ShaderCompiler.ts` around lines 87 - 96, Update
ShaderCompiler.generate to accept an explicit source parameter and pass it
directly to ShaderClueIR instead of reading
ShaderCompilerUtils.processingPassText. Update every generate caller, including
the _parseShaderPass flow, to provide the pass source while preserving existing
backend and entry-point behavior.
tests/src/shader-compiler/MacroBranchRuntime.test.ts (1)

200-215: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Two tests assert the same diagnostic set but describe different causes.

Both tests expect exactly ["UseBeforeDeclaration"]. Neither asserts a diagnostic that identifies the non-complementary gap or the repeated condition. If the analyzer is expected to report only the use-before-declaration effect, state that in the test names. If a dedicated diagnostic is planned, add the assertion once it exists.

Also applies to: 217-232

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/src/shader-compiler/MacroBranchRuntime.test.ts` around lines 200 - 215,
Clarify the names of the tests around the non-complementary `#ifndef/`#elif gap
and repeated condition so they explicitly describe the only expected
UseBeforeDeclaration diagnostic. Keep the exact diagnostic assertions and
successful code-generation checks unchanged unless a dedicated gap or
repeated-condition diagnostic is implemented, in which case assert it once
rather than duplicating expectations.
tests/src/shader-compiler/PreprocessorConditionConformance.test.ts (1)

300-301: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The assertion locks in a fast-parser limitation.

Line 301 requires parsePreprocessorCondition to throw for every expression in this table, including ((A == B || A == C)), which is plain parenthesized logic. If the fast parser later supports nested parentheses, this test fails even though behavior improved. Assert the end-to-end result only, or move the "unsupported by the fast parser" expectation into a separate table that is easy to update.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/src/shader-compiler/PreprocessorConditionConformance.test.ts` around
lines 300 - 301, Update the test around parsePreprocessorCondition so it no
longer requires the fast parser to throw for every expression in the conformance
table, especially valid nested-parentheses logic such as ((A == B || A == C)).
Assert the expected end-to-end codegen/WebGL result, and isolate any
intentionally unsupported fast-parser cases in a separate maintainable table.
packages/shader-compiler/src/codeGen/GLESVisitor.ts (1)

108-108: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update the comment that names the removed visitShaderProgram method.

visitShaderProgram no longer exists; generate replaced it. The comment on Line 108 and the comment on Line 116 both still point readers to that method. Name generate and ShaderCoreInfo instead.

♻️ Proposed comment fix
-    // MRT structs were collected in visitShaderProgram; here only mark the fragment return statements
+    // MRT structs were collected in `ShaderCoreInfo`; here only mark the fragment return statements

Apply the same correction outside this range at Line 116:

// Both stage struct-var maps are already populated in `generate`; just
// pre-walk macro refs so struct codegen sees the references.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shader-compiler/src/codeGen/GLESVisitor.ts` at line 108, Update the
comments near the fragment return handling to reference the current generate
method instead of the removed visitShaderProgram method, and mention
ShaderCoreInfo where appropriate. Apply the same correction to the nearby
comment at line 116, preserving the existing description of struct-map
population and macro-reference pre-walking.
packages/shader-parser/src/parser/PassParser.ts (1)

12-19: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Align the summary line with the returned type.

Line 13 states the function parses a pass "into an AST". The function returns ShaderClueIR, and the @returns tag on Line 18 already says "Neutral IR". Update the summary so the doc for this new public export is consistent.

♻️ Proposed doc fix
-/**
- * Parses one shader pass into an AST and parse-stage diagnostics.
+/**
+ * Parses one shader pass into neutral IR and parse-stage diagnostics.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shader-parser/src/parser/PassParser.ts` around lines 12 - 19, Update
the summary line of the parser function’s doc comment to say it parses the
shader pass into neutral IR rather than an AST, matching the returned
ShaderClueIR type and existing `@returns` description.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/shader/ShaderMacroProcessor.ts`:
- Around line 342-368: Update ShaderMacroProcessor._evalRawCondition to catch
exceptions thrown during PreprocessorExpressionEvaluator evaluation, including
failures from malformed raw expressions or expansion, and return false so the
raw condition is treated as unsatisfied. Keep successful evaluations unchanged.

In `@packages/shader-analyzer/src/cli.ts`:
- Around line 82-93: Update readIncludeMap and its visit traversal to read only
files with supported shader-chunk extensions, skipping unrelated assets before
readFileSync and includeMap insertion. Preserve the existing .git and
node_modules directory exclusions, and explicitly retain the current behavior of
ignoring symbolic-link entries unless the intended behavior requires otherwise.
- Around line 1-5: The cli.ts source file requires a shebang to run as an
executable binary, but it cannot be added to the TypeScript source code since
`#!/usr/bin/env node` is not valid syntax. Update the build process or build
configuration to prepend the shebang line to the compiled dist/cli.js output
file after TypeScript compilation completes. Ensure the shebang appears as the
first line of the final emitted JavaScript file, before any existing import
statements.

In `@packages/shader-analyzer/src/PreprocessorExpressionValidator.ts`:
- Around line 132-143: Update PreprocessorExpressionValidator.parse() to track
whether _parseConditional() encountered an expandable identifier, and mark the
trailing-token failure as uncertain when that flag is set, including leftover
'(' from function-like macro invocations. Preserve the existing certainty
behavior for other unexpected tokens, and add a test covering a function-like
macro used inside `#if`.

In `@packages/shader-analyzer/src/ShaderAnalyzer.ts`:
- Around line 50-53: Move the validatePreprocessorExpressions and
ShaderCompilerUtils.clearAllShaderCompilerObjectPool calls inside analyze’s
existing try block so exceptions from either pre-parse step are converted into
diagnostics by the established catch paths. Preserve their current order and
behavior.

In `@packages/shader-compiler/package.json`:
- Around line 34-39: The `./verbose` export is pointing to the same dist/main.js
and dist/module.js files as the non-verbose export, which means it does not
actually provide a verbose build since the Rollup configuration builds those
artifacts with `_VERBOSE: false`. Update the `./verbose` export to reference
separate verbose-specific build artifacts (such as dist/main.verbose.js and
dist/module.verbose.js), and reorder the export map properties so that `types`
appears first, before `import` and `require`. Ensure the Rollup configuration
includes a second output that builds these verbose artifacts with the verbose
diagnostics flag enabled.

In `@packages/shader-compiler/rollup.config.js`:
- Line 71: Update the node-resolve configuration in the Rollup setup to include
“module” and “main” after “debug” in mainFields, preserving debug as the
preferred entry while restoring fallback resolution for dependencies without a
debug field. Leave exportConditions unchanged.

In `@packages/shader-parser/package.json`:
- Around line 14-37: The debug export conditions reference src files
(src/runtime.ts in the root export and src/index.ts in the ./verbose export) but
the files array does not include src/**/*, preventing npm from publishing these
debug entry points for consumers. Either add src/**/* to the files array to
publish source files, or update both debug export paths to point to published
dist artifacts (following the same pattern as the import and require conditions)
to ensure the exports remain resolvable after publication.

In `@packages/shader-parser/src/ir/ShaderCoreInfo.ts`:
- Around line 83-87: Update the struct-role derivation flow around
removeRoleConflicts and deriveStructVariableRoles so struct types removed as
conflicts are also excluded from structRoles and the
vertexStructVarMap/fragmentStructVarMap results. Pass or otherwise reuse the
conflict information when deriving roles, ensuring
CodeGenVisitor.visitPostfixExpression cannot select a role for a conflicted
struct while preserving role derivation for non-conflicting structs.

In `@packages/shader-parser/src/lexer/Lexer.ts`:
- Around line 246-261: Update _tokenizeForCodegen’s MACRO_ELIF, MACRO_ELSE, and
corresponding MACRO_ENDIF cleanup to propagate the existing constant-false
condition into every later arm once the branch stack records a true `#if` or
preceding `#elif`. Keep conditionalArm advancement intact and mirror the verbose
path’s static-dead handling so definitions in unreachable codegen arms remain
excluded.

In `@packages/shader-parser/src/parser/AST.ts`:
- Around line 1817-1821: In _collectIdentifierRefs, replace the early return
inside the references loop for macro-defined names with continue so only that
reference is skipped. Preserve processing of later non-macro references within
the same MacroCallSymbol or MacroCallFunction, matching the codegen path
behavior.

In `@packages/shader-parser/src/Preprocessor.ts`:
- Around line 139-143: Add an active-include tracking set to the recursive
expansion flow around _expand, checking each path before recursion and reporting
a preprocessing diagnostic when it already exists on the current include path.
Mark the key before calling _expand, and remove it after expansion completes so
only active recursion is guarded while normal cache reuse remains unchanged.

In `@tests/src/shader-analyzer/MacroBranchMatrix.test.ts`:
- Around line 23-41: Reset the shared ShaderCompiler/parser state at the start
of compile before invoking ShaderAnalyzer.analyze or
ShaderCompiler._parseShaderPass, including mutable state such as
ShaderCompilerUtils.processingPassText and any parsed-state singleton used by
these paths. Ensure each test case starts from a clean state without changing
the returned codes or fragment behavior.

In `@tests/src/shader-analyzer/PreprocessorExpressionDiagnostics.test.ts`:
- Around line 76-77: Update the diagnostic severity filters in
PreprocessorExpressionDiagnostics.test.ts and BuiltinShaderSmoke.test.ts to
compare against DiagnosticSeverity.Error rather than the string literal "error",
ensuring the assertions inspect actual error diagnostics.

In `@tests/src/shader-compiler/PrecompileABTest.test.ts`:
- Around line 321-330: Strengthen the test case around validatePrecompiledWebGL
for overlapping Particle render-mode macros by evaluating the generated vertex
instructions and asserting the expected priority winner’s output. Also verify
that instructions from the competing render-mode branches are absent, so a
priority reversal cannot pass merely because the shader remains valid.

In `@tests/src/shader-compiler/PreprocessorConditionConformance.test.ts`:
- Around line 292-295: The test expectations for the unsigned integer comparison
cases do not match the actual behavior of ShaderMacroProcessor, which evaluates
these expressions using signed semantics. Update the expected boolean values in
the test table: change the expectation for "-1 < 1u" from true to false, and for
"0xffffffffu > 0u" from false to true, to align with the signed comparison
behavior implemented in ShaderMacroProcessor's _compareValues and
evaluateBinaryExpression methods.

---

Outside diff comments:
In `@packages/shader-parser/src/common/PreprocessorCondition.ts`:
- Around line 120-130: Update scanNumber so literals that cannot be represented
exactly by its integer reasoning model are rejected rather than coerced: after
parsing, validate that the value is an integer within the supported signed
32-bit range, and call throwMalformedPreprocessorCondition for fractional or
out-of-range values. Remove the | 0 conversion while preserving normal integer
parsing and index advancement.

In `@packages/shader-parser/src/sourceParser/SourceLexer.ts`:
- Around line 154-159: Update SourceLexer.scanToCharacter to stop recovery at
either `}` or EOF and return a status indicating whether the target character
was found, without advancing past those delimiters. Adjust the enclosing
render-state/property parsing flow to honor this status, end parsing safely, and
leave `}` or EOF unconsumed so subsequent token access cannot dereference
undefined values.

---

Nitpick comments:
In `@packages/shader-analyzer/package.json`:
- Around line 16-23: Reorder the conditions in the root "." export so the
"types" entry precedes "import" and "require", while preserving all existing
targets and the "./package.json" export unchanged.

In `@packages/shader-analyzer/src/PreprocessorExpressionValidator.ts`:
- Around line 225-227: Update _fail to throw a dedicated Error subclass carrying
the ParseFailure fields, and adjust parse to handle only that subclass as a
validation failure while re-throwing unexpected errors from _parseConditional or
tokenize. Preserve the existing failure message, token, and certain values for
owned parse failures.
- Around line 278-290: Move the duplicated positionAt helper into a shared
package module, then import and use that single helper from both
PreprocessorExpressionValidator.ts and ShaderAnalyzer.ts. Preserve its current
1-based line/column behavior and newline handling while removing both local
duplicate definitions.

In `@packages/shader-analyzer/src/ShaderAnalysisInfo.ts`:
- Around line 75-82: Update ShaderAnalysisInfo.functions() to be a generator
that obtains a fresh this._functionsByName.values() iterator on each
invocation/iteration, rather than capturing one iterator in the returned object.
Preserve the existing behavior of yielding every function definition across all
name groups while ensuring the returned Iterable can be iterated repeatedly.

In `@packages/shader-analyzer/src/ShaderAnalyzer.ts`:
- Around line 61-79: Extract the final diagnostics.some predicate passed to
_analyzePass into a clearly named local constant, such as
skipSemanticValidation, before the call, then pass that constant as the tenth
argument. Preserve the predicate’s existing diagnostic and statement range
conditions; leave broader parameter grouping unchanged unless needed for this
refactor.
- Around line 165-166: The assignment of diagnostic.file at line 166
unconditionally overwrites the file value that was previously set by
gseErrorToDiagnostic from GSError.file. When startSegment.file is undefined,
this erases the parser-provided attribution. Update the assignment to only set
diagnostic.file when startSegment.file is defined, preserving the original value
from gseErrorToDiagnostic in cases where the segment carries no file.

In `@packages/shader-analyzer/src/ShaderIOValidator.ts`:
- Around line 198-211: Update ShaderIOValidator._entryNotFound to use a shared
module-level frozen zero-position ShaderPosition constant instead of
constructing and casting an inline fallback object. Keep the existing location
value when provided and preserve the current default index, line, and column
values.

In `@packages/shader-compiler/src/codeGen/GLESVisitor.ts`:
- Line 108: Update the comments near the fragment return handling to reference
the current generate method instead of the removed visitShaderProgram method,
and mention ShaderCoreInfo where appropriate. Apply the same correction to the
nearby comment at line 116, preserving the existing description of struct-map
population and macro-reference pre-walking.

In `@packages/shader-compiler/src/ShaderCompiler.ts`:
- Around line 87-96: Update ShaderCompiler.generate to accept an explicit source
parameter and pass it directly to ShaderClueIR instead of reading
ShaderCompilerUtils.processingPassText. Update every generate caller, including
the _parseShaderPass flow, to provide the pass source while preserving existing
backend and entry-point behavior.

In `@packages/shader-parser/src/common/BaseToken.ts`:
- Around line 354-368: Add a single sentence to the function containing the
greedy candidate loop, documenting that its first-satisfiable-negation search
does not backtrack and therefore a false result is not proof that no
counterexample exists. Do not change the loop or related coverage behavior.

In `@packages/shader-parser/src/lalr/LALR1.ts`:
- Around line 66-71: In the for loop that calls item.symbolByOffset, change the
loop condition from checking nextSymbol for truthiness to explicitly checking
nextSymbol !== undefined. This ensures that zero-valued terminals like
Keyword.CONST (which equals 0) are not skipped due to being falsy, allowing the
lookahead scan to correctly process all symbols including those at position 0 in
the grammar.

In `@packages/shader-parser/src/parser/PassParser.ts`:
- Around line 12-19: Update the summary line of the parser function’s doc
comment to say it parses the shader pass into neutral IR rather than an AST,
matching the returned ShaderClueIR type and existing `@returns` description.

In `@packages/shader-parser/src/parser/SemanticAnalyzer.ts`:
- Around line 124-128: Define a local type alias for the redefinition conflict
values near reportRedefinition, matching Exclude<DeclarationCoexistence,
"exclusive"> | "none", and update SemanticAnalyzer.reportRedefinition to accept
that alias instead of exposing the DeclarationCoexistence expression directly.
Keep the behavior and accepted values unchanged.

In `@rollup.config.js`:
- Around line 27-29: Guard the `pkgs.push` call for `shaderParserPkg` with a
presence check, matching the existing `shaderPkg` and `analyzerPkg` guards, so
the package is only pushed with `verboseMode: true` when
`@galacean/engine-shader-parser` is found.

In `@tests/src/shader-analyzer/BranchAwareLookup.test.ts`:
- Around line 450-453: Update the assertion in the BranchAwareLookup test to
require the exact number of UseBeforeDeclaration diagnostics produced by
references to data.value, guardedValue, and guardedHelper(). First confirm the
current diagnostic count, then replace the greaterThan(0) check with an
exact-count assertion while preserving the existing filtering criteria.
- Around line 357-374: Update the test around the module-level analyzer to use
the shared analyzer instance and the existing errorsOf helper when asserting the
UseBeforeDeclaration diagnostic; only retain a fresh ShaderAnalyzer construction
if it is deliberate and document the reason inline.
- Around line 337-355: Add an explanatory comment to the test case in “does not
report an integer-only coverage gap as an error,” documenting that MODE <= 0,
MODE == 1, and MODE >= 2 cover all integers, while the solver currently leaves
coverage unknown and therefore intentionally emits one warning. Keep the
existing assertions unchanged so future classification as covered is recognized
as an intended improvement.

In `@tests/src/shader-analyzer/ReviewRegression.test.ts`:
- Around line 394-395: Update the diagnostics assertion in
ReviewRegression.test.ts to pass JSON.stringify(result.diagnostics) as its
failure message, matching the existing assertions near lines 49 and 66 so any
unexpected diagnostic is shown.

In `@tests/src/shader-compiler/MacroBranchRuntime.test.ts`:
- Around line 200-215: Clarify the names of the tests around the
non-complementary `#ifndef/`#elif gap and repeated condition so they explicitly
describe the only expected UseBeforeDeclaration diagnostic. Keep the exact
diagnostic assertions and successful code-generation checks unchanged unless a
dedicated gap or repeated-condition diagnostic is implemented, in which case
assert it once rather than duplicating expectations.

In `@tests/src/shader-compiler/PreprocessorConditionConformance.test.ts`:
- Around line 300-301: Update the test around parsePreprocessorCondition so it
no longer requires the fast parser to throw for every expression in the
conformance table, especially valid nested-parentheses logic such as ((A == B ||
A == C)). Assert the expected end-to-end codegen/WebGL result, and isolate any
intentionally unsupported fast-parser cases in a separate maintainable table.

In `@tests/src/shader-compiler/StateIsolation.test.ts`:
- Around line 24-25: Update the comment above the broken shader fixture to
describe that compiling a shader without the required vertex entry causes
_generate to throw, _parseShaderPass to catch and log the error, and the finally
block to restore processingPassText without leaking visitor state.
- Around line 41-49: Update the degraded-compile test around ShaderCompiler and
compile so it spies on Logger.error before compiling broken, then asserts it was
called exactly once while preserving the existing undefined-result and
subsequent valid-compile assertions; follow the established spy pattern in
StandaloneAnalyzer.test.ts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 29ee22de-acfe-4d0b-a307-84c80e858704

📥 Commits

Reviewing files that changed from the base of the PR and between 3aa3479 and 8fd6abc.

⛔ Files ignored due to path filters (22)
  • packages/shader/src/ShaderLibrary/Common/Fog.glsl is excluded by !**/*.glsl
  • packages/shader/src/ShaderLibrary/Lighting/AmbientOcclusion/BilateralBlur.glsl is excluded by !**/*.glsl
  • packages/shader/src/ShaderLibrary/Lighting/AmbientOcclusion/ScalableAmbientOcclusion.glsl is excluded by !**/*.glsl
  • packages/shader/src/ShaderLibrary/Particle/ParticleVert.glsl is excluded by !**/*.glsl
  • packages/shader/src/Shaders/Effect/Particle.shader is excluded by !**/*.shader
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • tests/src/shader-compiler/shaders/define-comment-with-dot.shader is excluded by !**/*.shader
  • tests/src/shader-compiler/shaders/define-elif-polarity.shader is excluded by !**/*.shader
  • tests/src/shader-compiler/shaders/define-in-comment-repro.shader is excluded by !**/*.shader
  • tests/src/shader-compiler/shaders/define-line-continuation-member-access.shader is excluded by !**/*.shader
  • tests/src/shader-compiler/shaders/define-line-continuation-repro.shader is excluded by !**/*.shader
  • tests/src/shader-compiler/shaders/define-mixed-form-repro.shader is excluded by !**/*.shader
  • tests/src/shader-compiler/shaders/define-multiline-params.shader is excluded by !**/*.shader
  • tests/src/shader-compiler/shaders/digit-ending-id-repro.shader is excluded by !**/*.shader
  • tests/src/shader-compiler/shaders/macro-author-error-trailing-comma.shader is excluded by !**/*.shader
  • tests/src/shader-compiler/shaders/macro-author-error-unbalanced-paren.shader is excluded by !**/*.shader
  • tests/src/shader-compiler/shaders/macro-member-access-builtin-arg.shader is excluded by !**/*.shader
  • tests/src/shader-compiler/shaders/macro-token-fragment-trailing-comma.shader is excluded by !**/*.shader
  • tests/src/shader-compiler/shaders/macro-token-fragment-unbalanced-bracket.shader is excluded by !**/*.shader
  • tests/src/shader-compiler/shaders/macro-token-fragment-unbalanced-paren.shader is excluded by !**/*.shader
  • tests/src/shader-compiler/shaders/macro-value-refs-with-comments.shader is excluded by !**/*.shader
  • tests/src/shader-compiler/shaders/macro-value-refs.shader is excluded by !**/*.shader
📒 Files selected for processing (83)
  • examples/package.json
  • examples/src/shader-playground.ts
  • packages/core/src/Engine.ts
  • packages/core/src/shader/ShaderMacroProcessor.ts
  • packages/design/src/shader-compiler/ICondition.ts
  • packages/design/src/shader-compiler/index.ts
  • packages/shader-analyzer/package.json
  • packages/shader-analyzer/src/Diagnostic.ts
  • packages/shader-analyzer/src/DiagnosticCategory.ts
  • packages/shader-analyzer/src/DiagnosticType.ts
  • packages/shader-analyzer/src/PreprocessorExpressionValidator.ts
  • packages/shader-analyzer/src/ShaderAnalysisInfo.ts
  • packages/shader-analyzer/src/ShaderAnalyzer.ts
  • packages/shader-analyzer/src/ShaderIOValidator.ts
  • packages/shader-analyzer/src/ShaderValidator.ts
  • packages/shader-analyzer/src/cli.ts
  • packages/shader-analyzer/src/convert.ts
  • packages/shader-analyzer/src/index.ts
  • packages/shader-compiler/package.json
  • packages/shader-compiler/rollup.config.js
  • packages/shader-compiler/src/ShaderBackend.ts
  • packages/shader-compiler/src/ShaderCompiler.ts
  • packages/shader-compiler/src/ShaderInstructionEncoder.ts
  • packages/shader-compiler/src/codeGen/CodeGenVisitor.ts
  • packages/shader-compiler/src/codeGen/GLES300.ts
  • packages/shader-compiler/src/codeGen/GLESVisitor.ts
  • packages/shader-compiler/src/codeGen/VisitorContext.ts
  • packages/shader-parser/package.json
  • packages/shader-parser/src/GSError.ts
  • packages/shader-parser/src/ParserUtils.ts
  • packages/shader-parser/src/Preprocessor.ts
  • packages/shader-parser/src/ShaderCompilerUtils.ts
  • packages/shader-parser/src/common/BaseLexer.ts
  • packages/shader-parser/src/common/BaseToken.ts
  • packages/shader-parser/src/common/PreprocessorCondition.ts
  • packages/shader-parser/src/common/ShaderPosition.ts
  • packages/shader-parser/src/common/SymbolTable.ts
  • packages/shader-parser/src/common/SymbolTableStack.ts
  • packages/shader-parser/src/index.ts
  • packages/shader-parser/src/ir/ShaderClueIR.ts
  • packages/shader-parser/src/ir/ShaderCoreInfo.ts
  • packages/shader-parser/src/ir/index.ts
  • packages/shader-parser/src/lalr/CFG.ts
  • packages/shader-parser/src/lalr/LALR1.ts
  • packages/shader-parser/src/lalr/StateItem.ts
  • packages/shader-parser/src/lalr/Utils.ts
  • packages/shader-parser/src/lexer/Lexer.ts
  • packages/shader-parser/src/parser/AST.ts
  • packages/shader-parser/src/parser/PassParser.ts
  • packages/shader-parser/src/parser/SemanticAnalyzer.ts
  • packages/shader-parser/src/parser/ShaderInfo.ts
  • packages/shader-parser/src/parser/ShaderTargetParser.ts
  • packages/shader-parser/src/parser/TargetParser.y
  • packages/shader-parser/src/parser/TypeSystem.ts
  • packages/shader-parser/src/runtime.ts
  • packages/shader-parser/src/sourceParser/ShaderSourceParser.ts
  • packages/shader-parser/src/sourceParser/SourceLexer.ts
  • packages/shader-parser/src/sourceParser/index.ts
  • packages/shader-parser/verbose/package.json
  • rollup.config.js
  • tests/package.json
  • tests/src/shader-analyzer/BranchAwareLookup.test.ts
  • tests/src/shader-analyzer/BranchDeclarationConflict.test.ts
  • tests/src/shader-analyzer/BranchResolutionAmbiguity.test.ts
  • tests/src/shader-analyzer/BuiltinShaderSmoke.test.ts
  • tests/src/shader-analyzer/DiagnosticCoverage.test.ts
  • tests/src/shader-analyzer/MacroBranchMatrix.test.ts
  • tests/src/shader-analyzer/PreprocessorExpressionDiagnostics.test.ts
  • tests/src/shader-analyzer/ReviewRegression.test.ts
  • tests/src/shader-analyzer/ShaderAnalyzer.test.ts
  • tests/src/shader-analyzer/ShaderIOAnalyzer.test.ts
  • tests/src/shader-analyzer/ShaderPlayground.test.ts
  • tests/src/shader-compiler/DiagnosticDriverConsistency.test.ts
  • tests/src/shader-compiler/MacroBranchRuntime.test.ts
  • tests/src/shader-compiler/Precompile.test.ts
  • tests/src/shader-compiler/PrecompileABTest.test.ts
  • tests/src/shader-compiler/PreprocessorConditionConformance.test.ts
  • tests/src/shader-compiler/ReturnStatementInvariant.test.ts
  • tests/src/shader-compiler/ShaderCompiler.test.ts
  • tests/src/shader-compiler/ShaderNeutralIR.test.ts
  • tests/src/shader-compiler/StandaloneAnalyzer.test.ts
  • tests/src/shader-compiler/StateIsolation.test.ts
  • tests/vitest.config.ts
💤 Files with no reviewable changes (1)
  • packages/shader-parser/src/parser/ShaderInfo.ts
🚧 Files skipped from review as they are similar to previous changes (18)
  • examples/package.json
  • packages/shader-parser/src/index.ts
  • packages/shader-parser/src/sourceParser/index.ts
  • tests/vitest.config.ts
  • packages/shader-analyzer/src/Diagnostic.ts
  • tests/src/shader-compiler/ReturnStatementInvariant.test.ts
  • packages/shader-compiler/src/codeGen/GLES300.ts
  • packages/shader-analyzer/src/DiagnosticCategory.ts
  • tests/package.json
  • packages/shader-parser/src/parser/ShaderTargetParser.ts
  • tests/src/shader-analyzer/ShaderIOAnalyzer.test.ts
  • packages/shader-analyzer/src/index.ts
  • tests/src/shader-compiler/DiagnosticDriverConsistency.test.ts
  • packages/shader-parser/src/ParserUtils.ts
  • packages/shader-compiler/src/codeGen/VisitorContext.ts
  • tests/src/shader-compiler/Precompile.test.ts
  • tests/src/shader-analyzer/BranchResolutionAmbiguity.test.ts
  • tests/src/shader-analyzer/ShaderAnalyzer.test.ts

Comment on lines +342 to +368
case "raw":
return ShaderMacroProcessor._evalRawCondition(cond.e, valueMacros, funcMacros);
}
}

private static _evalRawCondition(
expression: string,
valueMacros: Map<string, string>,
funcMacros: Map<string, FuncMacro>
): boolean {
const withDefinedValues = expression.replace(
/\bdefined\s*(?:\(\s*([A-Za-z_]\w*)\s*\)|([A-Za-z_]\w*))/g,
(_match, parenthesized: string | undefined, bare: string | undefined) => {
const name = parenthesized ?? bare!;
return valueMacros.has(name) || funcMacros.has(name) ? "1" : "0";
}
);
const expandedNames = ShaderMacroProcessor._expandedNames;
expandedNames.clear();
const expanded = ShaderMacroProcessor._recursiveExpandMacro(
withDefinedValues,
valueMacros,
funcMacros,
expandedNames
);
return new PreprocessorExpressionEvaluator(expanded).evaluate() !== 0;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Contain evaluator exceptions inside _evalRawCondition.

PreprocessorExpressionEvaluator.evaluate() throws for invalid syntax, unterminated comments, unknown operators, and division by zero. _evalRawCondition propagates that throw through _evalCondition and out of ShaderMacroProcessor.evaluate, which runs on the runtime shader-variant path.

ShaderInstructionEncoder._parseCondition (packages/shader-compiler/src/ShaderInstructionEncoder.ts, lines 161-167) produces { t: "raw", e: expression } exactly when parsePreprocessorCondition already failed. The raw payload therefore carries the expressions most likely to be malformed. A shader containing #if 1 + or #if X / 0 now aborts runtime compilation instead of resolving the branch as unsatisfied.

Catch the failure at this boundary and return a defined result.

🛡️ Proposed fix to contain the throw
   private static _evalRawCondition(
     expression: string,
     valueMacros: Map<string, string>,
     funcMacros: Map<string, FuncMacro>
   ): boolean {
     const withDefinedValues = expression.replace(
       /\bdefined\s*(?:\(\s*([A-Za-z_]\w*)\s*\)|([A-Za-z_]\w*))/g,
       (_match, parenthesized: string | undefined, bare: string | undefined) => {
         const name = parenthesized ?? bare!;
         return valueMacros.has(name) || funcMacros.has(name) ? "1" : "0";
       }
     );
     const expandedNames = ShaderMacroProcessor._expandedNames;
     expandedNames.clear();
     const expanded = ShaderMacroProcessor._recursiveExpandMacro(
       withDefinedValues,
       valueMacros,
       funcMacros,
       expandedNames
     );
-    return new PreprocessorExpressionEvaluator(expanded).evaluate() !== 0;
+    try {
+      return new PreprocessorExpressionEvaluator(expanded).evaluate() !== 0;
+    } catch {
+      // A malformed or unevaluable `#if` expression resolves as unsatisfied so one bad
+      // directive cannot abort variant generation for the whole shader.
+      return false;
+    }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
case "raw":
return ShaderMacroProcessor._evalRawCondition(cond.e, valueMacros, funcMacros);
}
}
private static _evalRawCondition(
expression: string,
valueMacros: Map<string, string>,
funcMacros: Map<string, FuncMacro>
): boolean {
const withDefinedValues = expression.replace(
/\bdefined\s*(?:\(\s*([A-Za-z_]\w*)\s*\)|([A-Za-z_]\w*))/g,
(_match, parenthesized: string | undefined, bare: string | undefined) => {
const name = parenthesized ?? bare!;
return valueMacros.has(name) || funcMacros.has(name) ? "1" : "0";
}
);
const expandedNames = ShaderMacroProcessor._expandedNames;
expandedNames.clear();
const expanded = ShaderMacroProcessor._recursiveExpandMacro(
withDefinedValues,
valueMacros,
funcMacros,
expandedNames
);
return new PreprocessorExpressionEvaluator(expanded).evaluate() !== 0;
}
case "raw":
return ShaderMacroProcessor._evalRawCondition(cond.e, valueMacros, funcMacros);
}
}
private static _evalRawCondition(
expression: string,
valueMacros: Map<string, string>,
funcMacros: Map<string, FuncMacro>
): boolean {
const withDefinedValues = expression.replace(
/\bdefined\s*(?:\(\s*([A-Za-z_]\w*)\s*\)|([A-Za-z_]\w*))/g,
(_match, parenthesized: string | undefined, bare: string | undefined) => {
const name = parenthesized ?? bare!;
return valueMacros.has(name) || funcMacros.has(name) ? "1" : "0";
}
);
const expandedNames = ShaderMacroProcessor._expandedNames;
expandedNames.clear();
const expanded = ShaderMacroProcessor._recursiveExpandMacro(
withDefinedValues,
valueMacros,
funcMacros,
expandedNames
);
try {
return new PreprocessorExpressionEvaluator(expanded).evaluate() !== 0;
} catch {
// A malformed or unevaluable `#if` expression resolves as unsatisfied so one bad
// directive cannot abort variant generation for the whole shader.
return false;
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/shader/ShaderMacroProcessor.ts` around lines 342 - 368,
Update ShaderMacroProcessor._evalRawCondition to catch exceptions thrown during
PreprocessorExpressionEvaluator evaluation, including failures from malformed
raw expressions or expansion, and return false so the raw condition is treated
as unsatisfied. Keep successful evaluations unchanged.

Comment thread packages/shader-analyzer/src/cli.ts Outdated
Comment on lines +1 to +5
import { readFileSync, readdirSync } from "node:fs";
import { dirname, join, relative, resolve, sep } from "node:path";
import type { IncludeMap } from "@galacean/engine-shader-parser/verbose";
import { ShaderAnalyzer } from "./ShaderAnalyzer";
import { DiagnosticSeverity, formatDiagnostic } from "./Diagnostic";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm no shebang is present in the CLI source and inspect the analyzer build scripts.
fd -t f 'cli.ts' packages/shader-analyzer --exec head -n 3 {}
echo '--- package.json ---'
fd -t f 'package.json' packages/shader-analyzer -d 2 --exec jq '{name, bin, scripts, files}' {}
echo '--- existing shebang conventions in the repo ---'
rg -n --glob '!node_modules' '#!/usr/bin/env node' packages | head -20

Repository: galacean/engine

Length of output: 754


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- packages/shader-analyzer files ---'
git ls-files 'packages/shader-analyzer/*' | sed -n '1,120p'

echo '--- package manifests mentioning shader-analyzer or bin entries ---'
rg -n --glob 'package.json' '"`@galacean/engine-shader-analyzer`"|galacean-shader-analyzer|./dist/cli\.js|bin"|compilerOptions|transform|rollup|esbuild|vite|tsx|node' packages -g 'package.json' | sed -n '1,200p'

echo '--- CLI related tracked files ---'
fd -t f 'cli' packages/shader-analyzer
fd -t f 'rollup.config|tsconfig|vitest|vite' packages/shader-analyzer packages -g 'rollup.config.*' -g 'tsconfig*.json' -g '*.{mjs,ts,js}' | sed -n '1,120p'

Repository: galacean/engine

Length of output: 2084


🌐 Web query:

Node.js CLI executable shebang POSIX npm bin file #!/usr/bin/env node npx

💡 Result:

To create a Node.js CLI tool that runs correctly across different environments, you must combine the correct package.json configuration with a specific shebang line in your entry file [1][2][3]. The Shebang Line Every executable Node.js file must begin with the shebang line #!/usr/bin/env node [1][4][5]. This line tells POSIX-compliant operating systems (like Linux and macOS) to use the env command to locate the node executable in the user's PATH [6][7]. This is superior to hard-coding a path (e.g., #!/usr/local/bin/node) because the node installation location varies across systems [4][2][8]. How npm Handles CLI Executables When you distribute your CLI package via npm, the bin field in your package.json tells npm which files should be installed as executables [1][3]. 1. POSIX Systems: npm creates a symbolic link from your specified file to a global bin directory (or a local.bin folder for project dependencies), allowing the script to be run by the name defined in the bin object [1][3]. 2. Windows: Because Windows does not natively support shebang lines, npm automatically generates a.cmd shim file alongside your executable [9][10][3]. This shim re-invokes node against your script [3]. Critically, npm reads your file's shebang line to decide how to construct this shim [9][3]. Failing to include the shebang can cause installation issues or failure to execute on Windows [1][10]. Package.json Configuration The bin field maps your command name to the path of the entry file [1][2]: { "bin": { "my-cli-command": "./bin/cli.js" } } If you have only one executable and want it to share the package name, you can simplify it to a string: "bin": "./bin/cli.js" [1]. Important Considerations - Execution Permissions: On POSIX systems, you must ensure the file is marked as executable (e.g., chmod +x bin/cli.js), though npm handles this automatically during global installation [4][6][3]. - npx Usage: The npx tool uses the same bin configuration as npm [10]. When you run npx my-cli-command, npx downloads the package (if not present) and executes the linked binary defined in the bin field [3][11]. - Node.js Behavior: Node.js itself is designed to ignore the shebang line if the file is executed directly with the node command (e.g., node cli.js), as the # character starts a comment in many languages (though not natively in JS, the runtime handles it gracefully) [6][8]. Top Results: [1], [4], [10], [3]

Citations:


Add a shebang and build-time prep before publishing the CLI binary.

bin.galacean-shader-analyzer points to dist/cli.js, but cli.ts starts with an import statement and tsc does not add #!/usr/bin/env node. Running npx galacean-shader-analyzer will be attempted as a shell script without an interpreter, and the missing shebang also causes npm to skip the Windows .cmd shim.

Prepend the shebang to the emitted or bundled entry file. A #! line is not valid TypeScript input, so add it in the build step instead of in src/cli.ts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shader-analyzer/src/cli.ts` around lines 1 - 5, The cli.ts source
file requires a shebang to run as an executable binary, but it cannot be added
to the TypeScript source code since `#!/usr/bin/env node` is not valid syntax.
Update the build process or build configuration to prepend the shebang line to
the compiled dist/cli.js output file after TypeScript compilation completes.
Ensure the shebang appears as the first line of the final emitted JavaScript
file, before any existing import statements.

Comment thread packages/shader-analyzer/src/cli.ts Outdated
Comment on lines +82 to +93
function readIncludeMap(root: string): IncludeMap {
const includeMap: Record<string, string> = {};
const visit = (directory: string): void => {
for (const entry of readdirSync(directory, { withFileTypes: true })) {
const path = join(directory, entry.name);
if (entry.isDirectory() && entry.name !== ".git" && entry.name !== "node_modules") visit(path);
else if (entry.isFile()) includeMap[toIncludeKey(relative(root, path))] = readFileSync(path, "utf8");
}
};
visit(root);
return includeMap;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Filter include files by extension before reading them.

visit reads every regular file under --include-root into memory as a UTF-8 string. It excludes only .git and node_modules. If an operator points --include-root at a project directory that also holds textures, models, or archives, the CLI decodes all of them into the include map. Include chunks are text shader fragments, so every non-shader file is wasted memory, and a large asset tree can exhaust the heap before any analysis begins.

Restrict the walk to shader chunk extensions.

♻️ Proposed fix
+const INCLUDE_EXTENSIONS = [".glsl", ".shader", ".frag", ".vert", ".chunk"];
+
 function readIncludeMap(root: string): IncludeMap {
   const includeMap: Record<string, string> = {};
   const visit = (directory: string): void => {
     for (const entry of readdirSync(directory, { withFileTypes: true })) {
       const path = join(directory, entry.name);
       if (entry.isDirectory() && entry.name !== ".git" && entry.name !== "node_modules") visit(path);
-      else if (entry.isFile()) includeMap[toIncludeKey(relative(root, path))] = readFileSync(path, "utf8");
+      else if (entry.isFile() && INCLUDE_EXTENSIONS.some((extension) => entry.name.endsWith(extension))) {
+        includeMap[toIncludeKey(relative(root, path))] = readFileSync(path, "utf8");
+      }
     }
   };
   visit(root);
   return includeMap;
 }

Note also that readdirSync with withFileTypes does not follow symbolic links, so entry.isDirectory() and entry.isFile() are both false for a link. Include chunks reached through a symlink are skipped without a message. Confirm whether that matches the intended behavior.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function readIncludeMap(root: string): IncludeMap {
const includeMap: Record<string, string> = {};
const visit = (directory: string): void => {
for (const entry of readdirSync(directory, { withFileTypes: true })) {
const path = join(directory, entry.name);
if (entry.isDirectory() && entry.name !== ".git" && entry.name !== "node_modules") visit(path);
else if (entry.isFile()) includeMap[toIncludeKey(relative(root, path))] = readFileSync(path, "utf8");
}
};
visit(root);
return includeMap;
}
const INCLUDE_EXTENSIONS = [".glsl", ".shader", ".frag", ".vert", ".chunk"];
function readIncludeMap(root: string): IncludeMap {
const includeMap: Record<string, string> = {};
const visit = (directory: string): void => {
for (const entry of readdirSync(directory, { withFileTypes: true })) {
const path = join(directory, entry.name);
if (entry.isDirectory() && entry.name !== ".git" && entry.name !== "node_modules") visit(path);
else if (entry.isFile() && INCLUDE_EXTENSIONS.some((extension) => entry.name.endsWith(extension))) {
includeMap[toIncludeKey(relative(root, path))] = readFileSync(path, "utf8");
}
}
};
visit(root);
return includeMap;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shader-analyzer/src/cli.ts` around lines 82 - 93, Update
readIncludeMap and its visit traversal to read only files with supported
shader-chunk extensions, skipping unrelated assets before readFileSync and
includeMap insertion. Preserve the existing .git and node_modules directory
exclusions, and explicitly retain the current behavior of ignoring symbolic-link
entries unless the intended behavior requires otherwise.

Comment thread packages/shader-analyzer/src/PreprocessorExpressionValidator.ts
Comment thread packages/shader-analyzer/src/ShaderAnalyzer.ts Outdated
Comment thread packages/shader-parser/src/Preprocessor.ts
Comment thread tests/src/shader-analyzer/MacroBranchMatrix.test.ts
Comment thread tests/src/shader-analyzer/PreprocessorExpressionDiagnostics.test.ts Outdated
Comment thread tests/src/shader-compiler/PrecompileABTest.test.ts
Comment on lines +292 to +295
["(~MASK & 0xffu) != 0", [["MASK", "255"]], false],
["0xffffffffu + 1u == 0u", [], true],
["-1 < 1u", [], true],
["0xffffffffu > 0u", [], false],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect how the macro processor evaluates numeric literals and comparisons.
fd -t f 'ShaderMacroProcessor.ts' packages/core/src --exec cat -n

Repository: galacean/engine

Length of output: 29074


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and inspect the conformance test around the referenced expectations and native-check handling.
fd -t f 'PreprocessorConditionConformance.test.ts' tests --exec sh -c '
  echo "--- $1 ---"
  sed -n "240,325p" "$1"
' sh {}

# Inspect all relevant files sharing the processor name for completeness.
fd -t f 'ShaderMacroProcessor.ts' . --exec sh -c 'echo "--- $1 ==="; wc -l "$1"' sh {}

Repository: galacean/engine

Length of output: 3640


Fix the unsigned comparison test expectations.

ShaderMacroProcessor evaluates integer literals as signed values and uses signed comparisons in _compareValues and evaluateBinaryExpression. For -1 < 1u and 0xffffffffu > 0u, the table currently expects true and false, but native preprocessor semantics match the table labels: -1 < 1u is false and 0xffffffffu > 0u is true. Update these rows to false/true, or exclude them from native checking if signed semantics are intentional.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/src/shader-compiler/PreprocessorConditionConformance.test.ts` around
lines 292 - 295, The test expectations for the unsigned integer comparison cases
do not match the actual behavior of ShaderMacroProcessor, which evaluates these
expressions using signed semantics. Update the expected boolean values in the
test table: change the expectation for "-1 < 1u" from true to false, and for
"0xffffffffu > 0u" from false to true, to align with the signed comparison
behavior implemented in ShaderMacroProcessor's _compareValues and
evaluateBinaryExpression methods.

@GuoLei1990 GuoLei1990 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🫧 尘小沫

结论

Request changes(P1)。已基于 8fd6abc...3aabb46 的 2 个增量 commit 审查实际 diff,并沿 parser package public boundary → compiler/analyzer consumers → npm resolver,以及 ShaderLab source parser → precompile 各追一层;当前 GitHub checks 全绿。实际 review 动作为 REQUEST_CHANGES,目标 HEAD 为 3aabb46。自动 CR 不替代人工 Reviewer 的合入门禁(APPROVE)。

已关闭问题清单

  • include cache 已按 canonical include path 展开、递归也以当前 include 的 canonical path 为 base,并有双 root 顺序回归。
  • canonical self-guard 不再覆盖外层 feature 约束,struct/function/variable 统一由 branch engine 消费,并已覆盖 outer feature。
  • analyzer-only 的 call graph、reachability 与 IO facts 已收敛到 ShaderAnalysisInfo;死分支和未调用 helper 不再伪造 stage IO。
  • overload call graph、pooled AST lifetime、bare return、bool const、strict comparison bound、cross-stage fallback、browser entry 与 GLES300 的 undefined 输出均已修复。
  • arithmetic legality/result 已收敛到 TypeSystem.arithmeticOperation;逗号 declarator 的 const 传播和 array shape 泄漏已有回归保护。
  • driver oracle 已独立调用 compiler,include diagnostics 已通过 source map 回写。
  • 本轮 EmptyStruct 已由真实的宏空 member 场景触发,替换此前不可达的 gap fixture。

问题

  1. [P1] packages/shader-parser/src/parser/AST.ts:316-371,580-660,1709-1737 / packages/shader-analyzer/src/ShaderValidator.ts:333-351 — 全局 const 初始化仍绕过 declarator 契约,非法源码不会产生 NonConstInitializer。 VariableDeclaratorInfo 已携带 isConst 与 initializer,但 VariableDeclaration 只注册 symbol/isStatic;ShaderValidator 只校验 void 和 assignability。相反,局部 SingleDeclaration 与 InitDeclaratorList 仍各自在 parser 中调用 ParserUtils.isConstExpr。因此 float runtimeValue; const float bad = runtimeValue; 在全局不会得到诊断,却会被当成 static 声明继续 codegen,最后才交给 driver。请保留 VariableDeclaratorInfo 为每个 declarator 的唯一事实、ShaderValidator 为唯一诊断 owner:将 const-expression 检查迁到 validator,删除两个 parser-local initializer 校验分支,并补 global scalar/array、local/逗号 declarator 的同一矩阵回归。

  2. [P1] packages/shader-compiler/src/ShaderCompiler.ts:34-40,122-168 — source parser 的结构性错误仍被 logging-only 路径吞掉,precompile 可将被丢弃的 RenderState 作为成功产物发布。 ShaderSourceParser.parseWithErrors 已是 shaderSource 与 errors 的权威 owner,但 _parseShaderSource 逐条 Logger.error 后仅返回 IShaderSource,_precompile 随即序列化它。InvalidRenderStateProperty 明确不会写入 render-state map,因而这条路径可发布默认 RenderState;仅靠下游 MissingEntry 阻断并不能封住其他 source errors。请传递同一个 typed parse envelope,_precompile 在 source errors 时直接失败,analyzer 机械消费它做诊断;删除 compiler logging-only side path,并覆盖 invalid RenderState 与 duplicate entry 的 precompile rejection。

  3. [P1] packages/shader-parser/package.json:12-29 / internal/package.json:1-5 / internal/verbose/package.json:1-5 — 新的 internal boundary 仍保留根包的 legacy resolver 入口,公开契约随 resolver 分叉。 exports 已只声明 ./internal 与 ./internal/verbose,但根 package 仍把 main/module/debug/types 指向同一份 runtime artifact,而 src/runtime.ts 导出全部 parser internals。moduleResolution=node 和忽略 exports 的 legacy bundler 会继续接受裸包导入;遵循 exports 的 Node 则拒绝它,形成同一源码在工具链间一边可编译或加载、一边运行失败的第二协议。请保留 root exports 作为公开路径 owner,并保留 internal 目录 manifests 仅供其 legacy 子路径解析;删除根 main/module/debug/types fallback,使旧裸包 tests/fixtures 按新 internal 契约改写,补 pack 后 root import 拒绝而两个 internal path 可解析的边界回归。

  4. [P2] 公共契约文档与 PR 元数据仍未收口。 PR title 仍为 refactor(shader): add neutral IR and standalone diagnostics,实际新增并发布 parser/analyzer 公共 package、standalone diagnostics 与 CLI,应改为 feat(shader): ...。此外新增/修改的公共 surface 仍有单行 TSDoc,例如 ShaderIncludeMap、AnalyzerOptions、AnalysisResult(ShaderAnalyzer.ts:19-35)、ShaderCompiler(ShaderCompiler.ts:17)和 version(index.ts:7-8)。请按仓库多行公共 TSDoc 规范连同 param/returns 一次性收口,不要把公开契约说明继续分散在实现注释中。

架构、熵增与测试治理

本轮 package boundary 的方向正确:parser internals 应只由 compiler/analyzer 通过 internal 子路径消费,AnalyzerOptions 已把外露的 include map 收敛为 ShaderIncludeMap。可是 exports 的单一公开 owner 又被根 main/module/debug/types fallback 复制为第二条 legacy 协议;删除根 fallback 后的数据流应为消费者 → exports → internal artifact,classic resolver 仅经 internal 目录 manifest 机械解析。另一条 source→precompile 链路仍把 parseWithErrors 的唯一 errors 事实降级为日志副作用,而 declarator 的唯一事实又被三处 parser 校验和 validator 分割;应分别收敛为 parse envelope → compiler/analyzer 与 VariableDeclaratorInfo → ShaderValidator。新 EmptyStruct 测试正确删除了不可达 gap 的思路;但仍缺 global const、source error rejection 和 packed package resolution 的新契约回归。不得为了任何旧裸包测试保留 root compatibility fallback。

- Centralize const diagnostics and reject structural source parse errors before precompile.

- Strip analyzer-only paths from runtime artifacts and enforce parser package boundaries.

- Cover macro, include, source mapping, package, and artifact regressions.
@zhuxudong zhuxudong changed the title refactor(shader): add neutral IR and standalone diagnostics feat(shader): add neutral IR and standalone diagnostics Aug 4, 2026
- Execute the active npm CLI through Node so pack verification works across platforms.

@GuoLei1990 GuoLei1990 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🫧 尘小沫

结论

阻塞级别:无新增 P0/P1/P2。已基于 3aabb46591b2d94f3f4fab64ad5261e8683faf94...5c593e0b97a63b66f4aa32dd075358f2f3544008 的 2 个增量 commit 完成审查,并沿 ShaderLab source parser → compiler/precompile/Shader.create、declarator facts → validator,以及 parser package exports → runtime/analyzer consumers 各追一层;GitHub checks 全绿。实际 review 动作为 COMMENTED,目标 HEAD 为 5c593e0b97a63b66f4aa32dd075358f2f3544008。自动 CR 不替代人工 Reviewer 的合入门禁(APPROVE)。

已关闭问题清单

  • const initializer 已由 VariableDeclaratorInfo → ShaderValidator 单链路校验,parser-local 的重复校验已删除;补充了 global、local-array 和 comma declarator 回归(a12d346)。
  • ShaderLab 结构性错误现在由 parse result 驱动 compiler/precompile 直接失败,invalid RenderState 与 duplicate entry 不会再发布局部 precompile 产物(a12d346)。
  • parser root 的 legacy resolver fallback 已移除,公开入口收敛为 ./internal./internal/verbose;package verifier 覆盖打包内容、root 拒绝和子路径解析,Windows 也已接入(a12d3465c593e0)。
  • canonical include cache、include cycle、canonical self-guard coverage、reachable IO/call graph、overload identity、AST lifetime、bare return、operator type owner 与 runtime artifact gate 均已按此前结论闭环,并保留对应回归。
  • PR 标题已调整为 feat(shader),新增 public TSDoc 与 analyzer/runtime 边界说明也已收口。

架构、熵增与测试治理

本轮将 source-structure errors 的权威 owner 固定为 ShaderSourceParser.parseWithErrors:上游产生同一 parse result,下游 compiler 以 _requireValidShaderSource 机械阻断 Shader.create/_precompile,不再把 errors 降级为日志或以 partial render state 继续序列化。declarator 的 const/initializer 事实保持在 VariableDeclaratorInfo,validator 是唯一诊断 owner;parser runtime 仅保留 codegen 所需事实,verbose parser 供 analyzer 消费。package root exports 是唯一公开协议,internal manifests 仅服务其对应兼容解析。

这些改动删除了 source-error side path、parser-local const 校验、root resolver fallback 和 runtime analyzer surface,没有新增镜像状态或 compatibility branch。新增回归覆盖 source failure、macro/cycle、artifact/package boundary 和 Windows verifier;旧测试已改为消费 parse envelope 或新 internal 契约,未为旧测试保留生产 fallback。

- Remove _VERBOSE/jscc dual builds.

- Add explicit runtime and analyzer package entries.

- Keep proofs and diagnostics analyzer-only without runtime regressions.

- Add package, artifact, CLI, macro, and regression gates.

@GuoLei1990 GuoLei1990 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🫧 尘小沫

结论

阻塞级别:无新增 P0/P1;本轮仅发现 1 个 P2 注释规范问题。已基于 5c593e0b97a63b66f4aa32dd075358f2f3544008...f1504b0f90e5e9f9b8cead93b15df0b03da70bf1 的 1 个增量 commit 完成审查,并沿 parser 的 runtime/analyzer 入口 → compiler/analyzer 消费端 → 打包产物与 package resolver 各追一层;GitHub build、lint、E2E 和 coverage checks 均为成功。实际 review 动作为 COMMENTED,目标 HEAD 为 f1504b0f90e5e9f9b8cead93b15df0b03da70bf1。自动 CR 不替代人工 Reviewer 的合入门禁(APPROVE)。

已关闭问题清单

  • const initializer 已固定为 VariableDeclaratorInfo → ShaderValidator 的单一事实/诊断链,global、local array 和 comma declarator 回归已覆盖。
  • ShaderLab 结构性 source errors 已由 ShaderSourceParser.parseWithErrors 直接阻断 compiler/precompile,invalid RenderState 和 duplicate entry 不再发布局部产物。
  • parser 根包 legacy resolver fallback 已移除,公开入口收敛为 internal 子路径;此前的 packed resolver、Windows 和 artifact 边界回归继续有效。
  • canonical include cache、include-cycle/self-guard、branch reachability、IO/call graph、overload identity、AST lifetime、bare return、operator type owner 及 runtime artifact gate 均已按此前结论闭环。
  • PR 标题和新增 public TSDoc 已按上一轮要求收口;本轮将旧 internal/verbose 路径机械替换为 internal/analyzer,未恢复旧兼容入口。

问题

  1. [P2] tests/src/shader-compiler/ShaderCompiler.test.ts:361-362 — 本提交改写的两行 // 注释仍以句号结尾,偏离仓库“单行注释不加句号”的规范。 请把这两行合并或去掉末尾句号;不影响行为,但应随本次注释改动一并收口。

架构、熵增与测试治理

本轮将 runtime 链路保持为 Lexer → ShaderTargetParser.create() → ShaderCompiler,而 analyzer 链路由 AnalyzerLexer → branchAnalysis/analyzerSemanticDiagnostics → parseShaderPass → ShaderAnalyzer/Validator 独占 branch proof 与诊断事实;compiler 的 cold build 仅对 runtime internal 子路径绑定 workspace source,发布时仍由 exports 指向各自 artifact。exports、internal manifests 与 package verifier 共同构成唯一公开协议,旧 verbose manifest/artifact 和 root fallback 已删除。

相较上一版,条件编译开关和 verbose 名称被两个显式、单向的 runtime/analyzer 入口替换,没有新增镜像状态、同步层或 production compatibility branch;迁移后的测试/fixture 直接消费 internal/analyzer,package verifier 同时检查旧 artifact 不再打包、新 export target 可打包与 runtime 不泄漏 analyzer source。除上述新注释外,未发现需要删除的同根因冗余或失效旧测试。

- Load analyzer CLI includes lazily and verify the built executable contract.

- Correct the EmptyStruct playground preset and cover every labeled diagnostic sample.

- Clean the remaining review nits without restoring legacy verbose paths.

@GuoLei1990 GuoLei1990 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🫧 尘小沫

结论

阻塞级别:Request changes(P1)。已基于 f1504b0f90e5e9f9b8cead93b15df0b03da70bf1...55df995efb2d5241572d2de81260da50babbb6b9 的 1 个增量 commit、8 个文件完成审查,并沿 CLI 选项 → include-map/preprocessor → analyzer 诊断输出,以及 build script → Rollup CLI 产物 → npm bin 消费路径各追一层。实际 review 动作为 REQUEST_CHANGES,目标 HEAD 为 55df995efb2d5241572d2de81260da50babbb6b9。自动 CR 不替代人工 Reviewer 的合入门禁(APPROVE)。

已关闭问题清单

  • VariableDeclaratorInfo → ShaderValidator、ShaderLab parse envelope、branch/reachability/IO、operator type owner、include cache/cycle 和 AST lifetime 等此前阻塞项均已按单一事实链路闭环,并保留回归覆盖。
  • parser 公开边界已收敛为 runtime/analyzer internal 子路径:legacy root resolver fallback、verbose export/artifact 与对应旧测试路径均已删除,runtime artifact gate 继续有效。
  • 上轮的两行单行注释已合并为无句尾句号的当前行为说明;EmptyStruct playground fixture 也改为真实的宏空 member 场景,未恢复 legacy 路径。
  • 本轮按需 include map 取代了 CLI 启动时递归读取整个 include root 的实现;Preprocessor 仍是 canonical include key、cycle detection 和 source-map 归属的唯一 owner。

问题

  1. [P1] scripts/verify-shader-analyzer-cli.mjs:47-50 / package.json:15 — 新增的 raw dist/cli.js 直启断言在 POSIX 上稳定阻断了整个 build。 Rollup 仅在 rollup.config.js:177-185 为该产物写入 shebang;本轮 verification 却在未经过打包安装的工作区直接 spawnSync(cliPath, ["--help"])。目标 HEAD 的 CI 已实证 Ubuntu 与 macOS 都在 :49 得到 status === nullnull !== 0),导致两个 build 均失败;coverage 和全部 E2E 也因其先执行 npm run build 随之失败,Windows 只是跳过这个分支才通过。应保留 packages/shader-analyzer/package.jsonbin 及 npm/pnpm 安装器为可执行命令、权限和 shim 的权威 owner,删除这个 raw-dist executable 协议:要验证发布 CLI,请在临时消费者中 npm pack 后安装并调用生成的 node_modules/.bin/galacean-shader-analyzer;若只验证 Rollup 产物,则继续用 process.execPath 执行。不要在 build 后手工 chmod dist/cli.js 来迎合该测试,那会额外维护一条与 package bin 平行的发布协议。

架构、熵增与测试治理

本轮生产数据流是 CLI 的 --include-root → lazy IncludeMap cache → Preprocessor._resolveIncludePath 的 canonical key → parseShaderPass/ShaderAnalyzer;lazy map 只缓存已请求的 source 或缺失结果,替代旧的全目录扫描,没有新增 mirror state、compat fallback 或第二套 include 转换。包含路径规范化、递归 base 和 cycle 检测仍由 Preprocessor 单一拥有;CLI 只提供文件系统 lookup,因此该方向是净减熵。

但新 build verification 在下游把 raw Rollup 文件的 POSIX mode 误当成另一份 CLI 可执行契约,与上游 package.json#bin + package-manager shim 的唯一发布 owner 平行,且已造成跨平台 CI 失败。删除 raw-dist 直启检查、改为 pack/install 消费者测试后,链路应为 rollup JS payload → packed package bin metadata → installer shim → user command,而不是增加手工 chmod 或 production compatibility branch。playground fixture/覆盖和 TypeSystem 的纯冗余删除没有引入额外状态;现有测试也无需为旧 eager include loader 保留任何生产 fallback。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants