diff --git a/README.md b/README.md index 3045a3b..8e1e0a1 100644 --- a/README.md +++ b/README.md @@ -379,3 +379,50 @@ module.exports = { ] } ``` + +### Capitalization of a first letter of a Type name + +This rule enforces that first letter of types is capitalized + +**Pass** +``` +query { + someUnion { + ... on SomeType { + someField + } + } +} +``` + +**Fail** +``` +query { + someUnion { + ... on someType { + someField + } + } +} +``` + +The rule is defined as `graphql/capitalized-type-name` and requires a `schema`; + +```js +// In a file called .eslintrc.js +module.exports = { + parser: "babel-eslint", + rules: { + "graphql/template-strings": ['error', { + env: 'apollo', + schemaJson: require('./schema.json'), + }], + "graphql/capitalized-type-name": ['warn', { + schemaJson: require('./schema.json'), + }], + }, + plugins: [ + 'graphql' + ] +} +``` diff --git a/src/index.js b/src/index.js index 948d6ee..44a50f4 100644 --- a/src/index.js +++ b/src/index.js @@ -210,6 +210,34 @@ export const rules = { ); }, }, + 'capitalized-type-name': { + meta: { + schema: { + type: 'array', + minLength: 1, + items: { + additionalProperties: false, + properties: { ...defaultRuleProperties }, + oneOf: [{ + required: ['schemaJson'], + not: { required: ['schemaString', 'schemaJsonFilepath'], }, + }, { + required: ['schemaJsonFilepath'], + not: { required: ['schemaString', 'schemaJson'], }, + }, { + required: ['schemaString'], + not: { required: ['schemaJson', 'schemaJsonFilepath'], }, + }], + }, + }, + }, + create: (context) => { + return createRule(context, (optionGroup) => parseOptions({ + validators: ['typeNamesShouldBeCapitalized'], + ...optionGroup, + }));; + }, + }, }; function parseOptions(optionGroup) { @@ -458,6 +486,6 @@ export const processors = reduce(gqlFiles, (result, value) => { }, {}) export default { - rules, + rules, processors } diff --git a/src/rules.js b/src/rules.js index ca1c701..3c77fa3 100644 --- a/src/rules.js +++ b/src/rules.js @@ -45,3 +45,16 @@ export function RequiredFields(context, options) { }, }; } + +export function typeNamesShouldBeCapitalized(context) { + return { + NamedType(node) { + const typeName = node.name.value; + if (typeName[0] == typeName[0].toLowerCase()) { + context.reportError( + new GraphQLError("All type names should start with a capital letter", [ node ]) + ); + } + } + } +} diff --git a/test/makeRule.js b/test/makeRule.js index bc1b8c7..3ff5648 100644 --- a/test/makeRule.js +++ b/test/makeRule.js @@ -892,6 +892,28 @@ const requiredFieldsTestCases = { ], }; +const typeNameCapValidatorCases = { + pass: [ + 'const x = gql`fragment FilmFragment on Film { title } { allFilms { films { ...FilmFragment } } }`', + 'const x = gql`query { someUnion {... on SomeUnionMember { someField }}}`', + ], + fail: [ + { + code: 'const x = gql`fragment FilmFragment on film { title } { allFilms { films { ...FilmFragment } } }`', + errors: [{ + message: 'All type names should start with a capital letter', + type: 'TaggedTemplateExpression', + }], + }, + { + code: 'const x = gql`query { someUnion {... on someUnionMember { someField }}}`', + errors: [{ + message: 'All type names should start with a capital letter', + type: 'TaggedTemplateExpression', + }], + }, + ] +}; { let options = [{ schemaJson, tagName: 'gql', @@ -970,3 +992,11 @@ const requiredFieldsTestCases = { invalid: requiredFieldsTestCases.fail.map(({code, errors}) => ({options, parserOptions, code, errors})), }); } + +let options = [{ + schemaJson, tagName: 'gql', +}]; +ruleTester.run('testing capitalized-type-name rule', rules['capitalized-type-name'], { + valid: typeNameCapValidatorCases.pass.map((code) => ({options, parserOptions, code})), + invalid: typeNameCapValidatorCases.fail.map(({code, errors}) => ({options, parserOptions, code, errors})), +});