Skip to content

GraphQL symbol extraction: fragment X on Y never captured (426 misses on graphql-platform), directive @X never captured, extend only handles extend type #171

Description

@Widthdom

Summary

The GraphQL pattern set in SymbolExtractor.cs is missing three common SDL / executable-document constructs:

  1. fragment FragmentName on TypeName { ... } — client-side reusable selection sets. The primary building block of Apollo / Relay / urql / graphql-codegen / StrawberryShake / Nitro CLI / any modern GraphQL-over-HTTP client. On ChilliCream/graphql-platform@main (the HotChocolate + StrawberryShake + Nitro mono-repo), 426 real fragment X on Y declarations across 117 files → 0 symbol rows.
  2. directive @directiveName on <LOCATIONS> — SDL directive definitions. The built-in GraphQL spec directives @defer, @oneOf, @specifiedBy plus any custom directives like @auth, @key, @requires, @provides are all invisible. Small scale per file, but 100% coverage miss.
  3. extend interface / extend input / extend enum / extend union / extend schema — the existing pattern only handles extend type X, even though GraphQL allows extending all declarable types.

Same overall shape as the F# / VB / Makefile / SCSS / Rust issues recently filed (#165 / #166 / #167 / #168 / #169) — the per-line regex list is missing real language constructs and the miss is silent.

Repro

curl -fsSL https://raw.githubusercontent.com/Widthdom/CodeIndex/main/install.sh | bash
CDIDX=/root/.local/bin/cdidx

mkdir -p /tmp/srcs && cd /tmp/srcs
curl -fsSL -o hotchocolate.tar.gz https://codeload.github.com/ChilliCream/graphql-platform/tar.gz/refs/heads/main
tar xzf hotchocolate.tar.gz

# Smaller focused sample first
mkdir -p /tmp/gqltest
cp /tmp/srcs/graphql-platform-main/src/Nitro/CommandLine/src/ChilliCream.Nitro.Client/schema.graphql       /tmp/gqltest/
cp /tmp/srcs/graphql-platform-main/src/Nitro/CommandLine/src/ChilliCream.Nitro.Client/fragments.graphql    /tmp/gqltest/
cp /tmp/srcs/graphql-platform-main/src/Nitro/CommandLine/src/ChilliCream.Nitro.Client/Apis/Operations/ListApiCommand.graphql /tmp/gqltest/
cp /tmp/srcs/graphql-platform-main/src/HotChocolate/Core/test/Types.Tests/__resources__/schema_coordinates.graphql /tmp/gqltest/

"$CDIDX" /tmp/gqltest --db /tmp/gqltest.db --rebuild
"$CDIDX" status --db /tmp/gqltest.db | tail -6
# Kinds:
#   class     702
#   interface  48
#   enum       21
#   function    1   ← the one `query ListApiCommandQuery(...)` operation; zero fragments, zero directives

1. Fragments: 120 declarations in fragments.graphql → 0 symbols

"$CDIDX" symbols "ProcessingTimeoutError" --db /tmp/gqltest.db --lang graphql --exact
# class      ProcessingTimeoutError    schema.graphql:3072-3074      ← the type, OK
# (the fragment with the same name in fragments.graphql:12 is NOT in the index)

Source (fragments.graphql:12):

fragment ProcessingTimeoutError on ProcessingTimeoutError {
  __typename
  message
}

Rough scale via grep across the whole repo (regular-word regex):

^fragment\s+\w+\s+on\s+\w  → 426 occurrences across 117 files

None are captured.

2. Directives: 0 symbols

"$CDIDX" symbols "defer"       --db /tmp/gqltest.db --lang graphql --exact   # No symbols found.
"$CDIDX" symbols "oneOf"       --db /tmp/gqltest.db --lang graphql --exact   # No symbols found.
"$CDIDX" symbols "specifiedBy" --db /tmp/gqltest.db --lang graphql --exact   # No symbols found.
"$CDIDX" symbols "qux"         --db /tmp/gqltest.db --lang graphql --exact   # No symbols found.

Source (schema.graphql:4956, schema.graphql:4959, schema.graphql:4962, schema_coordinates.graphql:26):

directive @defer("…" if: Boolean "…" label: String) on FRAGMENT_SPREAD | INLINE_FRAGMENT
directive @oneOf on INPUT_OBJECT
directive @specifiedBy("…" url: String!) on SCALAR
directive @qux(a: String) on FIELD_DEFINITION

3. extend beyond extend type

grep -RnE '^extend\s+(interface|input|enum|union|schema)\s+' /tmp/srcs/graphql-platform-main
# extend enum Episode { ... }
# extend schema @key(fields: "id")
# ...

The existing regex at SymbolExtractor.cs:321 only matches extend type X:

new("class", new Regex(@"^\s*(?:extend\s+type)\s+(?<name>\w+)", RegexOptions.Compiled), BodyStyle.Brace),

extend enum Episode, extend input FooInput, extend interface Node, extend union U, extend schema @... all fall through.

Root cause

src/CodeIndex/Indexer/SymbolExtractor.cs:315-323:

["graphql"] =
[
    new("interface", new Regex(@"^\s*interface\s+(?<name>\w+)", RegexOptions.Compiled), BodyStyle.Brace),
    new("enum",     new Regex(@"^\s*enum\s+(?<name>\w+)", RegexOptions.Compiled), BodyStyle.Brace),
    new("class",    new Regex(@"^\s*(?:type|union|scalar|input)\s+(?<name>\w+)", RegexOptions.Compiled), BodyStyle.Brace),
    new("function", new Regex(@"^\s*(?:query|mutation|subscription)\s+(?<name>\w+)", RegexOptions.Compiled), BodyStyle.Brace),
    new("class",    new Regex(@"^\s*(?:extend\s+type)\s+(?<name>\w+)", RegexOptions.Compiled), BodyStyle.Brace),
    new("class",    new Regex(@"^\s*schema\s*\{", RegexOptions.Compiled), BodyStyle.Brace),
],
  • No fragment pattern.
  • No directive pattern.
  • extend alternation covers only type.

Why it matters

  • Fragment coverage gap is the largest. Every client-side GraphQL codebase — the entire reason .graphql files exist alongside source code instead of being embedded — stores reusable fragments in separate files. Missing all of them means callers / references / definition on fragment names return nothing. StrawberryShake, Relay, Apollo Client, graphql-codegen templates, Nitro CLI, every .graphql corpus I've seen has significant fragment use.
  • Directives matter for auth/schema-federation workflows. Apollo Federation's @key, @external, @provides, @requires are user-authored directives. HotChocolate's directives. Without directive extraction, symbols --kind function is incomplete and AI consumers can't discover the schema's directive surface.
  • extend coverage gap is smaller by volume but bites schema-stitching / federation codebases hard.
  • Silent. No warning; user sees classes + interfaces + enums + 1 query as their entire symbol surface of a 120-fragment file.

Suggested direction

Three additive patterns + one alternation widening; all one-line fixes.

  1. Add fragment Name on Type pattern. Kind is a judgment call (function or class). function aligns with how query/mutation/subscription are currently kinded; the intent is "executable-document declaration":

    new("function", new Regex(@"^\s*fragment\s+(?<name>\w+)\s+on\s+\w+", RegexOptions.Compiled), BodyStyle.Brace),
  2. Add directive @Name pattern. GraphQL directive names start with @; capture without the @:

    new("function", new Regex(@"^\s*directive\s+@(?<name>\w+)", RegexOptions.Compiled), BodyStyle.None),

    BodyStyle.None is correct because directive declarations do not have { ... } bodies — they end at the first on <LOCATIONS>.

  3. Widen the extend alternation to cover all declarable types:

    new("class", new Regex(@"^\s*extend\s+(?:type|interface|input|enum|union)\s+(?<name>\w+)", RegexOptions.Compiled), BodyStyle.Brace),

    extend schema has no name and is better left to the existing schema { ... } capture — or gets a dedicated nameless-capture row if you want it visible.

  4. (Polish) scalar Name already works — scalars can also be declared without a body (scalar DateTime with no { ... }). The existing BodyStyle.Brace for scalar may incorrectly treat the next type's { as the scalar's body. Quick verification on a scalar-heavy schema would be useful. Not strictly part of this bug; noting for the same regex pass.

Expected impact after fix

On /tmp/srcs/graphql-platform-main:

  • symbols --kind function --lang graphql --count rises from the current "executable operations only" count to include 426+ fragments and every directive @X.
  • symbols "ProcessingTimeoutError" --lang graphql returns both the schema type and the fragment by the same name.
  • callers Error --lang graphql (a fragment spread target) starts producing edges from the 75+ fragments that spread ...Error.

Scope

  • src/CodeIndex/Indexer/SymbolExtractor.cs — add fragment pattern, add directive pattern, widen extend alternation.
  • src/CodeIndex/Indexer/ReferenceExtractor.cs — verify whether fragment spread ...FragmentName and directive use @directiveName are already captured as references; if not, this is a natural companion patch.
  • tests/CodeIndex.Tests/SymbolExtractorTests.cs — fixtures for: (a) fragment Foo on Bar { x } captured as function kind, (b) directive @auth(role: String!) on FIELD_DEFINITION captured as function kind, (c) extend enum Episode { NEWHOPE } captured, (d) extend input FooInput { bar: Int } captured, (e) existing type / query / interface / enum patterns still match (no regression).

Related

Same family: per-line regex list missing a common language construct. GraphQL is the smallest surface of the batch; fragment is the one that bites modern codebases the hardest.

Environment

  • cdidx: v1.10.0 (installed via install.sh).
  • Corpus: ChilliCream/graphql-platform@main — 426 fragment X on Y declarations across 117 .graphql files, plus hundreds of directive @X on Y lines, all currently dropped.
  • Platform: linux-x64 container.
  • Filed from a cloud Claude Code session per CLOUD_BOOTSTRAP_PROMPT.md.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions