Skip to content

A template file's embedded code block should also yield a symbol_table module #209

Description

@rahlk

Is your feature request related to a problem? Please describe.

A code block embedded in a template — the TS/JS analogue of a JSP scriptlet — is invisible to every analysis this repo performs. Measured at HEAD (-a 2) on a fixture with one of each shape:

MODULES: ['src/real.ts']

src/Widget.vue (a <script lang="ts"> block with an import and two functions), src/pages/index.astro (frontmatter declaring a function), views/page.ejs (<% %> statements) and public/index.html (an inline <script> with a function and a call to it) each produce an artifact record carrying the full source — and nothing else. No callables, no call edges, no CFG, no entrypoints. Code that a request actually executes is stored as opaque text.

Two structurally different things are conflated by the word "scriptlet", and they want different answers:

(a) An embedded code BLOCK.astro frontmatter (--- … ---, statements run server-side per request), .vue/.svelte <script>/<script setup> (declarations), EJS <% … %> statement scriptlets, an inline <script> in .html. This is a module whose file happens to be a template. It needs no new node kind: the content is TS/JS, callables have real byte spans in the real file, assignIds already mints can://<app>/ts/<file>/…, and heritage/call graph/CFG work unchanged. Because span.bytes are UTF-8 byte offsets into the file rather than into a preprocessed string (#179), a block starting at byte 130 of the .vue has a truthful span with no source-map layer.

(b) A template EXPRESSION{{ count }} (Vue), {expr} (Svelte/Astro/JSX-in-markup), <%= total %> (EJS), a directive value like @click="bump". One to three tokens, dozens per file, no signature and no body. Modeling each as a TSCallable is wrong. The only interesting fact about one is which declaration it reads, which is an edge, not a node — and the shape already exists in this repo as TS_USES_CONFIG (:TSBodyNode → target, prov: string[]).

Artifacts and modules are disjoint by construction, at two lines:

  • src/artifacts/index.ts:179if (SOURCE_EXTS.has(path.extname(e.name))) continue; // source lives in the symbol table
  • src/syntactic_analysis/discovery.ts:5 and its JS_EXTS sibling — only .ts/.tsx/.mts/.cts plus the JS extensions are admitted as modules.

So a .vue can only ever be an :Artifact. Relaxing this is what lets one file yield both an artifact record and a symbol_table entry — which is itself the decision that needs designing, because "one file, two sections" is a new relationship between two level-free layers, and is_entrypoint/entrypoints[] on a callable inside a template is a claim consumers will query.

Describe the solution you'd like

  • Spec, committed under docs/design/specs/, deciding: whether a template file appears in both artifacts{} and symbol_table{} or is moved wholesale; the id shape for a callable inside a template; whether (b) is a body-node role or an edge; and how roles[] on the artifact relates to the module.
  • Cross-language coining agreed before implementation. codeanalyzer-java is doing JSP/JSF/Thymeleaf now; whichever analyzer ships first coins the vocabulary for both, and this is exactly the case the parity clause exists for. Half (a) ideally coins nothing — a template file yielding a module needs no new vocabulary at all, which is the cheapest possible parity.
  • Only then: implementation, per whatever the spec decides.

Describe alternatives you've considered

Not stated in the original issue.

Additional context

Scope boundary

  • This is a contract change and belongs on the design rung, not here. It changes what a symbol_table key means (today: a TS/JS source file), and half (b) needs either a new body-node role or a new edge type. Filed as a work item so the thread is not dropped; entering it means entering designing-cldk-changes, not maintaining-cldk.
  • Artifact role assignment for the same files is deliberately out of scope and tracked separately (artifacts: view templates get roles: ["unknown"] (.vue, .svelte, .astro, .ejs, .html) #208) — that one moves no contract and can land first.
  • Parsing .astro's markup, Vue's <template>, or Pug is out of scope for (a): only the code block is.

Caveats and known risks

  • Which parser. ts-morph parses TS, not .vue/.astro. Extracting the block means either a small hand-written scanner for the delimiters (cheap, and the byte offsets stay honest) or a per-framework compiler dependency (@vue/compiler-sfc, @astrojs/compiler — accurate, but a new dependency per framework and a version-skew surface). The scanner is the lazy option and is probably right; the spec should say which and why.
  • Byte offsets are the whole reason this is tractable. Any approach that rewrites the block into a synthetic string before parsing needs a source map, and every span in the tree becomes a lie unless the map is applied everywhere. Parse in place, or accept that cost explicitly.
  • EJS <% %> blocks are not a module — they are statements in a per-render function whose parameters come from the render call. Treating them as file-scope declarations would produce plausible-looking nonsense; the spec should decide whether EJS is in the first cut at all.
  • Half (b) risks a large node-count increase: dozens of interpolations per template across a real application. Whatever shape is chosen has to be measured on a real project before it ships.
  • .html inline <script> has no module semantics at all (classic script scope, not module scope). Imports cannot appear; a <script type="module"> is different from a bare one. Do not collapse the two.

Definition of done

A committed spec that names, for each of (a) and (b), the exact section and id shape used; a recorded cross-analyzer agreement on any coined term (or an explicit statement that (a) coins nothing); and — for whichever half the spec puts in the first cut — a fixture where a .vue/.astro file's embedded function appears in symbol_table with a span whose bytes slice back to the original template text.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions