Skip to content

Commit

Permalink
Add env option to required-fields rule config (apollographql#75)
Browse files Browse the repository at this point in the history
* Add env option to required-fields rule config

Allows .graphql and .gql literal files to be linted with the required-fields rule.

* Attempting to test literal processing

* Removing console.log

* Fixing rule configuration, adding parser options

* Switching back to single quotes

* Remove `.only`
  • Loading branch information
Justin Schulz authored and jnwng committed Jul 18, 2017
1 parent a474a6a commit 17c39d5
Show file tree
Hide file tree
Showing 11 changed files with 105 additions and 18 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ node_modules
lib
test/schema.json
test/second-schema.json
.vscode/
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Change log

### vNEXT
- Add env config option for required-fields rule [Justin Schulz](https://github.com/PepperTeasdale) in [#75](https://github.com/apollographql/eslint-plugin-graphql/pull/75)

### v1.1.0
- Add option to pass schema as string [Christopher Cliff](https://github.com/christophercliff) in [#78](https://github.com/apollographql/eslint-plugin-graphql/pull/78)
Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,12 +234,12 @@ The full list of available validators is:
- `FragmentsOnCompositeTypes`
- `KnownArgumentNames`
- `KnownDirectives` (*disabled by default in `relay`*)
- `KnownFragmentNames` (*disabled by default in `apollo`, `lokka`, and `relay`*)
- `KnownFragmentNames` (*disabled by default in all envs*)
- `KnownTypeNames`
- `LoneAnonymousOperation`
- `NoFragmentCycles`
- `NoUndefinedVariables` (*disabled by default in `relay`*)
- `NoUnusedFragments` (*disabled by default in `apollo`, `lokka`, and `relay`*)
- `NoUnusedFragments` (*disabled by default in all envs*)
- `NoUnusedVariables`
- `OverlappingFieldsCanBeMerged`
- `PossibleFragmentSpreads`
Expand Down Expand Up @@ -359,7 +359,7 @@ query ViewerName {
}
```

The rule is defined as `graphql/required-fields` and requires a `schema` and `requiredFields`, with an optional `tagName`.
The rule is defined as `graphql/required-fields` and requires a `schema` and `requiredFields`, with an optional `tagName` and `env`.

```js
// In a file called .eslintrc.js
Expand All @@ -368,7 +368,8 @@ module.exports = {
'graphql/required-fields': [
'error',
{
schemaJsonFilepath: require('./schema.json'),
env: 'apollo',
schemaJsonFilepath: path.resolve(__dirname, './schema.json'),
requiredFields: ['uuid'],
},
],
Expand Down
19 changes: 14 additions & 5 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ function createRule(context, optionParser) {
tagNames.add(tagName);
tagRules.push({schema, env, tagName, validators: boundValidators});
}

return {
TaggedTemplateExpression(node) {
for (const {schema, env, tagName, validators} of tagRules) {
Expand All @@ -86,7 +87,7 @@ function createRule(context, optionParser) {
};
}

const rules = {
export const rules = {
'template-strings': {
meta: {
schema: {
Expand Down Expand Up @@ -171,6 +172,14 @@ const rules = {
additionalProperties: false,
properties: {
...defaultRuleProperties,
env: {
enum: [
'lokka',
'relay',
'apollo',
'literal',
],
},
requiredFields: {
type: 'array',
items: {
Expand Down Expand Up @@ -444,11 +453,11 @@ const gqlProcessor = {
}
}

const processors = reduce(gqlFiles, (result, value) => {
export const processors = reduce(gqlFiles, (result, value) => {
return { ...result, [`.${value}`]: gqlProcessor };
}, {})

module.exports = {
rules,
export default {
rules,
processors
};
}
1 change: 1 addition & 0 deletions test/__fixtures__/required-fields-invalid-array.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
query { stories { comments { text } } }
1 change: 1 addition & 0 deletions test/__fixtures__/required-fields-invalid-no-id.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
query { greetings { hello } }
1 change: 1 addition & 0 deletions test/__fixtures__/required-fields-valid-array.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
query { stories { id comments { text } } }
1 change: 1 addition & 0 deletions test/__fixtures__/required-fields-valid-id.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
query { greetings { id, hello, foo } }
1 change: 1 addition & 0 deletions test/__fixtures__/required-fields-valid-no-id.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
query { allFilms { films { title } } }
73 changes: 65 additions & 8 deletions test/makeProcessors.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,38 @@
import assert from 'assert';
import {
includes,
keys,
} from 'lodash';
import { CLIEngine } from 'eslint';
import { includes, keys } from 'lodash';
import path from 'path';

import { processors } from '../src';
import schemaJson from './schema.json';
import plugin, { processors } from '../src';

function execute(file) {
const cli = new CLIEngine({
extensions: ['.gql', '.graphql'],
baseConfig: {
rules: {
'graphql/required-fields': [
'error',
{
schemaJson,
env: 'literal',
requiredFields: ['id']
}
]
}
},
ignore: false,
useEslintrc: false,
parserOptions: {
ecmaVersion: 6,
sourceType: 'module'
}
});
cli.addPlugin('eslint-plugin-graphql', plugin);
return cli.executeOnFiles([
path.join(__dirname, '__fixtures__', `${file}.graphql`)
]);
}

describe('processors', () => {
it('should define processors', () => {
Expand All @@ -15,8 +43,8 @@ describe('processors', () => {
});

it('should escape backticks and prepend internalTag', () => {
const query = 'query { someValueWith` }'
const expected = 'ESLintPluginGraphQLFile`query { someValueWith\\` }`'
const query = 'query { someValueWith` }';
const expected = 'ESLintPluginGraphQLFile`query { someValueWith\\` }`';
const preprocess = processors['.gql'].preprocess;
const result = preprocess(query);

Expand All @@ -28,7 +56,7 @@ describe('processors', () => {
{ ruleId: 'no-undef' },
{ ruleId: 'semi' },
{ ruleId: 'graphql/randomString' },
{ ruleId: 'graphql/template-strings' },
{ ruleId: 'graphql/template-strings' }
];
const expected = { ruleId: 'graphql/template-strings' };
const postprocess = processors['.gql'].postprocess;
Expand All @@ -37,4 +65,33 @@ describe('processors', () => {
assert.equal(result.length, 1);
assert.equal(result[0].ruleId, expected.ruleId);
});

describe('graphql/required-fields', () => {
describe('valid', () => {
[
'required-fields-valid-no-id',
'required-fields-valid-id',
'required-fields-valid-array'
].forEach(filename => {
it(`does not warn on file ${filename}`, () => {
const results = execute(filename);
assert.equal(results.errorCount, 0);
});
});
});

describe('invalid', () => {
[
'required-fields-invalid-no-id',
'required-fields-invalid-array'
].forEach(filename => {
it(`warns on file ${filename}`, () => {
const results = execute(filename);
assert.equal(results.errorCount, 1);
const message = results.results[0].messages[0].message;
assert.ok(new RegExp("'id' field required").test(message));
});
});
});
});
});
15 changes: 14 additions & 1 deletion test/makeRule.js
Original file line number Diff line number Diff line change
Expand Up @@ -946,7 +946,20 @@ const requiredFieldsTestCases = {
invalid: values(namedOperationsValidatorCases).map(({fail: code, errors}) => ({options, parserOptions, code, errors})),
});

// Validate the required-fields rule
// Validate the required-fields rule with env specified
options = [{
schemaJson,
env: 'apollo',
tagName: 'gql',
requiredFields: ['id'],
}];

ruleTester.run('testing required-fields rule', rules['required-fields'], {
valid: requiredFieldsTestCases.pass.map((code) => ({options, parserOptions, code})),
invalid: requiredFieldsTestCases.fail.map(({code, errors}) => ({options, parserOptions, code, errors})),
});

// Validate required-fields without optional env argument
options = [{
schemaJson,
tagName: 'gql',
Expand Down

0 comments on commit 17c39d5

Please sign in to comment.