Skip to content
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

add mergeAST from GraphiQL, refactor using visit() [WIP] #1948

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,8 @@ export {
isValidLiteralValue,
// Concatenates multiple AST together.
concatAST,
// Inline named fragments from AST
inlineNamedFragments,
// Separates an AST into an AST per Operation.
separateOperations,
// Strips characters that are not significant to the validity or execution
Expand Down
31 changes: 31 additions & 0 deletions src/jsutils/uniqueBy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
*/

/**
* Returns an array of unique values based on iteratee
* which is invoked for each element in array to generate
* the criterion by which uniqueness is computed.
*
* Simiar to _.uniqBy from lodash.
*/
export function uniqueBy(
array: $ReadOnlyArray<any>,
iteratee: (item: any) => any,
) {
const FilteredMap = new Map();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not Set?

Copy link
Member Author

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

Copy link
Member

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.

Copy link
Contributor

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.

const result = [];
for (const item of array) {
const uniqeValue = iteratee(item);
if (!FilteredMap.has(uniqeValue)) {
FilteredMap.set(uniqeValue, true);
result.push(item);
}
}
return result;
}
189 changes: 189 additions & 0 deletions src/utilities/__tests__/inlineNamedFragments-fixture.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
*
*/

export const fixtures = [
{
desc: 'does not modify query with no fragments',
query: `
{
id
}`,
resultQuery: `
{
id
}
`,
},
{
desc: 'inlines simple nested fragment',
query: `
{
...Fragment1
}

fragment Fragment1 on Test {
id
}`,
resultQuery: `
{
... on Test {
id
}
}
`,
},
{
desc: 'inlines triple nested fragment',
query: `
{
...Fragment1
}

fragment Fragment1 on Test {
...Fragment2
}

fragment Fragment2 on Test {
...Fragment3
}

fragment Fragment3 on Test {
id
}`,
resultQuery: `
{
... on Test {
... on Test {
... on Test {
id
}
}
}
}
`,
},
{
desc: 'inlines multiple fragments',
query: `
{
...Fragment1
...Fragment2
...Fragment3
}

fragment Fragment1 on Test {
id
}

fragment Fragment2 on Test {
id
}

fragment Fragment3 on Test {
id
}`,
resultQuery: `
{
... on Test {
id
}
... on Test {
id
}
... on Test {
id
}
}
`,
},
{
desc: 'inlines multiple fragments on multiple queries',
query: `
{
...Fragment4
...Fragment5
}

fragment Fragment5 on Test1 {
...Fragment4
}

fragment Fragment4 on Test1 {
id
}`,
resultQuery: `
{
... on Test1 {
id
}
... on Test1 {
... on Test1 {
id
}
}
}
`,
},
{
desc: 'reuses the same fragment',
query: `
fragment ProfileInfo on Person {
name
title
phone
}

{
person {
...ProfileInfo
friend {
...ProfileInfo
}
}
}`,
resultQuery: `
{
person {
... on Person {
name
title
phone
}
friend {
... on Person {
name
title
phone
}
}
}
}
`,
},
{
desc: 'removes duplicate fragment spreads',
query: `
{
...Fragment1
...Fragment1
}

fragment Fragment1 on Test {
id
}`,
resultQuery: `
{
... on Test {
id
}
}
`,
},
];
25 changes: 25 additions & 0 deletions src/utilities/__tests__/inlineNamedFragments-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
*/

import { expect } from 'chai';
import { describe, it } from 'mocha';
import dedent from '../../jsutils/dedent';
import { parse } from '../../language/parser';
import { print } from '../../language/printer';
import { inlineNamedFragments } from '../inlineNamedFragments';
import { fixtures } from './inlineNamedFragments-fixture';

describe('inlineNamedFragments', () => {
fixtures.forEach(fixture => {
it(fixture.desc, () => {
const result = print(inlineNamedFragments(parse(fixture.query)));
expect(result).to.equal(dedent(fixture.resultQuery));
});
});
});
3 changes: 3 additions & 0 deletions src/utilities/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ export { isValidLiteralValue } from './isValidLiteralValue';
// Concatenates multiple AST together.
export { concatAST } from './concatAST';

// Inline named fragments from AST
export { inlineNamedFragments } from './inlineNamedFragments';

// Separates an AST into an AST per Operation.
export { separateOperations } from './separateOperations';

Expand Down
52 changes: 52 additions & 0 deletions src/utilities/inlineNamedFragments.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
*/

import { type ObjMap } from '../jsutils/ObjMap';
import { uniqueBy } from '../jsutils/uniqueBy';
import { visit } from '../language/visitor';
import {
type DocumentNode,
type FragmentDefinitionNode,
} from '../language/ast';

/**
* Given a document AST, inline all named fragment definitions
*/
export function inlineNamedFragments(documentAST: DocumentNode): DocumentNode {
const fragmentDefinitions: ObjMap<FragmentDefinitionNode> = Object.create(
null,
);

for (const definition of documentAST.definitions) {
if (definition.kind === 'FragmentDefinition') {
fragmentDefinitions[definition.name.value] = definition;
}
}

return visit(documentAST, {
FragmentSpread(node) {
return {
...fragmentDefinitions[node.name.value],
kind: 'InlineFragment',
};
Copy link
Member

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.

Copy link
Member Author

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

},
SelectionSet(node) {
return {
...node,
selections: uniqueBy(
node.selections,
selection => selection.name.value,
Copy link
Member

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.

),
};
},
FragmentDefinition() {
return null;
},
});
}