diff --git a/understand-anything-plugin/packages/core/src/__tests__/parsers.test.ts b/understand-anything-plugin/packages/core/src/__tests__/parsers.test.ts index 10d214208..fd9e4ce2a 100644 --- a/understand-anything-plugin/packages/core/src/__tests__/parsers.test.ts +++ b/understand-anything-plugin/packages/core/src/__tests__/parsers.test.ts @@ -353,6 +353,53 @@ type Mutation { const result = parser.analyzeFile("schema.graphql", content); expect(result.definitions!.some(d => d.name === "Role" && d.kind === "enum")).toBe(true); }); + + it("does not let a scalar inherit a following type's fields or line range", () => { + // Arrange — a custom scalar declared above an object type (a ubiquitous + // pattern, e.g. `scalar DateTime`). scalar has no brace body of its own. + const content = `scalar DateTime + +type User { + id: ID! + name: String! +}`; + + // Act + const result = parser.analyzeFile("schema.graphql", content); + + // Assert — the scalar is body-less: no stolen fields, single-line range. + const scalar = result.definitions!.find(d => d.name === "DateTime"); + expect(scalar).toMatchObject({ kind: "scalar", lineRange: [1, 1] }); + expect(scalar!.fields).toEqual([]); + + // The following type keeps its own fields and range. + const user = result.definitions!.find(d => d.name === "User"); + expect(user).toMatchObject({ kind: "type", lineRange: [3, 6] }); + expect(user!.fields).toEqual(["id", "name"]); + }); + + it("does not let a union inherit a following type's fields or line range", () => { + // Arrange — a union declared above an object type. union has no brace body. + const content = `union SearchResult = User | Post + +type Post { + id: ID! + title: String! +}`; + + // Act + const result = parser.analyzeFile("schema.graphql", content); + + // Assert — the union is body-less: no stolen fields, single-line range. + const union = result.definitions!.find(d => d.name === "SearchResult"); + expect(union).toMatchObject({ kind: "union", lineRange: [1, 1] }); + expect(union!.fields).toEqual([]); + + // The following type is unaffected. + const post = result.definitions!.find(d => d.name === "Post"); + expect(post).toMatchObject({ kind: "type", lineRange: [3, 6] }); + expect(post!.fields).toEqual(["id", "title"]); + }); }); describe("ProtobufParser", () => { diff --git a/understand-anything-plugin/packages/core/src/plugins/parsers/graphql-parser.ts b/understand-anything-plugin/packages/core/src/plugins/parsers/graphql-parser.ts index c3ccc5c03..1f63d7f5b 100644 --- a/understand-anything-plugin/packages/core/src/plugins/parsers/graphql-parser.ts +++ b/understand-anything-plugin/packages/core/src/plugins/parsers/graphql-parser.ts @@ -34,12 +34,18 @@ export class GraphQLParser implements AnalyzerPlugin { if (name === "Query" || name === "Mutation" || name === "Subscription") continue; const startLine = content.slice(0, match.index).split("\n").length; + // scalar and union have no brace-delimited body. Without this guard the + // logic below would scan forward and capture the fields and closing brace + // of a later braced definition — e.g. `scalar DateTime` declared above a + // `type` would inherit that type's fields and line range. + const hasBody = kind !== "scalar" && kind !== "union"; + // Extract fields (for type/input/interface/enum) - const fields = this.extractFields(content, match.index); + const fields = hasBody ? this.extractFields(content, match.index) : []; // Find closing brace const afterMatch = content.slice(match.index); - const closeBrace = afterMatch.indexOf("}"); + const closeBrace = hasBody ? afterMatch.indexOf("}") : -1; const endLine = closeBrace !== -1 ? content.slice(0, match.index + closeBrace + 1).split("\n").length : startLine;