- Notifications
You must be signed in to change notification settings - Fork 2.1k
add mergeAST from GraphiQL, refactor using visit() [WIP] #1948
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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. | ||
| */ | ||
| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove empty line
src/utilities/mergeAST.js Outdated
| return visit(ast, { | ||
| FragmentSpread(node) { | ||
| // No repeats! | ||
| if (inlinedFragments[node.name.value]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not Set?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Set makes a lot more sense
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.