Skip to content

Commit

Permalink
track discarded values of conflicting types and log them
Browse files Browse the repository at this point in the history
  • Loading branch information
pieh committed Feb 8, 2018
1 parent 720b16f commit fddc563
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 7 deletions.
2 changes: 2 additions & 0 deletions packages/gatsby/src/bootstrap/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,8 @@ module.exports = async (args: BootstrapArgs) => {
await require(`../schema`)()
activity.end()

require(`../schema/data-tree-utils`).printTypeConflicts()

// Extract queries
activity = report.activityTimer(`extract queries from components`)
activity.start()
Expand Down
95 changes: 88 additions & 7 deletions packages/gatsby/src/schema/data-tree-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
const _ = require(`lodash`)
const flatten = require(`flat`)
const typeOf = require(`type-of`)
const report = require(`gatsby-cli/lib/reporter`)

const createKey = require(`./create-key`)

Expand Down Expand Up @@ -37,6 +38,74 @@ const isEmptyObjectOrArray = (obj: any): boolean => {
return false
}

const getMeaningfulTypeName = value => {
if (_.isArray(value)) {
const uniqueTypes = _.uniq(
value.map(item => getMeaningfulTypeName(item))
).sort()
return `array<${uniqueTypes.join(`|`)}>`
} else {
return typeof value
}
}

class TypeConflictEntry {
constructor(selector) {
this.selector = selector
this.types = []
}

addValue(value) {
const typeName = getMeaningfulTypeName(value)
if (!_.includes(this.types, typeName)) {
this.types.push(typeName)
}
}

printEntry() {
report.log(`${this.selector}: ${this.types.sort().join(`, `)}`)
}
}

class TypeConflictVault {
constructor() {
this.entries = {}
}

_getFromSelector(selector) {
if (this.entries[selector]) {
return this.entries[selector]
}

const dataEntry = new TypeConflictEntry(selector)
this.entries[selector] = dataEntry
return dataEntry
}

addConflict(selector, ...values) {
const entry = this._getFromSelector(selector)
values
.filter(value => typeof value !== `undefined`)
.forEach(value => entry.addValue(value))
}

printConflicts() {
const entries = _.values(this.entries)
if (entries.length > 0) {
report.warn(
`There are conflicting field types in your data. GraphQL schema will omit those fields.`
)
entries.forEach(entry => entry.printEntry())
}
}
}

const typeConflictVault = new TypeConflictVault()

const printTypeConflicts = () => {
typeConflictVault.printConflicts()
}

/**
* Takes an array of source nodes and returns a pristine
* example that can be used to infer types.
Expand All @@ -56,20 +125,26 @@ const extractFieldExamples = (nodes: any[], selector: ?string) =>
_.isArray(nodes[0]) ? [] : {},
..._.clone(nodes),
(obj, next, key, po, pn, stack) => {
if (obj === INVALID_VALUE) return obj
const nextSelector = selector && `${selector}.${key}`
if (obj === INVALID_VALUE) {
if (nextSelector && next) {
typeConflictVault.addConflict(nextSelector, next)
}
return obj
}

// TODO: if you want to support infering Union types this should be handled
// differently. Maybe merge all like types into examples for each type?
// e.g. union: [1, { foo: true }, ['brown']] -> Union Int|Object|List
if (!isSameType(obj, next)) {
if (nextSelector) {
typeConflictVault.addConflict(nextSelector, obj, next)
}
return INVALID_VALUE
}

if (_.isPlainObject(obj || next)) {
return extractFieldExamples(
[obj, next],
selector && `${selector}.${key}`
)
return extractFieldExamples([obj, next], nextSelector)
}

if (!_.isArray(obj || next)) {
Expand All @@ -83,7 +158,12 @@ const extractFieldExamples = (nodes: any[], selector: ?string) =>
let array = [].concat(obj, next).filter(isDefined)

if (!array.length) return null
if (!areAllSameType(array)) return INVALID_VALUE
if (!areAllSameType(array)) {
if (nextSelector) {
typeConflictVault.addConflict(nextSelector, obj, next)
}
return INVALID_VALUE
}

// Linked node arrays don't get reduced further as we
// want to preserve all the linked node types.
Expand All @@ -95,7 +175,7 @@ const extractFieldExamples = (nodes: any[], selector: ?string) =>
if (!_.isObject(array[0])) return array.slice(0, 1)
let merged = extractFieldExamples(
array,
selector && `${selector}.${key}[]`
nextSelector && `${nextSelector}[]`
)
return isDefined(merged) ? [merged] : null
}
Expand Down Expand Up @@ -135,4 +215,5 @@ module.exports = {
buildFieldEnumValues,
extractFieldNames,
isEmptyObjectOrArray,
printTypeConflicts,
}

0 comments on commit fddc563

Please sign in to comment.