@graphql-eslint/eslint-plugin 3.1.1-alpha-f19a99b.0 → 3.2.0-alpha-001cd75.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -286,14 +286,6 @@ const TYPES_KINDS = [
286
286
  graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION,
287
287
  graphql.Kind.UNION_TYPE_DEFINITION,
288
288
  ];
289
- var CaseStyle;
290
- (function (CaseStyle) {
291
- CaseStyle["camelCase"] = "camelCase";
292
- CaseStyle["pascalCase"] = "PascalCase";
293
- CaseStyle["snakeCase"] = "snake_case";
294
- CaseStyle["upperCase"] = "UPPER_CASE";
295
- CaseStyle["kebabCase"] = "kebab-case";
296
- })(CaseStyle || (CaseStyle = {}));
297
289
  const pascalCase = (str) => lowerCase(str)
298
290
  .split(' ')
299
291
  .map(word => word.charAt(0).toUpperCase() + word.slice(1))
@@ -304,15 +296,15 @@ const camelCase = (str) => {
304
296
  };
305
297
  const convertCase = (style, str) => {
306
298
  switch (style) {
307
- case CaseStyle.camelCase:
299
+ case 'camelCase':
308
300
  return camelCase(str);
309
- case CaseStyle.pascalCase:
301
+ case 'PascalCase':
310
302
  return pascalCase(str);
311
- case CaseStyle.snakeCase:
303
+ case 'snake_case':
312
304
  return lowerCase(str).replace(/ /g, '_');
313
- case CaseStyle.upperCase:
305
+ case 'UPPER_CASE':
314
306
  return lowerCase(str).replace(/ /g, '_').toUpperCase();
315
- case CaseStyle.kebabCase:
307
+ case 'kebab-case':
316
308
  return lowerCase(str).replace(/ /g, '-');
317
309
  }
318
310
  };
@@ -335,42 +327,30 @@ function getLocation(loc, fieldName = '', offset) {
335
327
  };
336
328
  }
337
329
 
