@graphql-eslint/eslint-plugin
Version:
GraphQL plugin for ESLint
163 lines (156 loc) • 6.32 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.rule = void 0;
const graphql_1 = require("graphql");
const utils_js_1 = require("../utils.js");
const RULE_ID = 'strict-id-in-types';
const schema = {
type: 'array',
maxItems: 1,
items: {
type: 'object',
additionalProperties: false,
properties: {
acceptedIdNames: {
...utils_js_1.ARRAY_DEFAULT_OPTIONS,
default: ['id'],
},
acceptedIdTypes: {
...utils_js_1.ARRAY_DEFAULT_OPTIONS,
default: ['ID'],
},
exceptions: {
type: 'object',
additionalProperties: false,
properties: {
types: {
...utils_js_1.ARRAY_DEFAULT_OPTIONS,
description: 'This is used to exclude types with names that match one of the specified values.',
},
suffixes: {
...utils_js_1.ARRAY_DEFAULT_OPTIONS,
description: 'This is used to exclude types with names with suffixes that match one of the specified values.',
},
},
},
},
},
};
exports.rule = {
meta: {
type: 'suggestion',
docs: {
description: 'Requires output types to have one unique identifier unless they do not have a logical one. Exceptions can be used to ignore output types that do not have unique identifiers.',
category: 'Schema',
recommended: true,
url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
requiresSchema: true,
examples: [
{
title: 'Incorrect',
usage: [
{
acceptedIdNames: ['id', '_id'],
acceptedIdTypes: ['ID'],
exceptions: { suffixes: ['Payload'] },
},
],
code: /* GraphQL */ `
# Incorrect field name
type InvalidFieldName {
key: ID!
}
# Incorrect field type
type InvalidFieldType {
id: String!
}
# Incorrect exception suffix
type InvalidSuffixResult {
data: String!
}
# Too many unique identifiers. Must only contain one.
type InvalidFieldName {
id: ID!
_id: ID!
}
`,
},
{
title: 'Correct',
usage: [
{
acceptedIdNames: ['id', '_id'],
acceptedIdTypes: ['ID'],
exceptions: { types: ['Error'], suffixes: ['Payload'] },
},
],
code: /* GraphQL */ `
type User {
id: ID!
}
type Post {
_id: ID!
}
type CreateUserPayload {
data: String!
}
type Error {
message: String!
}
`,
},
],
},
schema,
},
create(context) {
const options = {
acceptedIdNames: ['id'],
acceptedIdTypes: ['ID'],
exceptions: {},
...context.options[0],
};
const schema = (0, utils_js_1.requireGraphQLSchemaFromContext)(RULE_ID, context);
const rootTypeNames = [
schema.getQueryType(),
schema.getMutationType(),
schema.getSubscriptionType(),
]
.filter(utils_js_1.truthy)
.map(type => type.name);
const selector = `ObjectTypeDefinition[name.value!=/^(${rootTypeNames.join('|')})$/]`;
return {
[selector](node) {
var _a, _b, _c;
const typeName = node.name.value;
const shouldIgnoreNode = ((_a = options.exceptions.types) === null || _a === void 0 ? void 0 : _a.includes(typeName)) ||
((_b = options.exceptions.suffixes) === null || _b === void 0 ? void 0 : _b.some(suffix => typeName.endsWith(suffix)));
if (shouldIgnoreNode) {
return;
}
const validIds = (_c = node.fields) === null || _c === void 0 ? void 0 : _c.filter(field => {
const fieldNode = field.rawNode();
const isValidIdName = options.acceptedIdNames.includes(fieldNode.name.value);
// To be a valid type, it must be non-null and one of the accepted types.
let isValidIdType = false;
if (fieldNode.type.kind === graphql_1.Kind.NON_NULL_TYPE &&
fieldNode.type.type.kind === graphql_1.Kind.NAMED_TYPE) {
isValidIdType = options.acceptedIdTypes.includes(fieldNode.type.type.name.value);
}
return isValidIdName && isValidIdType;
});
// Usually, there should be only one unique identifier field per type.
// Some clients allow multiple fields to be used. If more people need this,
// we can extend this rule later.
if ((validIds === null || validIds === void 0 ? void 0 : validIds.length) !== 1) {
const pluralNamesSuffix = options.acceptedIdNames.length > 1 ? 's' : '';
const pluralTypesSuffix = options.acceptedIdTypes.length > 1 ? 's' : '';
context.report({
node: node.name,
message: `${typeName} must have exactly one non-nullable unique identifier. Accepted name${pluralNamesSuffix}: ${(0, utils_js_1.englishJoinWords)(options.acceptedIdNames)}. Accepted type${pluralTypesSuffix}: ${(0, utils_js_1.englishJoinWords)(options.acceptedIdTypes)}.`,
});
}
},
};
},
};