add mergeAST from GraphiQL, refactor using visit() [WIP]#1948
add mergeAST from GraphiQL, refactor using visit() [WIP]#1948acao wants to merge 1 commit intographql:masterfrom
Conversation
023aa09 to
f78ec10
Compare
f78ec10 to
19a8337
Compare
|
I found a failing case that wasn't in the original unit tests: query Test1 {
...Fragment4
...Fragment5
}
fragment Fragment5 on Test1 {
...Fragment4
}
fragment Fragment4 on Test1 {
id
}does not give me query Test1 {
...on Test1 {
id
}
...on Test1 {
...on Test1 {
id
}
}
}instead, I get: query Test1 {
...on Test1 {
id
}
...on Test1 {
id
}
}this works in the original! hmmm 🤔 |
src/utilities/mergeAST.js
Outdated
| // Collect the existing FragmentDefinition nodes. | ||
| fragmentDefinitions[node.name.value] = node; | ||
| }, | ||
| }); |
There was a problem hiding this comment.
You don't need visit here, simple for of over definitions would be enough.
| it(fixture.desc, () => { | ||
| const result = print(mergeAST(parse(fixture.query))).replace(/\s/g, ''); | ||
| expect(result).to.equal(fixture.mergedQuery.replace(/\s/g, '')); | ||
| }); |
There was a problem hiding this comment.
We don't use fixtures please unroll it here.
Use dedent to remove indentation from strings.
And defined expectMerged wrapper to not repeat print(mergeAST(parse
There was a problem hiding this comment.
Please write it like this:
function expectInlined(query) {
return expect(print(inlineNamedFragments(parse(query))));
}
describe('inlineNamedFragments', () => {
it('does not modify query with no fragments', () => {
expectInlined(dedent`
{
id
}
`).to.equal(dedent`
{
id
}
`);
});| import { expect } from 'chai'; | ||
| import { describe, it } from 'mocha'; | ||
|
|
||
| import { parse, print } from '../../index'; |
There was a problem hiding this comment.
Use direct imports from appropriate files.
src/utilities/mergeAST.js
Outdated
|
|
||
| export function mergeAST(ast: DocumentNode): DocumentNode { | ||
| const fragmentDefinitions: { [name: string]: FragmentDefinitionNode } = {}; | ||
| const inlinedFragments: { [name: string]: FragmentSpreadNode } = {}; |
There was a problem hiding this comment.
Please use Object.create(null) here and use ObjMap for types.
src/utilities/mergeAST.js
Outdated
| * Given a document AST, merge all fragment definitions into the | ||
| * provided operations using inline fragments. | ||
| */ | ||
|
|
src/utilities/mergeAST.js
Outdated
| return visit(ast, { | ||
| FragmentSpread(node) { | ||
| // No repeats! | ||
| if (inlinedFragments[node.name.value]) { |
There was a problem hiding this comment.
This is incorrect check, same fragment can be used in multiple places
{
person: {
...ProfileInfo
friend: {
...ProfileInfo
}
}
}Also please cover in tests.
There was a problem hiding this comment.
covered in unit tests now :)
I think I know the reason, I will fix this. |
19a8337 to
5d3b34a
Compare
8199f23 to
8d1d14c
Compare
8d1d14c to
997ead9
Compare
langpavel
left a comment
There was a problem hiding this comment.
Does this work well with aliases and experimental fragment variables?
| array: $ReadOnlyArray<any>, | ||
| iteratee: (item: any) => any, | ||
| ) { | ||
| const FilteredMap = new Map(); |
There was a problem hiding this comment.
Map, Set should be used only for non-string keys, Object.create(null) is perfectly fine for working with string keys.
There was a problem hiding this comment.
@IvanGoncharov Set is much better primitive for checking presence/uniqueness than Object.create(null), both by math sense and by the fact — Maps and Sets are well tested and perfectly tuned for purpose. Misusing objects as hash maps, well, it's only relique. Polyfilling is not needed today.
|
@langpavel good questions! do you mean like this: fragment UserFriends ($first: Int!, $after: Int) on User {
name
avatar
}
query {
...UserFriends(first: 100, after: null)
}I don't see why it wouldn't, since this would still require visiting the same node types, but I can add some test cases to be sure |
| return { | ||
| ...fragmentDefinitions[node.name.value], | ||
| kind: 'InlineFragment', | ||
| }; |
There was a problem hiding this comment.
Please review type definitions before you use them or convert them into each other.
Fragment definition has way more fields than inline fragments.
There was a problem hiding this comment.
for sure, adding more test cases, it can't be this simple
| ...node, | ||
| selections: uniqueBy( | ||
| node.selections, | ||
| selection => selection.name.value, |
There was a problem hiding this comment.
You also removing duplicated fields, including fields with different aliases, args, etc.
|
disabling until I've done more research |
|
I have a more thorough implementation of this here: graphql/graphiql#1542 Let me know if you're interested into a PR to GraphQL.js |
|
@benjie I think it would make sense to test it in GraphiQL to refine API and implementation and latter move to |
Accomplishes graphql/graphiql#836, originally introduced via graphql/graphiql#762 earlier this year,
mergeASTis a handy utility for in-lining fragments for complex queries.As per @IvanGoncharov's suggestion, I refactored it to use
language/visitor'svisitfunction. Good thing I'd been working with AST and visitors on another project using antlr4 earlier this year, or I would've been so lost here, haha. That said, GraphQL'svisit()is so powerful, I'm blown away at how simple this was.Thank you @ramonsaboya for the inspiration, and some solid unit tests, and an excellent idea all around.
I'm wondering if a
Setwould be a more appropriate data structure here?This is a WIP, just learning about
visit()and this is just a learning excercise for me.Not a huge priority to any part of the ecosystem currently, and a lot of important edge cases are still broken here.