eslint-plugin-graphql
Advanced tools
Comparing version 2.1.1 to 3.0.0
# Change log | ||
### vNEXT | ||
### v3.0.0 | ||
- BREAKING: The `required-fields` rule has been significantly changed to make it a completely reliable method of ensuring an `id` field (or any other field name) is always requested when available. [PR #199](https://github.com/apollographql/eslint-plugin-graphql/pull/199) Here is the behavior, let's say we are requiring field `id`: | ||
- On any field whose return type defines a field called `id`, the selection set must directly contain `id`. | ||
- In any named fragment declaration whose type defines a field called `id`, the selection set must directly contain `id`. | ||
- An inline fragment whose type defines a field called `id` must contain `id` in its selection set unless its parent is also an inline fragment that contains the field `id`. | ||
- Here's a specific case which is _no longer valid_: | ||
- `query { greetings { hello ... on Greetings { id } } }` | ||
- This must now be written as `query { greetings { id hello ... on Greetings { id } } }` | ||
- This is a more conservative approach than before, driven by the fact that it's quite hard to ensure that a combination of inline fragments actually covers all of the possible types of a selection set. | ||
- Fix breaking change in `graphql@^14.0.0` that renamed `ProvidedNonNullArguments` to `ProvidedRequiredArguments` [#192](https://github.com/apollographql/eslint-plugin-graphql/pull/192) | ||
- Update dependencies to graphql-tools 4 and eslint 5.9 [#193](https://github.com/apollographql/eslint-plugin-graphql/pull/193) | ||
### v2.1.1 | ||
- Fix support for InlineFragments with the `required-fields` rule in [#140](https://github.com/apollographql/eslint-plugin-graphql/pull/140/files) by [Steve Hollaar](https://github.com/stevehollaar) | ||
- Fix error location information for literal .graphql files and strings with leading newlines in [#122](https://github.com/apollographql/eslint-plugin-graphql/pull/122) by [Dan Freeman](https://github.com/dfreeman) | ||
- Add [`fraql`](https://github.com/smooth-code/fraql) environment | ||
@@ -8,0 +22,0 @@ ### v2.1.0 |
272
lib/index.js
@@ -24,6 +24,10 @@ 'use strict'; | ||
var _rules = require('./rules'); | ||
var _customGraphQLValidationRules = require('./customGraphQLValidationRules'); | ||
var customRules = _interopRequireWildcard(_rules); | ||
var customRules = _interopRequireWildcard(_customGraphQLValidationRules); | ||
var _constants = require('./constants'); | ||
var _createRule = require('./createRule'); | ||
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } | ||
@@ -43,7 +47,11 @@ | ||
lokka: (0, _lodash.without)(allGraphQLValidatorNames, 'KnownFragmentNames', 'NoUnusedFragments'), | ||
relay: (0, _lodash.without)(allGraphQLValidatorNames, 'KnownDirectives', 'KnownFragmentNames', 'NoUndefinedVariables', 'NoUnusedFragments', 'ProvidedNonNullArguments', 'ScalarLeafs'), | ||
fraql: (0, _lodash.without)(allGraphQLValidatorNames, 'KnownFragmentNames', 'NoUnusedFragments'), | ||
relay: (0, _lodash.without)(allGraphQLValidatorNames, 'KnownDirectives', 'KnownFragmentNames', 'NoUndefinedVariables', 'NoUnusedFragments', | ||
// `graphql` < 14 | ||
'ProvidedNonNullArguments', | ||
// `graphql`@14 | ||
'ProvidedRequiredArguments', 'ScalarLeafs'), | ||
literal: (0, _lodash.without)(allGraphQLValidatorNames, 'KnownFragmentNames', 'NoUnusedFragments') | ||
}; | ||
var internalTag = 'ESLintPluginGraphQLFile'; | ||
var gqlFiles = ['gql', 'graphql']; | ||
@@ -53,3 +61,3 @@ | ||
env: { | ||
enum: ['lokka', 'relay', 'apollo', 'literal'] | ||
enum: ['lokka', 'fraql', 'relay', 'apollo', 'literal'] | ||
}, | ||
@@ -72,90 +80,5 @@ schemaJson: { | ||
} | ||
}; | ||
function createRule(context, optionParser) { | ||
var tagNames = new Set(); | ||
var tagRules = []; | ||
var options = context.options.length === 0 ? [{}] : context.options; | ||
var _iteratorNormalCompletion = true; | ||
var _didIteratorError = false; | ||
var _iteratorError = undefined; | ||
try { | ||
var _loop = function _loop() { | ||
var optionGroup = _step.value; | ||
var _optionParser = optionParser(optionGroup), | ||
schema = _optionParser.schema, | ||
env = _optionParser.env, | ||
tagName = _optionParser.tagName, | ||
validators = _optionParser.validators; | ||
var boundValidators = validators.map(function (v) { | ||
return function (ctx) { | ||
return v(ctx, optionGroup); | ||
}; | ||
}); | ||
if (tagNames.has(tagName)) { | ||
throw new Error('Multiple options for GraphQL tag ' + tagName); | ||
} | ||
tagNames.add(tagName); | ||
tagRules.push({ schema: schema, env: env, tagName: tagName, validators: boundValidators }); | ||
}; | ||
for (var _iterator = options[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { | ||
_loop(); | ||
} | ||
} catch (err) { | ||
_didIteratorError = true; | ||
_iteratorError = err; | ||
} finally { | ||
try { | ||
if (!_iteratorNormalCompletion && _iterator.return) { | ||
_iterator.return(); | ||
} | ||
} finally { | ||
if (_didIteratorError) { | ||
throw _iteratorError; | ||
} | ||
} | ||
} | ||
return { | ||
TaggedTemplateExpression: function TaggedTemplateExpression(node) { | ||
var _iteratorNormalCompletion2 = true; | ||
var _didIteratorError2 = false; | ||
var _iteratorError2 = undefined; | ||
try { | ||
for (var _iterator2 = tagRules[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { | ||
var _ref2 = _step2.value; | ||
var schema = _ref2.schema, | ||
env = _ref2.env, | ||
tagName = _ref2.tagName, | ||
validators = _ref2.validators; | ||
if (templateExpressionMatchesTag(tagName, node)) { | ||
return handleTemplateTag(node, context, schema, env, validators); | ||
} | ||
} | ||
} catch (err) { | ||
_didIteratorError2 = true; | ||
_iteratorError2 = err; | ||
} finally { | ||
try { | ||
if (!_iteratorNormalCompletion2 && _iterator2.return) { | ||
_iterator2.return(); | ||
} | ||
} finally { | ||
if (_didIteratorError2) { | ||
throw _iteratorError2; | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
} | ||
// schemaJson, schemaJsonFilepath, schemaString and projectName are mutually exclusive: | ||
var schemaPropsExclusiveness = { | ||
// schemaJson, schemaJsonFilepath, schemaString and projectName are mutually exclusive: | ||
};var schemaPropsExclusiveness = { | ||
oneOf: [{ | ||
@@ -201,3 +124,3 @@ required: ['schemaJson'], | ||
create: function create(context) { | ||
return createRule(context, function (optionGroup) { | ||
return (0, _createRule.createRule)(context, function (optionGroup) { | ||
return parseOptions(optionGroup, context); | ||
@@ -218,3 +141,3 @@ }); | ||
create: function create(context) { | ||
return createRule(context, function (optionGroup) { | ||
return (0, _createRule.createRule)(context, function (optionGroup) { | ||
return parseOptions(_extends({ | ||
@@ -246,3 +169,3 @@ validators: ['OperationsMustHaveNames'] | ||
create: function create(context) { | ||
return createRule(context, function (optionGroup) { | ||
return (0, _createRule.createRule)(context, function (optionGroup) { | ||
return parseOptions(_extends({ | ||
@@ -266,3 +189,3 @@ validators: ['RequiredFields'], | ||
create: function create(context) { | ||
return createRule(context, function (optionGroup) { | ||
return (0, _createRule.createRule)(context, function (optionGroup) { | ||
return parseOptions(_extends({ | ||
@@ -285,3 +208,3 @@ validators: ['typeNamesShouldBeCapitalized'] | ||
create: function create(context) { | ||
return createRule(context, function (optionGroup) { | ||
return (0, _createRule.createRule)(context, function (optionGroup) { | ||
return parseOptions(_extends({ | ||
@@ -341,4 +264,4 @@ validators: ['noDeprecatedFields'] | ||
// Validate env | ||
if (env && env !== 'lokka' && env !== 'relay' && env !== 'apollo' && env !== 'literal') { | ||
throw new Error('Invalid option for env, only `apollo`, `lokka`, `relay`, and `literal` supported.'); | ||
if (env && env !== 'lokka' && env !== 'fraql' && env !== 'relay' && env !== 'apollo' && env !== 'literal') { | ||
throw new Error('Invalid option for env, only `apollo`, `lokka`, `fraql`, `relay`, and `literal` supported.'); | ||
} | ||
@@ -353,3 +276,3 @@ | ||
} else if (env === 'literal') { | ||
tagName = internalTag; | ||
tagName = _constants.internalTag; | ||
} else { | ||
@@ -400,149 +323,2 @@ tagName = 'gql'; | ||
function templateExpressionMatchesTag(tagName, node) { | ||
var tagNameSegments = tagName.split('.').length; | ||
if (tagNameSegments === 1) { | ||
// Check for single identifier, like 'gql' | ||
if (node.tag.type !== 'Identifier' || node.tag.name !== tagName) { | ||
return false; | ||
} | ||
} else if (tagNameSegments === 2) { | ||
// Check for dotted identifier, like 'Relay.QL' | ||
if (node.tag.type !== 'MemberExpression' || node.tag.object.name + '.' + node.tag.property.name !== tagName) { | ||
return false; | ||
} | ||
} else { | ||
// We don't currently support 3 segments so ignore | ||
return false; | ||
} | ||
return true; | ||
} | ||
function handleTemplateTag(node, context, schema, env, validators) { | ||
var text = void 0; | ||
try { | ||
text = replaceExpressions(node.quasi, context, env); | ||
} catch (e) { | ||
if (e.message !== 'Invalid interpolation') { | ||
console.log(e); | ||
} | ||
return; | ||
} | ||
// Re-implement syntax sugar for fragment names, which is technically not valid | ||
// graphql | ||
if ((env === 'lokka' || env === 'relay') && /fragment\s+on/.test(text)) { | ||
text = text.replace('fragment', 'fragment _'); | ||
} | ||
var ast = void 0; | ||
try { | ||
ast = (0, _graphql.parse)(text); | ||
} catch (error) { | ||
context.report({ | ||
node: node, | ||
message: error.message.split('\n')[0], | ||
loc: locFrom(node, error) | ||
}); | ||
return; | ||
} | ||
var validationErrors = schema ? (0, _graphql.validate)(schema, ast, validators) : []; | ||
if (validationErrors && validationErrors.length > 0) { | ||
context.report({ | ||
node: node, | ||
message: validationErrors[0].message, | ||
loc: locFrom(node, validationErrors[0]) | ||
}); | ||
return; | ||
} | ||
} | ||
function locFrom(node, error) { | ||
if (!error.locations || !error.locations.length) { | ||
return; | ||
} | ||
var location = error.locations[0]; | ||
var line = void 0; | ||
var column = void 0; | ||
if (location.line === 1 && node.tag.name !== internalTag) { | ||
line = node.loc.start.line; | ||
column = node.tag.loc.end.column + location.column; | ||
} else { | ||
line = node.loc.start.line + location.line - 1; | ||
column = location.column - 1; | ||
} | ||
return { | ||
line: line, | ||
column: column | ||
}; | ||
} | ||
function replaceExpressions(node, context, env) { | ||
var chunks = []; | ||
node.quasis.forEach(function (element, i) { | ||
var chunk = element.value.cooked; | ||
var value = node.expressions[i]; | ||
chunks.push(chunk); | ||
if (!env || env === 'apollo') { | ||
// In Apollo, interpolation is only valid outside top-level structures like `query` or `mutation`. | ||
// We'll check to make sure there's an equivalent set of opening and closing brackets, otherwise | ||
// we're attempting to do an invalid interpolation. | ||
if (chunk.split('{').length - 1 !== chunk.split('}').length - 1) { | ||
context.report({ | ||
node: value, | ||
message: 'Invalid interpolation - fragment interpolation must occur outside of the brackets.' | ||
}); | ||
throw new Error('Invalid interpolation'); | ||
} | ||
} | ||
if (!element.tail) { | ||
// Preserve location of errors by replacing with exactly the same length | ||
var nameLength = value.end - value.start; | ||
if (env === 'relay' && /:\s*$/.test(chunk)) { | ||
// The chunk before this one had a colon at the end, so this | ||
// is a variable | ||
// Add 2 for brackets in the interpolation | ||
var placeholder = strWithLen(nameLength + 2); | ||
chunks.push('$' + placeholder); | ||
} else if (env === 'lokka' && /\.\.\.\s*$/.test(chunk)) { | ||
// This is Lokka-style fragment interpolation where you actually type the '...' yourself | ||
var _placeholder = strWithLen(nameLength + 3); | ||
chunks.push(_placeholder); | ||
} else if (env === 'relay') { | ||
// This is Relay-style fragment interpolation where you don't type '...' | ||
// Ellipsis cancels out extra characters | ||
var _placeholder2 = strWithLen(nameLength); | ||
chunks.push('...' + _placeholder2); | ||
} else if (!env || env === 'apollo') { | ||
// In Apollo, fragment interpolation is only valid outside of brackets | ||
// Since we don't know what we'd interpolate here (that occurs at runtime), | ||
// we're not going to do anything with this interpolation. | ||
} else { | ||
// Invalid interpolation | ||
context.report({ | ||
node: value, | ||
message: 'Invalid interpolation - not a valid fragment or variable.' | ||
}); | ||
throw new Error('Invalid interpolation'); | ||
} | ||
} | ||
}); | ||
return chunks.join(''); | ||
} | ||
function strWithLen(len) { | ||
// from http://stackoverflow.com/questions/14343844/create-a-string-of-variable-length-filled-with-a-repeated-character | ||
return new Array(len + 1).join('x'); | ||
} | ||
var gqlProcessor = { | ||
@@ -560,3 +336,3 @@ preprocess: function preprocess(text) { | ||
var escaped = text.replace(/[`\\]|\$\{/g, '\\$&'); | ||
return [internalTag + '`' + escaped + '`']; | ||
return [_constants.internalTag + '`' + escaped + '`']; | ||
}, | ||
@@ -563,0 +339,0 @@ postprocess: function postprocess(messages) { |
{ | ||
"name": "eslint-plugin-graphql", | ||
"version": "2.1.1", | ||
"version": "3.0.0", | ||
"description": "GraphQL ESLint plugin.", | ||
@@ -10,3 +10,5 @@ "author": "Sashko Stubailo", | ||
"prepublish": "babel ./src --ignore test --out-dir ./lib", | ||
"pretest": "node test/updateSchemaJson.js" | ||
"pretest": "node test/updateSchemaJson.js", | ||
"tav": "tav", | ||
"lint": "eslint 'src/**/*.js' 'test/**/*.js'" | ||
}, | ||
@@ -19,14 +21,13 @@ "homepage": "https://github.com/apollostack/eslint-plugin-graphql", | ||
"devDependencies": { | ||
"babel": "6.23.0", | ||
"babel-cli": "6.26.0", | ||
"babel-core": "6.26.3", | ||
"babel-eslint": "8.2.3", | ||
"babel-eslint": "10.0.1", | ||
"babel-plugin-transform-runtime": "6.23.0", | ||
"babel-preset-es2015": "6.24.1", | ||
"babel-preset-stage-0": "6.24.1", | ||
"eslint": "4.19.1", | ||
"mocha": "5.1.1", | ||
"graphql": "0.13.2", | ||
"graphql-tools": "3.0.0", | ||
"test-all-versions": "3.3.2" | ||
"eslint": "5.9.0", | ||
"graphql": "14.0.2", | ||
"graphql-tools": "4.0.3", | ||
"mocha": "5.2.0", | ||
"test-all-versions": "3.3.3" | ||
}, | ||
@@ -48,4 +49,4 @@ "babel": { | ||
"peerDependencies": { | ||
"graphql": "^0.12.0 || ^0.13.0" | ||
"graphql": "^0.12.0 || ^0.13.0 || ^14.0.0" | ||
} | ||
} |
@@ -19,2 +19,3 @@ # eslint-plugin-graphql | ||
3. [Lokka](https://github.com/kadirahq/lokka) | ||
3. [FraQL](https://github.com/smooth-code/fraql) | ||
@@ -35,5 +36,5 @@ If you want to lint your GraphQL schema, rather than queries, check out [cjoudrey/graphql-schema-linter](https://github.com/cjoudrey/graphql-schema-linter). | ||
All of the rules provided by this plugin have a few options in common. There are examples of how to use these with Apollo, Relay, Lokka and literal files further down. | ||
All of the rules provided by this plugin have a few options in common. There are examples of how to use these with Apollo, Relay, Lokka, FraQL and literal files further down. | ||
- `env`: Import default settings for your GraphQL client. Supported values: `'apollo'`, `'relay'`, `'lokka'`, `'literal'`. Defaults to `'apollo'`. This is used for the slight parsing differences in the GraphQL syntax between Apollo, Relay and Lokka, as well as giving nice defaults to some other options. | ||
- `env`: Import default settings for your GraphQL client. Supported values: `'apollo'`, `'relay'`, `'lokka'`, `'fraql'` `'literal'`. Defaults to `'apollo'`. This is used for the slight parsing differences in the GraphQL syntax between Apollo, Relay, Lokka and FraQL as well as giving nice defaults to some other options. | ||
@@ -44,3 +45,3 @@ - `tagName`: The name of the template literal tag that this plugin should look for when searching for GraphQL queries. It has different defaults depending on the `env` option: | ||
- `'internal'`: Special automatic value | ||
- others: `'gql'` | ||
- others: `'gql'`, `'graphql'` | ||
@@ -50,3 +51,3 @@ You also have to specify a schema. You can either do it using _one_ of these options: | ||
- `schemaJson`: Your schema as JSON. | ||
- `schemaJsonFilepath`: The absolute path to your schema as a .json file. | ||
- `schemaJsonFilepath`: The absolute path to your schema as a .json file. (Warning: this variant is incompatible with `eslint --cache`.) | ||
- `schemaString`: Your schema in the Schema Language format as a string. | ||
@@ -103,3 +104,3 @@ | ||
// Import default settings for your GraphQL client. Supported values: | ||
// 'apollo', 'relay', 'lokka', 'literal' | ||
// 'apollo', 'relay', 'lokka', 'fraql', 'literal' | ||
env: 'apollo', | ||
@@ -110,3 +111,3 @@ | ||
// OR provide absolute path to your schema JSON | ||
// OR provide absolute path to your schema JSON (but not if using `eslint --cache`!) | ||
// schemaJsonFilepath: path.resolve(__dirname, './schema.json'), | ||
@@ -135,3 +136,3 @@ | ||
// Import default settings for your GraphQL client. Supported values: | ||
// 'apollo', 'relay', 'lokka', 'literal' | ||
// 'apollo', 'relay', 'lokka', 'fraql', 'literal' | ||
env: 'relay', | ||
@@ -142,3 +143,3 @@ | ||
// OR provide absolute path to your schema JSON | ||
// OR provide absolute path to your schema JSON (but not if using `eslint --cache`!) | ||
// schemaJsonFilepath: path.resolve(__dirname, './schema.json'), | ||
@@ -167,3 +168,3 @@ | ||
// Import default settings for your GraphQL client. Supported values: | ||
// 'apollo', 'relay', 'lokka', 'literal' | ||
// 'apollo', 'relay', 'lokka', 'fraql', 'literal' | ||
env: 'lokka', | ||
@@ -174,2 +175,33 @@ | ||
// OR provide absolute path to your schema JSON (but not if using `eslint --cache`!) | ||
// schemaJsonFilepath: path.resolve(__dirname, './schema.json'), | ||
// OR provide the schema in the Schema Language format | ||
// schemaString: printSchema(schema), | ||
// Optional, the name of the template tag, defaults to 'gql' | ||
tagName: 'gql' | ||
}] | ||
}, | ||
plugins: [ | ||
'graphql' | ||
] | ||
} | ||
``` | ||
### Example config for FraQL | ||
```js | ||
// In a file called .eslintrc.js | ||
module.exports = { | ||
parser: "babel-eslint", | ||
rules: { | ||
"graphql/template-strings": ['error', { | ||
// Import default settings for your GraphQL client. Supported values: | ||
// 'apollo', 'relay', 'lokka', 'fraql', 'literal' | ||
env: 'fraql', | ||
// Import your schema JSON here | ||
schemaJson: require('./schema.json'), | ||
// OR provide absolute path to your schema JSON | ||
@@ -200,3 +232,3 @@ // schemaJsonFilepath: path.resolve(__dirname, './schema.json'), | ||
// Import default settings for your GraphQL client. Supported values: | ||
// 'apollo', 'relay', 'lokka', 'literal' | ||
// 'apollo', 'relay', 'lokka', 'fraql', 'literal' | ||
env: 'literal', | ||
@@ -207,3 +239,3 @@ | ||
// OR provide absolute path to your schema JSON | ||
// OR provide absolute path to your schema JSON (but not if using `eslint --cache`!) | ||
// schemaJsonFilepath: path.resolve(__dirname, './schema.json'), | ||
@@ -259,3 +291,3 @@ | ||
// Import default settings for your GraphQL client. Supported values: | ||
// 'apollo', 'relay', 'lokka', 'literal' | ||
// 'apollo', 'relay', 'lokka', 'fraql', 'literal' | ||
env: 'literal' | ||
@@ -334,3 +366,3 @@ // no need to specify schema here, it will be automatically determined using .graphqlconfig | ||
- `PossibleFragmentSpreads` | ||
- `ProvidedNonNullArguments` (*disabled by default in `relay`*) | ||
- `ProvidedRequiredArguments` (*disabled by default in `relay`*) | ||
- `ScalarLeafs` (*disabled by default in `relay`*) | ||
@@ -337,0 +369,0 @@ - `SingleFieldSubscriptions` |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
103611
11
16
765
584