Skip to content

Commit

Permalink
Add fragment warning code from Apollo client.
Browse files Browse the repository at this point in the history
Also normalize document strings (for whitespace anyway).

Note that we are simply checking fragment strings for equality, not ensuring that fragment definitions within the larger graphql document have exact equality.

We could do the above, but it'd be quite a bit more complicated, and I'm not sure if there's much benefit. AFAICT the main reason for wanting that before was exactly this functionality.
  • Loading branch information
tmeasday committed Nov 29, 2016
1 parent 7a8544c commit 51faf96
Show file tree
Hide file tree
Showing 2 changed files with 227 additions and 132 deletions.
73 changes: 67 additions & 6 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,62 @@
var parse = require('./parser').parse;

var cache = {};
// Strip insignificant whitespace
// Note that this could do a lot more, such as reorder fields etc.
function normalize(string) {
return string.replace(/[\s,]+/g, ' ').trim();
}

// A map docString -> graphql document
var docCache = {};

// A map fragmentName -> [normalized source]
var fragmentSourceMap = {};

function cacheKeyFromLoc(loc) {
return normalize(loc.source.body.substring(loc.start, loc.end));
}

// For testing.
function resetCaches() {
docCache = {};
fragmentSourceMap = {};
}

// Take a unstripped parsed document (query/mutation or even fragment), and
// check all fragment definitions, checking for name->source uniqueness
var printFragmentWarnings = true;
function checkFragments(ast) {
for (var i = 0; i < ast.definitions.length; i++) {
var fragmentDefinition = ast.definitions[i];
if (fragmentDefinition.kind === 'FragmentDefinition') {
var fragmentName = fragmentDefinition.name.value;
var sourceKey = cacheKeyFromLoc(fragmentDefinition.loc);

// We know something about this fragment
if (fragmentSourceMap.hasOwnProperty(fragmentName) &&
!fragmentSourceMap[fragmentName][sourceKey]) {

// this is a problem because the app developer is trying to register another fragment with
// the same name as one previously registered. So, we tell them about it.
if (printFragmentWarnings) {
console.warn("Warning: fragment with name " + fragmentName + " already exists.\n"
+ "graphql-tag enforces all fragment names across your application to be unique; read more about\n"
+ "this in the docs: http://dev.apollodata.com/core/fragments.html#unique-names");
}

fragmentSourceMap[fragmentName][sourceKey] = true;

} else if (!fragmentSourceMap.hasOwnProperty(fragmentName)) {
fragmentSourceMap[fragmentName] = {};
fragmentSourceMap[fragmentName][sourceKey] = true;
}
}
}
}

function disableFragmentWarnings() {
printFragmentWarnings = false;
}

function stripLoc (doc, removeLocAtThisLevel) {
var docType = Object.prototype.toString.call(doc);
Expand Down Expand Up @@ -39,19 +95,22 @@ function stripLoc (doc, removeLocAtThisLevel) {
}

function parseDocument(doc) {
if (cache[doc]) {
return cache[doc];
var cacheKey = normalize(doc);

if (docCache[cacheKey]) {
return docCache[cacheKey];
}

var parsed = parse(doc);

if (!parsed || parsed.kind !== 'Document') {
throw new Error('Not a valid GraphQL document.');
}

// check that all "new" fragments inside the documents are consistent with
// existing fragments of the same name
checkFragments(parsed);
parsed = stripLoc(parsed, false);

cache[doc] = parsed;
docCache[cacheKey] = parsed;

return parsed;
}
Expand Down Expand Up @@ -80,5 +139,7 @@ function gql(/* arguments */) {

// Support typescript, which isn't as nice as Babel about default exports
gql.default = gql;
gql.resetCaches = resetCaches;
gql.disableFragmentWarnings = disableFragmentWarnings;

module.exports = gql;
Loading

0 comments on commit 51faf96

Please sign in to comment.