From 5a5825cedee68a5d0fb0331fc2c259745e7cfd7e Mon Sep 17 00:00:00 2001 From: Naveen Jain Date: Tue, 3 Mar 2020 01:02:26 +0530 Subject: [PATCH] Added support to maintain comments when parsing Signed-off-by: Naveen Jain --- src/language/__tests__/parser-test.js | 35 ++++++++++++++++++++ src/language/__tests__/schema-parser-test.js | 29 ++++++++++++++++ src/language/ast.d.ts | 10 ++++++ src/language/ast.js | 10 ++++++ src/language/lexer.d.ts | 1 + src/language/lexer.js | 27 ++++++++++++++- src/language/parser.js | 5 ++- 7 files changed, 115 insertions(+), 2 deletions(-) diff --git a/src/language/__tests__/parser-test.js b/src/language/__tests__/parser-test.js index c3ff3a57de..5ba938dc16 100644 --- a/src/language/__tests__/parser-test.js +++ b/src/language/__tests__/parser-test.js @@ -147,6 +147,39 @@ describe('Parser', () => { ); }); + it('Add comments from type in AST', () => { + const ast = parse(` + #This comment has a \u0A0A multi-byte character. + type alpha{ field(arg: string):string } + `); + + expect(ast).to.have.nested.property( + 'comments[0].value', + 'This comment has a \u0A0A multi-byte character.', + ); + expect(ast.comments).to.have.length(1); + }); + + it('Add multiple comments in AST', () => { + const ast = parse(` + type alpha{ + #This comment is demo comment. + field(arg: string):string + #This is another demo comment having # inside + } + `); + + expect(ast).to.have.nested.property( + 'comments[0].value', + 'This comment is demo comment.', + ); + expect(ast).to.have.nested.property( + 'comments[1].value', + 'This is another demo comment having # inside', + ); + expect(ast.comments).to.have.length(2); + }); + it('parses kitchen sink', () => { expect(() => parse(kitchenSinkQuery)).to.not.throw(); }); @@ -231,6 +264,7 @@ describe('Parser', () => { expect(toJSONDeep(result)).to.deep.equal({ kind: Kind.DOCUMENT, + comments: [], loc: { start: 0, end: 41 }, definitions: [ { @@ -321,6 +355,7 @@ describe('Parser', () => { expect(toJSONDeep(result)).to.deep.equal({ kind: Kind.DOCUMENT, + comments: [], loc: { start: 0, end: 30 }, definitions: [ { diff --git a/src/language/__tests__/schema-parser-test.js b/src/language/__tests__/schema-parser-test.js index a9577d12da..d46a7076ca 100644 --- a/src/language/__tests__/schema-parser-test.js +++ b/src/language/__tests__/schema-parser-test.js @@ -79,6 +79,7 @@ describe('Schema Parser', () => { expect(toJSONDeep(doc)).to.deep.equal({ kind: 'Document', + comments: [], definitions: [ { kind: 'ObjectTypeDefinition', @@ -176,6 +177,7 @@ describe('Schema Parser', () => { expect(toJSONDeep(doc)).to.deep.equal({ kind: 'Document', + comments: [], definitions: [ { kind: 'ObjectTypeExtension', @@ -201,6 +203,7 @@ describe('Schema Parser', () => { expect(toJSONDeep(doc)).to.deep.equal({ kind: 'Document', + comments: [], definitions: [ { kind: 'ObjectTypeExtension', @@ -219,6 +222,7 @@ describe('Schema Parser', () => { const doc = parse('extend interface Hello implements Greeting'); expect(toJSONDeep(doc)).to.deep.equal({ kind: 'Document', + comments: [], definitions: [ { kind: 'InterfaceTypeExtension', @@ -242,6 +246,7 @@ describe('Schema Parser', () => { expect(toJSONDeep(doc)).to.deep.equal({ kind: 'Document', + comments: [], definitions: [ { kind: 'ObjectTypeExtension', @@ -304,6 +309,7 @@ describe('Schema Parser', () => { `); expect(toJSONDeep(doc)).to.deep.equal({ kind: 'Document', + comments: [], definitions: [ { kind: 'InterfaceTypeExtension', @@ -376,6 +382,7 @@ describe('Schema Parser', () => { const doc = parse(body); expect(toJSONDeep(doc)).to.deep.equal({ kind: 'Document', + comments: [], definitions: [ { kind: 'SchemaExtension', @@ -400,6 +407,7 @@ describe('Schema Parser', () => { const doc = parse(body); expect(toJSONDeep(doc)).to.deep.equal({ kind: 'Document', + comments: [], definitions: [ { kind: 'SchemaExtension', @@ -442,6 +450,7 @@ describe('Schema Parser', () => { expect(toJSONDeep(doc)).to.deep.equal({ kind: 'Document', + comments: [], definitions: [ { kind: 'ObjectTypeDefinition', @@ -471,6 +480,7 @@ describe('Schema Parser', () => { const doc = parse('interface Hello implements World { field: String }'); expect(toJSONDeep(doc)).to.deep.equal({ kind: 'Document', + comments: [], definitions: [ { kind: 'InterfaceTypeDefinition', @@ -497,6 +507,7 @@ describe('Schema Parser', () => { expect(toJSONDeep(doc)).to.deep.equal({ kind: 'Document', + comments: [], definitions: [ { kind: 'ObjectTypeDefinition', @@ -523,6 +534,7 @@ describe('Schema Parser', () => { expect(toJSONDeep(doc)).to.deep.equal({ kind: 'Document', + comments: [], definitions: [ { kind: 'ObjectTypeDefinition', @@ -551,6 +563,7 @@ describe('Schema Parser', () => { const doc = parse('interface Hello implements Wo & rld { field: String }'); expect(toJSONDeep(doc)).to.deep.equal({ kind: 'Document', + comments: [], definitions: [ { kind: 'InterfaceTypeDefinition', @@ -580,6 +593,7 @@ describe('Schema Parser', () => { expect(toJSONDeep(doc)).to.deep.equal({ kind: 'Document', + comments: [], definitions: [ { kind: 'ObjectTypeDefinition', @@ -610,6 +624,7 @@ describe('Schema Parser', () => { ); expect(toJSONDeep(doc)).to.deep.equal({ kind: 'Document', + comments: [], definitions: [ { kind: 'InterfaceTypeDefinition', @@ -639,6 +654,7 @@ describe('Schema Parser', () => { expect(toJSONDeep(doc)).to.deep.equal({ kind: 'Document', + comments: [], definitions: [ { kind: 'EnumTypeDefinition', @@ -658,6 +674,7 @@ describe('Schema Parser', () => { expect(toJSONDeep(doc)).to.deep.equal({ kind: 'Document', + comments: [], definitions: [ { kind: 'EnumTypeDefinition', @@ -684,6 +701,7 @@ describe('Schema Parser', () => { expect(toJSONDeep(doc)).to.deep.equal({ kind: 'Document', + comments: [], definitions: [ { kind: 'InterfaceTypeDefinition', @@ -714,6 +732,7 @@ describe('Schema Parser', () => { expect(toJSONDeep(doc)).to.deep.equal({ kind: 'Document', + comments: [], definitions: [ { kind: 'ObjectTypeDefinition', @@ -752,6 +771,7 @@ describe('Schema Parser', () => { expect(toJSONDeep(doc)).to.deep.equal({ kind: 'Document', + comments: [], definitions: [ { kind: 'ObjectTypeDefinition', @@ -794,6 +814,7 @@ describe('Schema Parser', () => { expect(toJSONDeep(doc)).to.deep.equal({ kind: 'Document', + comments: [], definitions: [ { kind: 'ObjectTypeDefinition', @@ -836,6 +857,7 @@ describe('Schema Parser', () => { expect(toJSONDeep(doc)).to.deep.equal({ kind: 'Document', + comments: [], definitions: [ { kind: 'ObjectTypeDefinition', @@ -876,6 +898,7 @@ describe('Schema Parser', () => { expect(toJSONDeep(doc)).to.deep.equal({ kind: 'Document', + comments: [], definitions: [ { kind: 'UnionTypeDefinition', @@ -895,6 +918,7 @@ describe('Schema Parser', () => { expect(toJSONDeep(doc)).to.deep.equal({ kind: 'Document', + comments: [], definitions: [ { kind: 'UnionTypeDefinition', @@ -917,6 +941,7 @@ describe('Schema Parser', () => { expect(toJSONDeep(doc)).to.deep.equal({ kind: 'Document', + comments: [], definitions: [ { kind: 'UnionTypeDefinition', @@ -967,6 +992,7 @@ describe('Schema Parser', () => { expect(toJSONDeep(doc)).to.deep.equal({ kind: 'Document', + comments: [], definitions: [ { kind: 'ScalarTypeDefinition', @@ -988,6 +1014,7 @@ input Hello { expect(toJSONDeep(doc)).to.deep.equal({ kind: 'Document', + comments: [], definitions: [ { kind: 'InputObjectTypeDefinition', @@ -1026,6 +1053,7 @@ input Hello { expect(toJSONDeep(doc)).to.deep.equal({ kind: 'Document', + comments: [], definitions: [ { kind: 'DirectiveDefinition', @@ -1062,6 +1090,7 @@ input Hello { expect(toJSONDeep(doc)).to.deep.equal({ kind: 'Document', + comments: [], definitions: [ { kind: 'DirectiveDefinition', diff --git a/src/language/ast.d.ts b/src/language/ast.d.ts index 576db47b6c..5c825f518e 100644 --- a/src/language/ast.d.ts +++ b/src/language/ast.d.ts @@ -204,6 +204,16 @@ export interface DocumentNode { readonly kind: 'Document'; readonly loc?: Location; readonly definitions: ReadonlyArray; + readonly comments: ReadonlyArray; +} + +export interface CommentNode { + readonly kind: 'Comment'; + readonly start: number; + readonly end: number; + readonly column: number; + readonly line: number; + readonly value: string | void; } export type DefinitionNode = diff --git a/src/language/ast.js b/src/language/ast.js index 9d5df64d86..50fb625607 100644 --- a/src/language/ast.js +++ b/src/language/ast.js @@ -240,6 +240,7 @@ export type DocumentNode = {| +kind: 'Document', +loc?: Location, +definitions: $ReadOnlyArray, + +comments: $ReadOnlyArray, |}; export type DefinitionNode = @@ -247,6 +248,15 @@ export type DefinitionNode = | TypeSystemDefinitionNode | TypeSystemExtensionNode; +export type CommentNode = {| + +kind: 'Comment', + +start: number, + +end: number, + +column: number, + +line: number, + +value: string | void, +|}; + export type ExecutableDefinitionNode = | OperationDefinitionNode | FragmentDefinitionNode; diff --git a/src/language/lexer.d.ts b/src/language/lexer.d.ts index 92484b6ccd..da5596df54 100644 --- a/src/language/lexer.d.ts +++ b/src/language/lexer.d.ts @@ -22,6 +22,7 @@ export class Lexer { */ token: Token; + commentsList: Array; /** * The (1-indexed) line containing the current token. */ diff --git a/src/language/lexer.js b/src/language/lexer.js index b6ab501308..4c2f56e983 100644 --- a/src/language/lexer.js +++ b/src/language/lexer.js @@ -2,7 +2,7 @@ import { syntaxError } from '../error/syntaxError'; -import { Token } from './ast'; +import { Token, type CommentNode } from './ast'; import { type Source } from './source'; import { dedentBlockStringValue } from './blockString'; import { type TokenKindEnum, TokenKind } from './tokenKind'; @@ -28,6 +28,10 @@ export class Lexer { */ token: Token; + /** + * The list of comments token encountered so far + */ + commentsList: Array; /** * The (1-indexed) line containing the current token. */ @@ -44,6 +48,7 @@ export class Lexer { this.source = source; this.lastToken = startOfFileToken; this.token = startOfFileToken; + this.commentsList = []; this.line = 1; this.lineStart = 0; } @@ -67,12 +72,32 @@ export class Lexer { do { // Note: next is only mutable during parsing, so we cast to allow this. token = token.next ?? ((token: any).next = readToken(this, token)); + if (token.kind === TokenKind.COMMENT) { + addCommentNodeToList(this, token); + continue; + } } while (token.kind === TokenKind.COMMENT); } return token; } } +function addCommentNodeToList(lexer: Lexer, commentToken: Token) { + if (commentToken.kind !== TokenKind.COMMENT) { + return; + } + const { start, end, column, line, value } = commentToken; + + lexer.commentsList.push({ + kind: 'Comment', + start, + end, + column, + line, + value, + }); +} + /** * @internal */ diff --git a/src/language/parser.js b/src/language/parser.js index 2fdcdc6ab6..f4e152b22c 100644 --- a/src/language/parser.js +++ b/src/language/parser.js @@ -197,7 +197,7 @@ class Parser { */ parseDocument(): DocumentNode { const start = this._lexer.token; - return { + const astWithoutComments = { kind: Kind.DOCUMENT, definitions: this.many( TokenKind.SOF, @@ -205,7 +205,10 @@ class Parser { TokenKind.EOF, ), loc: this.loc(start), + comments: [], }; + astWithoutComments.comments = this._lexer.commentsList; + return astWithoutComments; } /**