338
- function validateDoc(sourceNode, context, schema, documentNode, rules) {
339
- if (documentNode.definitions.length === 0) {
340
- return;
341
- }
342
- try {
343
- const validationErrors = schema
344
- ? graphql.validate(schema, documentNode, rules)
345
- : validate.validateSDL(documentNode, null, rules);
346
- for (const error of validationErrors) {
347
- /*
348
- * TODO: Fix ESTree-AST converter because currently it's incorrectly convert loc.end
349
- * Example: loc.end always equal loc.start
350
- * {
351
- * token: {
352
- * type: 'Name',
353
- * loc: { start: { line: 4, column: 13 }, end: { line: 4, column: 13 } },
354
- * value: 'veryBad',
355
- * range: [ 40, 47 ]
356
- * }
357
- * }
358
- */
359
- const { line, column } = error.locations[0];
360
- const ancestors = context.getAncestors();
361
- const token = ancestors[0].tokens.find(token => token.loc.start.line === line && token.loc.start.column === column);
330
+ function extractRuleName(stack) {
331
+ const match = (stack || '').match(/validation[/\\]rules[/\\](.*?)\.js:/) || [];
332
+ return match[1] || null;
333
+ }
334
+ function validateDoc(sourceNode, context, schema, documentNode, rules, ruleName = null) {
335
+ var _a;
336
+ if (((_a = documentNode === null || documentNode === void 0 ? void 0 : documentNode.definitions) === null || _a === void 0 ? void 0 : _a.length) > 0) {
337
+ try {
338
+ const validationErrors = schema ? graphql.validate(schema, documentNode, rules) : validate.validateSDL(documentNode, null, rules);
339
+ for (const error of validationErrors) {
340
+ const validateRuleName = ruleName || `[${extractRuleName(error.stack)}]`;
341
+ context.report({
342
+ loc: getLocation({ start: error.locations[0] }),
343
+ message: ruleName ? error.message : `${validateRuleName} ${error.message}`,
344
+ });
345
+ }
346
+ }
347
+ catch (e) {
362
348
  context.report({
363
- loc: getLocation({ start: error.locations[0] }, token === null || token === void 0 ? void 0 : token.value),
364
- message: error.message,
349
+ node: sourceNode,
350
+ message: e.message,
365
351
  });
366
352
  }
367
353
  }
368
- catch (e) {
369
- context.report({
370
- node: sourceNode,
371
- message: e.message,
372
- });
373
- }
374
354
  }
375
355
  const isGraphQLImportFile = rawSDL => {
376
356
  const trimmedRawSDL = rawSDL.trimLeft();
@@ -417,7 +397,7 @@ const validationToRule = (name, ruleName, docs, getDocumentNode) => {
417
397
  if (isRealFile && getDocumentNode) {
418
398
  documentNode = getDocumentNode(context);
419
399
  }
420
- validateDoc(node, context, schema, documentNode || node.rawNode(), [ruleFn]);
400
+ validateDoc(node, context, schema, documentNode || node.rawNode(), [ruleFn], ruleName);
421
401
  },
422
402
  };
423
403
  },
@@ -1059,13 +1039,7 @@ const rule$2 = {
1059
1039
  const MATCH_EXTENSION = 'MATCH_EXTENSION';
1060
1040
  const MATCH_STYLE = 'MATCH_STYLE';
1061
1041
  const ACCEPTED_EXTENSIONS = ['.gql', '.graphql'];
1062
- const CASE_STYLES = [
1063
- CaseStyle.camelCase,
1064
- CaseStyle.pascalCase,
1065
- CaseStyle.snakeCase,
1066
- CaseStyle.upperCase,
1067
- CaseStyle.kebabCase,
1068
- ];
1042
+ const CASE_STYLES = ['camelCase', 'PascalCase', 'snake_case', 'UPPER_CASE', 'kebab-case'];
1069
1043
  const schemaOption = {
1070
1044
  oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
1071
1045
  };
@@ -1089,7 +1063,7 @@ const rule$3 = {
1089
1063
  },
1090
1064
  {
1091
1065
  title: 'Correct',
1092
- usage: [{ query: CaseStyle.snakeCase }],
1066
+ usage: [{ query: 'snake_case' }],
1093
1067
  code: /* GraphQL */ `
1094
1068
  # user_by_id.gql
1095
1069
  query UserById {
@@ -1103,7 +1077,7 @@ const rule$3 = {
1103
1077
  },
1104
1078
  {
1105
1079
  title: 'Correct',
1106
- usage: [{ fragment: { style: CaseStyle.kebabCase, suffix: '.fragment' } }],
1080
+ usage: [{ fragment: { style: 'kebab-case', suffix: '.fragment' } }],
1107
1081
  code: /* GraphQL */ `
1108
1082
  # user-fields.fragment.gql
1109
1083
  fragment user_fields on User {
@@ -1114,7 +1088,7 @@ const rule$3 = {
1114
1088
  },
1115
1089
  {
1116
1090
  title: 'Correct',
1117
- usage: [{ mutation: { style: CaseStyle.pascalCase, suffix: 'Mutation' } }],
1091
+ usage: [{ mutation: { style: 'PascalCase', suffix: 'Mutation' } }],
1118
1092
  code: /* GraphQL */ `
1119
1093
  # DeleteUserMutation.gql
1120
1094
  mutation DELETE_USER {
@@ -1134,7 +1108,7 @@ const rule$3 = {
1134
1108
  },
1135
1109
  {
1136
1110
  title: 'Incorrect',
1137
- usage: [{ query: CaseStyle.pascalCase }],
1111
+ usage: [{ query: 'PascalCase' }],
1138
1112
  code: /* GraphQL */ `
1139
1113
  # user-by-id.gql
1140
1114
  query UserById {
@@ -1149,10 +1123,10 @@ const rule$3 = {
1149
1123
  ],
1150
1124
  configOptions: [
1151
1125
  {
1152
- query: CaseStyle.kebabCase,
1153
- mutation: CaseStyle.kebabCase,
1154
- subscription: CaseStyle.kebabCase,
1155
- fragment: CaseStyle.kebabCase,
1126
+ query: 'kebab-case',
1127
+ mutation: 'kebab-case',
1128
+ subscription: 'kebab-case',
1129
+ fragment: 'kebab-case',
1156
1130
  },
1157
1131
  ],
1158
1132
  },
@@ -1169,25 +1143,22 @@ const rule$3 = {
1169
1143
  asObject: {
1170
1144
  type: 'object',
1171
1145
  additionalProperties: false,
1146
+ minProperties: 1,
1172
1147
  properties: {
1173
- style: {
1174
- enum: CASE_STYLES,
1175
- },
1176
- suffix: {
1177
- type: 'string',
1178
- },
1148
+ style: { enum: CASE_STYLES },
1149
+ suffix: { type: 'string' },
1179
1150
  },
1180
1151
  },
1181
1152
  },
1182
1153
  type: 'array',
1154
+ minItems: 1,
1183
1155
  maxItems: 1,
1184
1156
  items: {
1185
1157
  type: 'object',
1186
1158
  additionalProperties: false,
1159
+ minProperties: 1,
1187
1160
  properties: {
1188
- fileExtension: {
1189
- enum: ACCEPTED_EXTENSIONS,
1190
- },
1161
+ fileExtension: { enum: ACCEPTED_EXTENSIONS },
1191
1162
  query: schemaOption,
1192
1163
  mutation: schemaOption,
1193
1164
  subscription: schemaOption,
@@ -1242,7 +1213,7 @@ const rule$3 = {
1242
1213
  option = { style: option };
1243
1214
  }
1244
1215
  const expectedExtension = options.fileExtension || fileExtension;
1245
- const expectedFilename = convertCase(option.style, docName) + (option.suffix || '') + expectedExtension;
1216
+ const expectedFilename = (option.style ? convertCase(option.style, docName) : filename) + (option.suffix || '') + expectedExtension;
1246
1217
  const filenameWithExtension = filename + expectedExtension;
1247
1218
  if (expectedFilename !== filenameWithExtension) {
1248
1219
  context.report({
@@ -1394,6 +1365,7 @@ const rule$4 = {
1394
1365
  ],
1395
1366
  },
1396
1367
  },
1368
+ hasSuggestions: true,
1397
1369
  schema: {
1398
1370
  definitions: {
1399
1371
  asString: {
@@ -1468,65 +1440,90 @@ const rule$4 = {
1468
1440
  const style = restOptions[kind] || types;
1469
1441
  return typeof style === 'object' ? style : { style };
1470
1442
  }
1471
- const checkNode = (selector) => (node) => {
1472
- const { name } = node.kind === graphql.Kind.VARIABLE_DEFINITION ? node.variable : node;
1473
- if (!name) {
1443
+ const checkNode = (selector) => (n) => {
1444
+ const { name: node } = n.kind === graphql.Kind.VARIABLE_DEFINITION ? n.variable : n;
1445
+ if (!node) {
1474
1446
  return;
1475
1447
  }
1476
1448
  const { prefix, suffix, forbiddenPrefixes, forbiddenSuffixes, style } = normalisePropertyOption(selector);
1477
- const nodeType = KindToDisplayName[node.kind] || node.kind;
1478
- const nodeName = name.value;
1479
- const errorMessage = getErrorMessage();
1480
- if (errorMessage) {
1449
+ const nodeType = KindToDisplayName[n.kind] || n.kind;
1450
+ const nodeName = node.value;
1451
+ const error = getError();
1452
+ if (error) {
1453
+ const { errorMessage, renameToName } = error;
1454
+ const [leadingUnderscore] = nodeName.match(/^_*/);
1455
+ const [trailingUnderscore] = nodeName.match(/_*$/);
1456
+ const suggestedName = leadingUnderscore + renameToName + trailingUnderscore;
1481
1457
  context.report({
1482
- loc: getLocation(name.loc, name.value),
1458
+ loc: getLocation(node.loc, node.value),
1483
1459
  message: `${nodeType} "${nodeName}" should ${errorMessage}`,
1460
+ suggest: [
1461
+ {
1462
+ desc: `Rename to "${suggestedName}"`,
1463
+ fix: fixer => fixer.replaceText(node, suggestedName),
1464
+ },
1465
+ ],
1484
1466
  });
1485
1467
  }
1486
- function getErrorMessage() {
1487
- let name = nodeName;
1488
- if (allowLeadingUnderscore) {
1489
- name = name.replace(/^_*/, '');
1490
- }
1491
- if (allowTrailingUnderscore) {
1492
- name = name.replace(/_*$/, '');
1493
- }
1468
+ function getError() {
1469
+ const name = nodeName.replace(/(^_+)|(_+$)/g, '');
1494
1470
  if (prefix && !name.startsWith(prefix)) {
1495
- return `have "${prefix}" prefix`;
1471
+ return {
1472
+ errorMessage: `have "${prefix}" prefix`,
1473
+ renameToName: prefix + name,
1474
+ };
1496
1475
  }
1497
1476
  if (suffix && !name.endsWith(suffix)) {
1498
- return `have "${suffix}" suffix`;
1477
+ return {
1478
+ errorMessage: `have "${suffix}" suffix`,
1479
+ renameToName: name + suffix,
1480
+ };
1499
1481
  }
1500
1482
  const forbiddenPrefix = forbiddenPrefixes === null || forbiddenPrefixes === void 0 ? void 0 : forbiddenPrefixes.find(prefix => name.startsWith(prefix));
1501
1483
  if (forbiddenPrefix) {
1502
- return `not have "${forbiddenPrefix}" prefix`;
1484
+ return {
1485
+ errorMessage: `not have "${forbiddenPrefix}" prefix`,
1486
+ renameToName: name.replace(new RegExp(`^${forbiddenPrefix}`), ''),
1487
+ };
1503
1488
  }
1504
1489
  const forbiddenSuffix = forbiddenSuffixes === null || forbiddenSuffixes === void 0 ? void 0 : forbiddenSuffixes.find(suffix => name.endsWith(suffix));
1505
1490
  if (forbiddenSuffix) {
1506
- return `not have "${forbiddenSuffix}" suffix`;
1507
- }
1508
- if (style && !ALLOWED_STYLES.includes(style)) {
1509
- return `be in one of the following options: ${ALLOWED_STYLES.join(', ')}`;
1491
+ return {
1492
+ errorMessage: `not have "${forbiddenSuffix}" suffix`,
1493
+ renameToName: name.replace(new RegExp(`${forbiddenSuffix}$`), ''),
1494
+ };
1510
1495
  }
1511
1496
  const caseRegex = StyleToRegex[style];
1512
1497
  if (caseRegex && !caseRegex.test(name)) {
1513
- return `be in ${style} format`;
1498
+ return {
1499
+ errorMessage: `be in ${style} format`,
1500
+ renameToName: convertCase(style, name),
1501
+ };
1514
1502
  }
1515
1503
  }
1516
1504
  };
1517
- const checkUnderscore = (node) => {
1505
+ const checkUnderscore = (isLeading) => (node) => {
1518
1506
  const name = node.value;
1507
+ const renameToName = name.replace(new RegExp(isLeading ? '^_+' : '_+$'), '');
1519
1508
  context.report({
1520
1509
  loc: getLocation(node.loc, name),
1521
- message: `${name.startsWith('_') ? 'Leading' : 'Trailing'} underscores are not allowed`,
1510
+ message: `${isLeading ? 'Leading' : 'Trailing'} underscores are not allowed`,
1511
+ suggest: [
1512
+ {
1513
+ desc: `Rename to "${renameToName}"`,
1514
+ fix: fixer => fixer.replaceText(node, renameToName),
1515
+ },
1516
+ ],
1522
1517
  });
1523
1518
  };
1524
1519
  const listeners = {};
1525
1520
  if (!allowLeadingUnderscore) {
1526
- listeners['Name[value=/^_/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] = checkUnderscore;
1521
+ listeners['Name[value=/^_/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] =
1522
+ checkUnderscore(true);
1527
1523
  }
1528
1524
  if (!allowTrailingUnderscore) {
1529
- listeners['Name[value=/_$/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] = checkUnderscore;
1525
+ listeners['Name[value=/_$/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] =
1526
+ checkUnderscore(false);
1530
1527
  }
1531
1528
  const selectors = new Set([types && TYPES_KINDS, Object.keys(restOptions)].flat().filter(Boolean));
1532
1529
  for (const selector of selectors) {
package/index.mjs CHANGED
@@ -280,14 +280,6 @@ const TYPES_KINDS = [
280
280
  Kind.INPUT_OBJECT_TYPE_DEFINITION,
281
281
  Kind.UNION_TYPE_DEFINITION,
282
282
  ];
283
- var CaseStyle;
284
- (function (CaseStyle) {
285
- CaseStyle["camelCase"] = "camelCase";
286
- CaseStyle["pascalCase"] = "PascalCase";
287
- CaseStyle["snakeCase"] = "snake_case";
288
- CaseStyle["upperCase"] = "UPPER_CASE";
289
- CaseStyle["kebabCase"] = "kebab-case";
290
- })(CaseStyle || (CaseStyle = {}));
291
283
  const pascalCase = (str) => lowerCase(str)
292
284
  .split(' ')
293
285
  .map(word => word.charAt(0).toUpperCase() + word.slice(1))
@@ -298,15 +290,15 @@ const camelCase = (str) => {
298
290
  };
299
291
  const convertCase = (style, str) => {
300
292
  switch (style) {
301
- case CaseStyle.camelCase:
293
+ case 'camelCase':
302
294
  return camelCase(str);
303
- case CaseStyle.pascalCase:
295
+ case 'PascalCase':
304
296
  return pascalCase(str);
305
- case CaseStyle.snakeCase:
297
+ case 'snake_case':
306
298
  return lowerCase(str).replace(/ /g, '_');
307
- case CaseStyle.upperCase:
299
+ case 'UPPER_CASE':
308
300
  return lowerCase(str).replace(/ /g, '_').toUpperCase();
309
- case CaseStyle.kebabCase:
301
+ case 'kebab-case':
310
302
  return lowerCase(str).replace(/ /g, '-');
311
303
  }
312
304
  };
@@ -329,42 +321,30 @@ function getLocation(loc, fieldName = '', offset) {
329
321
  };
330
322
  }
331
323
 
332
- function validateDoc(sourceNode, context, schema, documentNode, rules) {
333
- if (documentNode.definitions.length === 0) {
334
- return;
335
- }
336
- try {
337
- const validationErrors = schema
338
- ? validate(schema, documentNode, rules)
339
- : validateSDL(documentNode, null, rules);
340
- for (const error of validationErrors) {
341
- /*
342
- * TODO: Fix ESTree-AST converter because currently it's incorrectly convert loc.end
343
- * Example: loc.end always equal loc.start
344
- * {
345
- * token: {
346
- * type: 'Name',
347
- * loc: { start: { line: 4, column: 13 }, end: { line: 4, column: 13 } },
348
- * value: 'veryBad',
349
- * range: [ 40, 47 ]
350
- * }
351
- * }
352
- */
353
- const { line, column } = error.locations[0];
354
- const ancestors = context.getAncestors();
355
- const token = ancestors[0].tokens.find(token => token.loc.start.line === line && token.loc.start.column === column);
324
+ function extractRuleName(stack) {
325
+ const match = (stack || '').match(/validation[/\\]rules[/\\](.*?)\.js:/) || [];
326
+ return match[1] || null;
327
+ }
328
+ function validateDoc(sourceNode, context, schema, documentNode, rules, ruleName = null) {
329
+ var _a;
330
+ if (((_a = documentNode === null || documentNode === void 0 ? void 0 : documentNode.definitions) === null || _a === void 0 ? void 0 : _a.length) > 0) {
331
+ try {
332
+ const validationErrors = schema ? validate(schema, documentNode, rules) : validateSDL(documentNode, null, rules);
333
+ for (const error of validationErrors) {
334
+ const validateRuleName = ruleName || `[${extractRuleName(error.stack)}]`;
335
+ context.report({
336
+ loc: getLocation({ start: error.locations[0] }),
337
+ message: ruleName ? error.message : `${validateRuleName} ${error.message}`,
338
+ });
339
+ }
340
+ }
341
+ catch (e) {
356
342
  context.report({
357
- loc: getLocation({ start: error.locations[0] }, token === null || token === void 0 ? void 0 : token.value),
358
- message: error.message,
343
+ node: sourceNode,
344
+ message: e.message,
359
345
  });
360
346
  }
361
347
  }
362
- catch (e) {
363
- context.report({
364
- node: sourceNode,
365
- message: e.message,
366
- });
367
- }
368
348
  }
369
349
  const isGraphQLImportFile = rawSDL => {
370
350
  const trimmedRawSDL = rawSDL.trimLeft();
@@ -411,7 +391,7 @@ const validationToRule = (name, ruleName, docs, getDocumentNode) => {
411
391
  if (isRealFile && getDocumentNode) {
412
392
  documentNode = getDocumentNode(context);
413
393
  }
414
- validateDoc(node, context, schema, documentNode || node.rawNode(), [ruleFn]);
394
+ validateDoc(node, context, schema, documentNode || node.rawNode(), [ruleFn], ruleName);
415
395
  },
416
396
  };
417
397
  },
@@ -1053,13 +1033,7 @@ const rule$2 = {
1053
1033
  const MATCH_EXTENSION = 'MATCH_EXTENSION';
1054
1034
  const MATCH_STYLE = 'MATCH_STYLE';
1055
1035
  const ACCEPTED_EXTENSIONS = ['.gql', '.graphql'];
1056
- const CASE_STYLES = [
1057
- CaseStyle.camelCase,
1058
- CaseStyle.pascalCase,
1059
- CaseStyle.snakeCase,
1060
- CaseStyle.upperCase,
1061
- CaseStyle.kebabCase,
1062
- ];
1036
+ const CASE_STYLES = ['camelCase', 'PascalCase', 'snake_case', 'UPPER_CASE', 'kebab-case'];
1063
1037
  const schemaOption = {
1064
1038
  oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
1065
1039
  };
@@ -1083,7 +1057,7 @@ const rule$3 = {
1083
1057
  },
1084
1058
  {
1085
1059
  title: 'Correct',
1086
- usage: [{ query: CaseStyle.snakeCase }],
1060
+ usage: [{ query: 'snake_case' }],
1087
1061
  code: /* GraphQL */ `
1088
1062
  # user_by_id.gql
1089
1063
  query UserById {
@@ -1097,7 +1071,7 @@ const rule$3 = {
1097
1071
  },
1098
1072
  {
1099
1073
  title: 'Correct',
1100
- usage: [{ fragment: { style: CaseStyle.kebabCase, suffix: '.fragment' } }],
1074
+ usage: [{ fragment: { style: 'kebab-case', suffix: '.fragment' } }],
1101
1075
  code: /* GraphQL */ `
1102
1076
  # user-fields.fragment.gql
1103
1077
  fragment user_fields on User {
@@ -1108,7 +1082,7 @@ const rule$3 = {
1108
1082
  },
1109
1083
  {
1110
1084
  title: 'Correct',
1111
- usage: [{ mutation: { style: CaseStyle.pascalCase, suffix: 'Mutation' } }],
1085
+ usage: [{ mutation: { style: 'PascalCase', suffix: 'Mutation' } }],
1112
1086
  code: /* GraphQL */ `
1113
1087
  # DeleteUserMutation.gql
1114
1088
  mutation DELETE_USER {
@@ -1128,7 +1102,7 @@ const rule$3 = {
1128
1102
  },
1129
1103
  {
1130
1104
  title: 'Incorrect',
1131
- usage: [{ query: CaseStyle.pascalCase }],
1105
+ usage: [{ query: 'PascalCase' }],
1132
1106
  code: /* GraphQL */ `
1133
1107
  # user-by-id.gql
1134
1108
  query UserById {
@@ -1143,10 +1117,10 @@ const rule$3 = {
1143
1117
  ],
1144
1118
  configOptions: [
1145
1119
  {
1146
- query: CaseStyle.kebabCase,
1147
- mutation: CaseStyle.kebabCase,
1148
- subscription: CaseStyle.kebabCase,
1149
- fragment: CaseStyle.kebabCase,
1120
+ query: 'kebab-case',
1121
+ mutation: 'kebab-case',
1122
+ subscription: 'kebab-case',
1123
+ fragment: 'kebab-case',
1150
1124
  },
1151
1125
  ],
1152
1126
  },
@@ -1163,25 +1137,22 @@ const rule$3 = {
1163
1137
  asObject: {
1164
1138
  type: 'object',
1165
1139
  additionalProperties: false,
1140
+ minProperties: 1,
1166
1141
  properties: {
1167
- style: {
1168
- enum: CASE_STYLES,
1169
- },
1170
- suffix: {
1171
- type: 'string',
1172
- },
1142
+ style: { enum: CASE_STYLES },
1143
+ suffix: { type: 'string' },
1173
1144
  },
1174
1145
  },
1175
1146
  },
1176
1147
  type: 'array',
1148
+ minItems: 1,
1177
1149
  maxItems: 1,
1178
1150
  items: {
1179
1151
  type: 'object',
1180
1152
  additionalProperties: false,
1153
+ minProperties: 1,
1181
1154
  properties: {
1182
- fileExtension: {
1183
- enum: ACCEPTED_EXTENSIONS,
1184
- },
1155
+ fileExtension: { enum: ACCEPTED_EXTENSIONS },
1185
1156
  query: schemaOption,
1186
1157
  mutation: schemaOption,
1187
1158
  subscription: schemaOption,
@@ -1236,7 +1207,7 @@ const rule$3 = {
1236
1207
  option = { style: option };
1237
1208
  }
1238
1209
  const expectedExtension = options.fileExtension || fileExtension;
1239
- const expectedFilename = convertCase(option.style, docName) + (option.suffix || '') + expectedExtension;
1210
+ const expectedFilename = (option.style ? convertCase(option.style, docName) : filename) + (option.suffix || '') + expectedExtension;
1240
1211
  const filenameWithExtension = filename + expectedExtension;
1241
1212
  if (expectedFilename !== filenameWithExtension) {
1242
1213
  context.report({
@@ -1388,6 +1359,7 @@ const rule$4 = {
1388
1359
  ],
1389
1360
  },
1390
1361
  },
1362
+ hasSuggestions: true,
1391
1363
  schema: {
1392
1364
  definitions: {
1393
1365
  asString: {
@@ -1462,65 +1434,90 @@ const rule$4 = {
1462
1434
  const style = restOptions[kind] || types;
1463
1435
  return typeof style === 'object' ? style : { style };
1464
1436
  }
1465
- const checkNode = (selector) => (node) => {
1466
- const { name } = node.kind === Kind.VARIABLE_DEFINITION ? node.variable : node;
1467
- if (!name) {
1437
+ const checkNode = (selector) => (n) => {
1438
+ const { name: node } = n.kind === Kind.VARIABLE_DEFINITION ? n.variable : n;
1439
+ if (!node) {
1468
1440
  return;
1469
1441
  }
1470
1442
  const { prefix, suffix, forbiddenPrefixes, forbiddenSuffixes, style } = normalisePropertyOption(selector);
1471
- const nodeType = KindToDisplayName[node.kind] || node.kind;
1472
- const nodeName = name.value;
1473
- const errorMessage = getErrorMessage();
1474
- if (errorMessage) {
1443
+ const nodeType = KindToDisplayName[n.kind] || n.kind;
1444
+ const nodeName = node.value;
1445
+ const error = getError();
1446
+ if (error) {
1447
+ const { errorMessage, renameToName } = error;
1448
+ const [leadingUnderscore] = nodeName.match(/^_*/);
1449
+ const [trailingUnderscore] = nodeName.match(/_*$/);
1450
+ const suggestedName = leadingUnderscore + renameToName + trailingUnderscore;
1475
1451
  context.report({
1476
- loc: getLocation(name.loc, name.value),
1452
+ loc: getLocation(node.loc, node.value),
1477
1453
  message: `${nodeType} "${nodeName}" should ${errorMessage}`,
1454
+ suggest: [
1455
+ {
1456
+ desc: `Rename to "${suggestedName}"`,
1457
+ fix: fixer => fixer.replaceText(node, suggestedName),
1458
+ },
1459
+ ],
1478
1460
  });
1479
1461
  }
1480
- function getErrorMessage() {
1481
- let name = nodeName;
1482
- if (allowLeadingUnderscore) {
1483
- name = name.replace(/^_*/, '');
1484
- }
1485
- if (allowTrailingUnderscore) {
1486
- name = name.replace(/_*$/, '');
1487
- }
1462
+ function getError() {
1463
+ const name = nodeName.replace(/(^_+)|(_+$)/g, '');
1488
1464
  if (prefix && !name.startsWith(prefix)) {
1489
- return `have "${prefix}" prefix`;
1465
+ return {
1466
+ errorMessage: `have "${prefix}" prefix`,
1467
+ renameToName: prefix + name,
1468
+ };
1490
1469
  }
1491
1470
  if (suffix && !name.endsWith(suffix)) {
1492
- return `have "${suffix}" suffix`;
1471
+ return {
1472
+ errorMessage: `have "${suffix}" suffix`,
1473
+ renameToName: name + suffix,
1474
+ };
1493
1475
  }
1494
1476
  const forbiddenPrefix = forbiddenPrefixes === null || forbiddenPrefixes === void 0 ? void 0 : forbiddenPrefixes.find(prefix => name.startsWith(prefix));
1495
1477
  if (forbiddenPrefix) {
1496
- return `not have "${forbiddenPrefix}" prefix`;
1478
+ return {
1479
+ errorMessage: `not have "${forbiddenPrefix}" prefix`,
1480
+ renameToName: name.replace(new RegExp(`^${forbiddenPrefix}`), ''),
1481
+ };
1497
1482
  }
1498
1483
  const forbiddenSuffix = forbiddenSuffixes === null || forbiddenSuffixes === void 0 ? void 0 : forbiddenSuffixes.find(suffix => name.endsWith(suffix));
1499
1484
  if (forbiddenSuffix) {
1500
- return `not have "${forbiddenSuffix}" suffix`;
1501
- }
1502
- if (style && !ALLOWED_STYLES.includes(style)) {
1503
- return `be in one of the following options: ${ALLOWED_STYLES.join(', ')}`;
1485
+ return {
1486
+ errorMessage: `not have "${forbiddenSuffix}" suffix`,
1487
+ renameToName: name.replace(new RegExp(`${forbiddenSuffix}$`), ''),
1488
+ };
1504
1489
  }
1505
1490
  const caseRegex = StyleToRegex[style];
1506
1491
  if (caseRegex && !caseRegex.test(name)) {
1507
- return `be in ${style} format`;
1492
+ return {
1493
+ errorMessage: `be in ${style} format`,
1494
+ renameToName: convertCase(style, name),
1495
+ };
1508
1496
  }
1509
1497
  }
1510
1498
  };
1511
- const checkUnderscore = (node) => {
1499
+ const checkUnderscore = (isLeading) => (node) => {
1512
1500
  const name = node.value;
1501
+ const renameToName = name.replace(new RegExp(isLeading ? '^_+' : '_+$'), '');
1513
1502
  context.report({
1514
1503
  loc: getLocation(node.loc, name),
1515
- message: `${name.startsWith('_') ? 'Leading' : 'Trailing'} underscores are not allowed`,
1504
+ message: `${isLeading ? 'Leading' : 'Trailing'} underscores are not allowed`,
1505
+ suggest: [
1506
+ {
1507
+ desc: `Rename to "${renameToName}"`,
1508
+ fix: fixer => fixer.replaceText(node, renameToName),
1509
+ },
1510
+ ],
1516
1511
  });
1517
1512
  };
1518
1513
  const listeners = {};
1519
1514
  if (!allowLeadingUnderscore) {
1520
- listeners['Name[value=/^_/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] = checkUnderscore;
1515
+ listeners['Name[value=/^_/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] =
1516
+ checkUnderscore(true);
1521
1517
  }
1522
1518
  if (!allowTrailingUnderscore) {
1523
- listeners['Name[value=/_$/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] = checkUnderscore;
1519
+ listeners['Name[value=/_$/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] =
1520
+ checkUnderscore(false);
1524
1521
  }
1525
1522
  const selectors = new Set([types && TYPES_KINDS, Object.keys(restOptions)].flat().filter(Boolean));
1526
1523
  for (const selector of selectors) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graphql-eslint/eslint-plugin",
3
- "version": "3.1.1-alpha-f19a99b.0",
3
+ "version": "3.2.0-alpha-001cd75.0",
4
4
  "sideEffects": false,
5
5
  "peerDependencies": {
6
6
  "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
@@ -1,5 +1,5 @@
1
1
  import { GraphQLSchema, DocumentNode, ASTNode, ValidationRule } from 'graphql';
2
2
  import { GraphQLESLintRule, GraphQLESLintRuleContext } from '../types';
3
3
  import { GraphQLESTreeNode } from '../estree-parser';
4
- export declare function validateDoc(sourceNode: GraphQLESTreeNode<ASTNode>, context: GraphQLESLintRuleContext, schema: GraphQLSchema | null, documentNode: DocumentNode, rules: ReadonlyArray<ValidationRule>): void;
4
+ export declare function validateDoc(sourceNode: GraphQLESTreeNode<ASTNode>, context: GraphQLESLintRuleContext, schema: GraphQLSchema | null, documentNode: DocumentNode, rules: ReadonlyArray<ValidationRule>, ruleName?: string | null): void;
5
5
  export declare const GRAPHQL_JS_VALIDATIONS: Record<string, GraphQLESLintRule<any[], false>>;
package/rules/index.d.ts CHANGED
@@ -15,135 +15,8 @@ export declare const rules: {
15
15
  checkQueries?: boolean;
16
16
  checkMutations?: boolean;
17
17
  }], false>;
18
- 'match-document-filename': import("..").GraphQLESLintRule<[{
19
- fileExtension?: ".gql" | ".graphql";
20
- query?: import("../utils").CaseStyle | {
21
- style: import("../utils").CaseStyle;
22
- suffix: string;
23
- };
24
- mutation?: import("../utils").CaseStyle | {
25
- style: import("../utils").CaseStyle;
26
- suffix: string;
27
- };
28
- subscription?: import("../utils").CaseStyle | {
29
- style: import("../utils").CaseStyle;
30
- suffix: string;
31
- };
32
- fragment?: import("../utils").CaseStyle | {
33
- style: import("../utils").CaseStyle;
34
- suffix: string;
35
- };
36
- }], false>;
37
- 'naming-convention': import("..").GraphQLESLintRule<[{
38
- allowLeadingUnderscore?: boolean;
39
- allowTrailingUnderscore?: boolean;
40
- types?: ("PascalCase" | "camelCase" | "UPPER_CASE" | "snake_case") | {
41
- style?: "PascalCase" | "camelCase" | "UPPER_CASE" | "snake_case";
42
- suffix?: string;
43
- prefix?: string;
44
- forbiddenPrefixes?: string[];
45
- forbiddenSuffixes?: string[];
46
- };
47
- } & {
48
- [x: `OperationDefinition${string}`]: ("PascalCase" | "camelCase" | "UPPER_CASE" | "snake_case") | {
49
- style?: "PascalCase" | "camelCase" | "UPPER_CASE" | "snake_case";
50
- suffix?: string;
51
- prefix?: string;
52
- forbiddenPrefixes?: string[];
53
- forbiddenSuffixes?: string[];
54
- };
55
- [x: `VariableDefinition${string}`]: ("PascalCase" | "camelCase" | "UPPER_CASE" | "snake_case") | {
56
- style?: "PascalCase" | "camelCase" | "UPPER_CASE" | "snake_case";
57
- suffix?: string;
58
- prefix?: string;
59
- forbiddenPrefixes?: string[];
60
- forbiddenSuffixes?: string[];
61
- };
62
- [x: `Argument${string}`]: ("PascalCase" | "camelCase" | "UPPER_CASE" | "snake_case") | {
63
- style?: "PascalCase" | "camelCase" | "UPPER_CASE" | "snake_case";
64
- suffix?: string;
65
- prefix?: string;
66
- forbiddenPrefixes?: string[];
67
- forbiddenSuffixes?: string[];
68
- };
69
- [x: `FragmentDefinition${string}`]: ("PascalCase" | "camelCase" | "UPPER_CASE" | "snake_case") | {
70
- style?: "PascalCase" | "camelCase" | "UPPER_CASE" | "snake_case";
71
- suffix?: string;
72
- prefix?: string;
73
- forbiddenPrefixes?: string[];
74
- forbiddenSuffixes?: string[];
75
- };
76
- [x: `ScalarTypeDefinition${string}`]: ("PascalCase" | "camelCase" | "UPPER_CASE" | "snake_case") | {
77
- style?: "PascalCase" | "camelCase" | "UPPER_CASE" | "snake_case";
78
- suffix?: string;
79
- prefix?: string;
80
- forbiddenPrefixes?: string[];
81
- forbiddenSuffixes?: string[];
82
- };
83
- [x: `ObjectTypeDefinition${string}`]: ("PascalCase" | "camelCase" | "UPPER_CASE" | "snake_case") | {
84
- style?: "PascalCase" | "camelCase" | "UPPER_CASE" | "snake_case";
85
- suffix?: string;
86
- prefix?: string;
87
- forbiddenPrefixes?: string[];
88
- forbiddenSuffixes?: string[];
89
- };
90
- [x: `FieldDefinition${string}`]: ("PascalCase" | "camelCase" | "UPPER_CASE" | "snake_case") | {
91
- style?: "PascalCase" | "camelCase" | "UPPER_CASE" | "snake_case";
92
- suffix?: string;
93
- prefix?: string;
94
- forbiddenPrefixes?: string[];
95
- forbiddenSuffixes?: string[];
96
- };
97
- [x: `InputValueDefinition${string}`]: ("PascalCase" | "camelCase" | "UPPER_CASE" | "snake_case") | {
98
- style?: "PascalCase" | "camelCase" | "UPPER_CASE" | "snake_case";
99
- suffix?: string;
100
- prefix?: string;
101
- forbiddenPrefixes?: string[];
102
- forbiddenSuffixes?: string[];
103
- };
104
- [x: `InterfaceTypeDefinition${string}`]: ("PascalCase" | "camelCase" | "UPPER_CASE" | "snake_case") | {
105
- style?: "PascalCase" | "camelCase" | "UPPER_CASE" | "snake_case";
106
- suffix?: string;
107
- prefix?: string;
108
- forbiddenPrefixes?: string[];
109
- forbiddenSuffixes?: string[];
110
- };
111
- [x: `UnionTypeDefinition${string}`]: ("PascalCase" | "camelCase" | "UPPER_CASE" | "snake_case") | {
112
- style?: "PascalCase" | "camelCase" | "UPPER_CASE" | "snake_case";
113
- suffix?: string;
114
- prefix?: string;
115
- forbiddenPrefixes?: string[];
116
- forbiddenSuffixes?: string[];
117
- };
118
- [x: `EnumTypeDefinition${string}`]: ("PascalCase" | "camelCase" | "UPPER_CASE" | "snake_case") | {
119
- style?: "PascalCase" | "camelCase" | "UPPER_CASE" | "snake_case";
120
- suffix?: string;
121
- prefix?: string;
122
- forbiddenPrefixes?: string[];
123
- forbiddenSuffixes?: string[];
124
- };
125
- [x: `EnumValueDefinition${string}`]: ("PascalCase" | "camelCase" | "UPPER_CASE" | "snake_case") | {
126
- style?: "PascalCase" | "camelCase" | "UPPER_CASE" | "snake_case";
127
- suffix?: string;
128
- prefix?: string;
129
- forbiddenPrefixes?: string[];
130
- forbiddenSuffixes?: string[];
131
- };
132
- [x: `InputObjectTypeDefinition${string}`]: ("PascalCase" | "camelCase" | "UPPER_CASE" | "snake_case") | {
133
- style?: "PascalCase" | "camelCase" | "UPPER_CASE" | "snake_case";
134
- suffix?: string;
135
- prefix?: string;
136
- forbiddenPrefixes?: string[];
137
- forbiddenSuffixes?: string[];
138
- };
139
- [x: `DirectiveDefinition${string}`]: ("PascalCase" | "camelCase" | "UPPER_CASE" | "snake_case") | {
140
- style?: "PascalCase" | "camelCase" | "UPPER_CASE" | "snake_case";
141
- suffix?: string;
142
- prefix?: string;
143
- forbiddenPrefixes?: string[];
144
- forbiddenSuffixes?: string[];
145
- };
146
- }], false>;
18
+ 'match-document-filename': import("..").GraphQLESLintRule<[import("./match-document-filename").MatchDocumentFilenameRuleConfig], false>;
19
+ 'naming-convention': import("..").GraphQLESLintRule<[import("./naming-convention").NamingConventionRuleConfig], false>;
147
20
  'no-anonymous-operations': import("..").GraphQLESLintRule<any[], false>;
148
21
  'no-case-insensitive-enum-values-duplicates': import("..").GraphQLESLintRule<any[], false>;
149
22
  'no-deprecated': import("..").GraphQLESLintRule<[], true>;
@@ -2,10 +2,10 @@ import { CaseStyle } from '../utils';
2
2
  import { GraphQLESLintRule } from '../types';
3
3
  declare const ACCEPTED_EXTENSIONS: ['.gql', '.graphql'];
4
4
  declare type PropertySchema = {
5
- style: CaseStyle;
6
- suffix: string;
5
+ style?: CaseStyle;
6
+ suffix?: string;
7
7
  };
8
- declare type MatchDocumentFilenameRuleConfig = {
8
+ export declare type MatchDocumentFilenameRuleConfig = {
9
9
  fileExtension?: typeof ACCEPTED_EXTENSIONS[number];
10
10
  query?: CaseStyle | PropertySchema;
11
11
  mutation?: CaseStyle | PropertySchema;
@@ -25,7 +25,7 @@ declare type PropertySchema = {
25
25
  forbiddenSuffixes?: string[];
26
26
  };
27
27
  declare type Options = AllowedStyle | PropertySchema;
28
- declare type NamingConventionRuleConfig = {
28
+ export declare type NamingConventionRuleConfig = {
29
29
  allowLeadingUnderscore?: boolean;
30
30
  allowTrailingUnderscore?: boolean;
31
31
  types?: Options;
package/utils.d.ts CHANGED
@@ -20,13 +20,7 @@ export declare const getOnDiskFilepath: (filepath: string) => string;
20
20
  export declare const getTypeName: (node: any) => any;
21
21
  export declare const loaderCache: Record<string, LoaderSource[]>;
22
22
  export declare const TYPES_KINDS: readonly [Kind.OBJECT_TYPE_DEFINITION, Kind.INTERFACE_TYPE_DEFINITION, Kind.ENUM_TYPE_DEFINITION, Kind.SCALAR_TYPE_DEFINITION, Kind.INPUT_OBJECT_TYPE_DEFINITION, Kind.UNION_TYPE_DEFINITION];
23
- export declare enum CaseStyle {
24
- camelCase = "camelCase",
25
- pascalCase = "PascalCase",
26
- snakeCase = "snake_case",
27
- upperCase = "UPPER_CASE",
28
- kebabCase = "kebab-case"
29
- }
23
+ export declare type CaseStyle = 'camelCase' | 'PascalCase' | 'snake_case' | 'UPPER_CASE' | 'kebab-case';
30
24
  export declare const camelCase: (str: string) => string;
31
25
  export declare const convertCase: (style: CaseStyle, str: string) => string;
32
26
  export declare function getLocation(loc: Partial<AST.SourceLocation>, fieldName?: string, offset?: {