-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* wip: implement parser extensions for transforming the fragment argument transform syntax into operations without fragment arguments, which are executable by all graphql.js versions See graphql/graphql-js#3152 for reference * fixes Co-authored-by: Dotan Simha <[email protected]>
- Loading branch information
1 parent
02f7292
commit ebb0cef
Showing
9 changed files
with
331 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@envelop/fragment-arguments': patch | ||
--- | ||
|
||
NEW PLUGIN! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
{ | ||
"name": "@envelop/fragment-arguments", | ||
"version": "0.0.1", | ||
"author": "Dotan Simha <[email protected]>", | ||
"license": "MIT", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/dotansimha/envelop.git", | ||
"directory": "packages/plugins/fragment-arguments" | ||
}, | ||
"main": "dist/index.js", | ||
"module": "dist/index.mjs", | ||
"exports": { | ||
".": { | ||
"require": "./dist/index.js", | ||
"import": "./dist/index.mjs" | ||
}, | ||
"./*": { | ||
"require": "./dist/*.js", | ||
"import": "./dist/*.mjs" | ||
} | ||
}, | ||
"typings": "dist/index.d.ts", | ||
"typescript": { | ||
"definition": "dist/index.d.ts" | ||
}, | ||
"scripts": { | ||
"test": "jest", | ||
"prepack": "bob prepack" | ||
}, | ||
"dependencies": { | ||
"@envelop/types": "0.2.1" | ||
}, | ||
"devDependencies": { | ||
"@types/common-tags": "1.8.0", | ||
"@graphql-tools/utils": "7.10.0", | ||
"@graphql-tools/schema": "7.1.5", | ||
"bob-the-bundler": "1.4.1", | ||
"graphql": "15.5.1", | ||
"typescript": "4.3.4", | ||
"oneline": "1.0.3", | ||
"common-tags": "1.8.0" | ||
}, | ||
"peerDependencies": { | ||
"graphql": "^14.0.0 || ^15.0.0" | ||
}, | ||
"buildOptions": { | ||
"input": "./src/index.ts" | ||
}, | ||
"publishConfig": { | ||
"directory": "dist", | ||
"access": "public" | ||
} | ||
} |
86 changes: 86 additions & 0 deletions
86
packages/plugins/fragment-arguments/src/extended-parser.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import { Parser } from 'graphql/language/parser'; | ||
import { Lexer } from 'graphql/language/lexer'; | ||
import { TokenKind, Kind, Source, DocumentNode, TokenKindEnum, Token } from 'graphql'; | ||
|
||
declare module 'graphql/language/parser' { | ||
export class Parser { | ||
constructor(source: string | Source, options?: ParseOptions); | ||
_lexer: Lexer; | ||
expectOptionalKeyword(word: string): boolean; | ||
expectToken(token: TokenKindEnum): void; | ||
peek(token: TokenKindEnum): boolean; | ||
parseFragmentName(): string; | ||
parseArguments(flag: boolean): any; | ||
parseDirectives(flag: boolean): any; | ||
loc(start: Token): any; | ||
parseNamedType(): any; | ||
parseSelectionSet(): any; | ||
expectKeyword(keyword: string): void; | ||
parseVariableDefinitions(): void; | ||
parseDocument(): DocumentNode; | ||
} | ||
} | ||
|
||
export class FragmentArgumentCompatibleParser extends Parser { | ||
parseFragment() { | ||
const start = this._lexer.token; | ||
this.expectToken(TokenKind.SPREAD); | ||
const hasTypeCondition = this.expectOptionalKeyword('on'); | ||
|
||
if (!hasTypeCondition && this.peek(TokenKind.NAME)) { | ||
const name = this.parseFragmentName(); | ||
|
||
if (this.peek(TokenKind.PAREN_L)) { | ||
return { | ||
kind: Kind.FRAGMENT_SPREAD, | ||
name, | ||
arguments: this.parseArguments(false), | ||
directives: this.parseDirectives(false), | ||
loc: this.loc(start), | ||
}; | ||
} | ||
|
||
return { | ||
kind: Kind.FRAGMENT_SPREAD, | ||
name: this.parseFragmentName(), | ||
directives: this.parseDirectives(false), | ||
loc: this.loc(start), | ||
}; | ||
} | ||
|
||
return { | ||
kind: Kind.INLINE_FRAGMENT, | ||
typeCondition: hasTypeCondition ? this.parseNamedType() : undefined, | ||
directives: this.parseDirectives(false), | ||
selectionSet: this.parseSelectionSet(), | ||
loc: this.loc(start), | ||
}; | ||
} | ||
|
||
parseFragmentDefinition() { | ||
const start = this._lexer.token; | ||
this.expectKeyword('fragment'); | ||
const name = this.parseFragmentName(); | ||
|
||
if (this.peek(TokenKind.PAREN_L)) { | ||
return { | ||
kind: Kind.FRAGMENT_DEFINITION, | ||
name, | ||
variableDefinitions: this.parseVariableDefinitions(), | ||
typeCondition: (this.expectKeyword('on'), this.parseNamedType()), | ||
directives: this.parseDirectives(false), | ||
selectionSet: this.parseSelectionSet(), | ||
loc: this.loc(start), | ||
}; | ||
} | ||
|
||
return { | ||
kind: Kind.FRAGMENT_DEFINITION, | ||
name, | ||
typeCondition: (this.expectKeyword('on'), this.parseNamedType()), | ||
directives: this.parseDirectives(false), | ||
selectionSet: this.parseSelectionSet(), | ||
loc: this.loc(start), | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { Plugin } from '@envelop/types'; | ||
import { ParseOptions } from 'graphql/language/parser'; | ||
import { Source, DocumentNode } from 'graphql'; | ||
import { FragmentArgumentCompatibleParser } from './extended-parser'; | ||
import { applySelectionSetFragmentArguments } from './utils'; | ||
|
||
export function parseWithFragmentArguments(source: string | Source, options?: ParseOptions): DocumentNode { | ||
const parser = new FragmentArgumentCompatibleParser(source, options); | ||
|
||
return parser.parseDocument(); | ||
} | ||
|
||
export const useFragmentArguments = (): Plugin => { | ||
return { | ||
onParse({ setParseFn }) { | ||
setParseFn(parseWithFragmentArguments); | ||
|
||
return ({ result, replaceParseResult }) => { | ||
if (result && 'kind' in result) { | ||
replaceParseResult(applySelectionSetFragmentArguments(result)); | ||
} | ||
}; | ||
}, | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { InlineFragmentNode, ArgumentNode, DocumentNode, FragmentDefinitionNode, visit } from 'graphql'; | ||
|
||
export function applySelectionSetFragmentArguments(document: DocumentNode): DocumentNode | Error { | ||
const fragmentList = new Map<string, FragmentDefinitionNode>(); | ||
for (const def of document.definitions) { | ||
if (def.kind !== 'FragmentDefinition') { | ||
continue; | ||
} | ||
fragmentList.set(def.name.value, def); | ||
} | ||
|
||
return visit(document, { | ||
FragmentSpread(fragmentNode) { | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore | ||
if (fragmentNode.arguments != null && fragmentNode.arguments.length) { | ||
const fragmentDef = fragmentList.get(fragmentNode.name.value); | ||
if (!fragmentDef) { | ||
return; | ||
} | ||
|
||
const fragmentArguments = new Map<string, ArgumentNode>(); | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore | ||
for (const arg of fragmentNode.arguments) { | ||
fragmentArguments.set(arg.name.value, arg); | ||
} | ||
|
||
const selectionSet = visit(fragmentDef.selectionSet, { | ||
Variable(variableNode) { | ||
const fragArg = fragmentArguments.get(variableNode.name.value); | ||
if (fragArg) { | ||
return fragArg.value; | ||
} | ||
|
||
return variableNode; | ||
}, | ||
}); | ||
|
||
const inlineFragment: InlineFragmentNode = { | ||
kind: 'InlineFragment', | ||
typeCondition: fragmentDef.typeCondition, | ||
selectionSet, | ||
}; | ||
|
||
return inlineFragment; | ||
} | ||
return fragmentNode; | ||
}, | ||
}); | ||
} |
87 changes: 87 additions & 0 deletions
87
packages/plugins/fragment-arguments/test/use-fragment-arguments.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import { buildSchema, print } from 'graphql'; | ||
import { oneLine, stripIndent } from 'common-tags'; | ||
import { diff } from 'jest-diff'; | ||
import { envelop, useSchema } from '@envelop/core'; | ||
import { useFragmentArguments } from '../src'; | ||
|
||
function compareStrings(a: string, b: string): boolean { | ||
return a.includes(b); | ||
} | ||
|
||
expect.extend({ | ||
toBeSimilarStringTo(received: string, expected: string) { | ||
const strippedReceived = oneLine`${received}`.replace(/\s\s+/g, ' '); | ||
const strippedExpected = oneLine`${expected}`.replace(/\s\s+/g, ' '); | ||
|
||
if (compareStrings(strippedReceived, strippedExpected)) { | ||
return { | ||
message: () => | ||
`expected | ||
${received} | ||
not to be a string containing (ignoring indents) | ||
${expected}`, | ||
pass: true, | ||
}; | ||
} else { | ||
const diffString = diff(stripIndent`${expected}`, stripIndent`${received}`, { | ||
expand: this.expand, | ||
}); | ||
const hasExpect = diffString && diffString.includes('- Expect'); | ||
|
||
const message = hasExpect | ||
? `Difference:\n\n${diffString}` | ||
: `expected | ||
${received} | ||
to be a string containing (ignoring indents) | ||
${expected}`; | ||
|
||
return { | ||
message: () => message, | ||
pass: false, | ||
}; | ||
} | ||
}, | ||
}); | ||
|
||
declare global { | ||
// eslint-disable-next-line no-redeclare | ||
namespace jest { | ||
interface Matchers<R, T> { | ||
/** | ||
* Normalizes whitespace and performs string comparisons | ||
*/ | ||
toBeSimilarStringTo(expected: string): R; | ||
} | ||
} | ||
} | ||
|
||
describe('useFragmentArguments', () => { | ||
const schema = buildSchema(/* GraphQL */ ` | ||
type Query { | ||
a: TestType | ||
} | ||
type TestType { | ||
a(b: String): Boolean | ||
} | ||
`); | ||
test('can inline fragment with argument', () => { | ||
const { parse } = envelop({ plugins: [useFragmentArguments(), useSchema(schema)] })({}); | ||
const result = parse(/* GraphQL */ ` | ||
fragment TestFragment($c: String) on Query { | ||
a(b: $c) | ||
} | ||
query TestQuery($a: String) { | ||
...TestFragment(c: $a) | ||
} | ||
`); | ||
expect(print(result)).toBeSimilarStringTo(/* GraphQL */ ` | ||
query TestQuery($a: String) { | ||
... on Query { | ||
a(b: $a) | ||
} | ||
} | ||
`); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2358,6 +2358,11 @@ | |
"@types/connect" "*" | ||
"@types/node" "*" | ||
|
||
"@types/[email protected]": | ||
version "1.8.0" | ||
resolved "https://registry.yarnpkg.com/@types/common-tags/-/common-tags-1.8.0.tgz#79d55e748d730b997be5b7fce4b74488d8b26a6b" | ||
integrity sha512-htRqZr5qn8EzMelhX/Xmx142z218lLyGaeZ3YR8jlze4TATRU9huKKvuBmAJEW4LCC4pnY1N6JAm6p85fMHjhg== | ||
|
||
"@types/component-emitter@^1.2.10": | ||
version "1.2.10" | ||
resolved "https://registry.yarnpkg.com/@types/component-emitter/-/component-emitter-1.2.10.tgz#ef5b1589b9f16544642e473db5ea5639107ef3ea" | ||
|
@@ -4188,6 +4193,11 @@ commander@^7.2.0: | |
resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" | ||
integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== | ||
|
||
[email protected]: | ||
version "1.8.0" | ||
resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937" | ||
integrity sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw== | ||
|
||
commondir@^1.0.1: | ||
version "1.0.1" | ||
resolved "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" | ||
|
@@ -8684,6 +8694,11 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: | |
dependencies: | ||
wrappy "1" | ||
|
||
[email protected]: | ||
version "1.0.3" | ||
resolved "https://registry.yarnpkg.com/oneline/-/oneline-1.0.3.tgz#2f2631bd3a5716a4eeb439291697af2fc7fa39a5" | ||
integrity sha512-KWLrLloG/ShWvvWuvmOL2jw17++ufGdbkKC2buI2Aa6AaM4AkjCtpeJZg60EK34NQVo2qu1mlPrC2uhvQgCrhQ== | ||
|
||
onetime@^5.1.0, onetime@^5.1.2: | ||
version "5.1.2" | ||
resolved "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" | ||
|