@checkdigit/eslint-plugin 6.6.0-PR.75-a513 → 6.6.0-PR.75-8f9f

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.
Files changed (63) hide show
  1. package/dist-cjs/index.cjs +519 -428
  2. package/dist-cjs/metafile.json +169 -178
  3. package/dist-mjs/{fixture → agent}/add-url-domain.mjs +2 -2
  4. package/dist-mjs/{fixture → agent}/fetch-response-body-json.mjs +2 -2
  5. package/dist-mjs/agent/fetch-response-header-getter.mjs +117 -0
  6. package/dist-mjs/{fixture → agent}/fetch-then.mjs +6 -6
  7. package/dist-mjs/{fixture → agent}/fetch.mjs +3 -3
  8. package/dist-mjs/{fixture → agent}/no-fixture.mjs +6 -6
  9. package/dist-mjs/{fixture → agent}/no-full-response.mjs +4 -4
  10. package/dist-mjs/{fixture → agent}/no-service-wrapper.mjs +4 -4
  11. package/dist-mjs/{fixture → agent}/no-status-code.mjs +2 -2
  12. package/dist-mjs/{fixture → agent}/response-reference.mjs +3 -3
  13. package/dist-mjs/{fixture → agent}/url.mjs +2 -2
  14. package/dist-mjs/index.mjs +21 -18
  15. package/dist-mjs/library/format.mjs +14 -0
  16. package/dist-mjs/{ast → library}/tree.mjs +2 -2
  17. package/dist-mjs/library/ts-tree.mjs +72 -0
  18. package/dist-mjs/{fixture → library}/variable.mjs +2 -2
  19. package/dist-mjs/require-resolve-full-response.mjs +156 -0
  20. package/dist-types/index.d.ts +3 -2
  21. package/dist-types/{ast → library}/ts-tree.d.ts +1 -0
  22. package/dist-types/require-resolve-full-response.d.ts +4 -0
  23. package/package.json +1 -1
  24. package/src/{fixture → agent}/add-url-domain.ts +2 -2
  25. package/src/{fixture/fetch-response-header-getter-ts.ts → agent/fetch-response-header-getter.ts} +32 -21
  26. package/src/{fixture → agent}/fetch-then.ts +4 -5
  27. package/src/{fixture → agent}/fetch.ts +1 -1
  28. package/src/{fixture → agent}/no-fixture.ts +4 -5
  29. package/src/{fixture → agent}/no-full-response.ts +2 -2
  30. package/src/{fixture → agent}/no-service-wrapper.ts +2 -2
  31. package/src/{fixture → agent}/response-reference.ts +1 -1
  32. package/src/index.ts +18 -15
  33. package/src/{ast → library}/ts-tree.ts +11 -0
  34. package/src/require-resolve-full-response.ts +199 -0
  35. package/dist-mjs/ast/format.mjs +0 -14
  36. package/dist-mjs/ast/ts-tree.mjs +0 -65
  37. package/dist-mjs/fixture/fetch-header-getter.mjs +0 -71
  38. package/dist-mjs/fixture/fetch-response-header-getter-ts.mjs +0 -110
  39. package/dist-mjs/fixture/ts-tree.mjs +0 -12
  40. package/dist-types/fixture/fetch-header-getter.d.ts +0 -4
  41. package/dist-types/fixture/ts-tree.d.ts +0 -2
  42. package/src/fixture/fetch-header-getter.ts +0 -91
  43. package/src/fixture/ts-tree.ts +0 -14
  44. /package/dist-types/{fixture → agent}/add-url-domain.d.ts +0 -0
  45. /package/dist-types/{fixture → agent}/fetch-response-body-json.d.ts +0 -0
  46. /package/dist-types/{fixture/fetch-response-header-getter-ts.d.ts → agent/fetch-response-header-getter.d.ts} +0 -0
  47. /package/dist-types/{fixture → agent}/fetch-then.d.ts +0 -0
  48. /package/dist-types/{fixture → agent}/fetch.d.ts +0 -0
  49. /package/dist-types/{fixture → agent}/no-fixture.d.ts +0 -0
  50. /package/dist-types/{fixture → agent}/no-full-response.d.ts +0 -0
  51. /package/dist-types/{fixture → agent}/no-service-wrapper.d.ts +0 -0
  52. /package/dist-types/{fixture → agent}/no-status-code.d.ts +0 -0
  53. /package/dist-types/{fixture → agent}/response-reference.d.ts +0 -0
  54. /package/dist-types/{fixture → agent}/url.d.ts +0 -0
  55. /package/dist-types/{ast → library}/format.d.ts +0 -0
  56. /package/dist-types/{ast → library}/tree.d.ts +0 -0
  57. /package/dist-types/{fixture → library}/variable.d.ts +0 -0
  58. /package/src/{fixture → agent}/fetch-response-body-json.ts +0 -0
  59. /package/src/{fixture → agent}/no-status-code.ts +0 -0
  60. /package/src/{fixture → agent}/url.ts +0 -0
  61. /package/src/{ast → library}/format.ts +0 -0
  62. /package/src/{ast → library}/tree.ts +0 -0
  63. /package/src/{fixture → library}/variable.ts +0 -0
@@ -0,0 +1,72 @@
1
+ // src/library/ts-tree.ts
2
+ import { AST_NODE_TYPES } from "@typescript-eslint/utils";
3
+ function getParent(node) {
4
+ return node.parent;
5
+ }
6
+ function getAncestor(node, matcher, exitMatcher) {
7
+ const parent = getParent(node);
8
+ if (!parent) {
9
+ return void 0;
10
+ } else if (typeof matcher === "string" && parent.type === matcher) {
11
+ return parent;
12
+ } else if (typeof matcher === "function" && matcher(parent)) {
13
+ return parent;
14
+ } else if (typeof exitMatcher === "string" && parent.type === exitMatcher) {
15
+ return void 0;
16
+ } else if (typeof exitMatcher === "function" && exitMatcher(parent)) {
17
+ return void 0;
18
+ }
19
+ return getAncestor(parent, matcher, exitMatcher);
20
+ }
21
+ function isBlockStatement(node) {
22
+ return node.type.endsWith("Statement") || node.type.endsWith("Declaration");
23
+ }
24
+ function getEnclosingStatement(node) {
25
+ return getAncestor(node, isBlockStatement);
26
+ }
27
+ function getEnclosingScopeNode(node) {
28
+ return getAncestor(
29
+ node,
30
+ (parentNode) => ["FunctionExpression", "FunctionDeclaration", "ArrowFunctionExpression", "Program"].includes(parentNode.type)
31
+ );
32
+ }
33
+ function isUsedInArrayOrAsArgument(node) {
34
+ if (isBlockStatement(node)) {
35
+ return false;
36
+ }
37
+ const parent = getParent(node);
38
+ if (!parent) {
39
+ return false;
40
+ }
41
+ if (parent.type === AST_NODE_TYPES.ArrayExpression || parent.type === AST_NODE_TYPES.CallExpression && parent.arguments.includes(node)) {
42
+ return true;
43
+ }
44
+ return isUsedInArrayOrAsArgument(parent);
45
+ }
46
+ function getEnclosingFunction(node) {
47
+ if (node.type === AST_NODE_TYPES.FunctionDeclaration || node.type === AST_NODE_TYPES.FunctionExpression || node.type === AST_NODE_TYPES.ArrowFunctionExpression) {
48
+ return node;
49
+ }
50
+ const parent = getParent(node);
51
+ if (!parent) {
52
+ return;
53
+ }
54
+ return getEnclosingFunction(parent);
55
+ }
56
+ function getTypeParentNode(node) {
57
+ if (!node) {
58
+ return void 0;
59
+ }
60
+ return node.type === AST_NODE_TYPES.TSTypeAnnotation || node.type === AST_NODE_TYPES.TSAsExpression ? node : getTypeParentNode(node.parent);
61
+ }
62
+ export {
63
+ getAncestor,
64
+ getEnclosingFunction,
65
+ getEnclosingScopeNode,
66
+ getEnclosingStatement,
67
+ getParent,
68
+ getTypeParentNode,
69
+ isBlockStatement,
70
+ isUsedInArrayOrAsArgument
71
+ };
72
+ //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vc3JjL2xpYnJhcnkvdHMtdHJlZS50cyJdLAogICJtYXBwaW5ncyI6ICI7QUFRQSxTQUFTLHNCQUFnQztBQVFsQyxTQUFTLFVBQVUsTUFBdUQ7QUFDL0UsU0FBUSxLQUF3QztBQUNsRDtBQUVPLFNBQVMsWUFDZCxNQUNBLFNBQ0EsYUFDMkI7QUFDM0IsUUFBTSxTQUFTLFVBQVUsSUFBSTtBQUM3QixNQUFJLENBQUMsUUFBUTtBQUNYLFdBQU87QUFBQSxFQUNULFdBQVcsT0FBTyxZQUFZLFlBQVksT0FBTyxTQUFTLFNBQVM7QUFDakUsV0FBTztBQUFBLEVBQ1QsV0FBVyxPQUFPLFlBQVksY0FBYyxRQUFRLE1BQU0sR0FBRztBQUMzRCxXQUFPO0FBQUEsRUFDVCxXQUFXLE9BQU8sZ0JBQWdCLFlBQVksT0FBTyxTQUFTLGFBQWE7QUFDekUsV0FBTztBQUFBLEVBQ1QsV0FBVyxPQUFPLGdCQUFnQixjQUFjLFlBQVksTUFBTSxHQUFHO0FBQ25FLFdBQU87QUFBQSxFQUNUO0FBQ0EsU0FBTyxZQUFZLFFBQVEsU0FBUyxXQUFXO0FBQ2pEO0FBRU8sU0FBUyxpQkFBaUIsTUFBcUI7QUFDcEQsU0FBTyxLQUFLLEtBQUssU0FBUyxXQUFXLEtBQUssS0FBSyxLQUFLLFNBQVMsYUFBYTtBQUM1RTtBQUVPLFNBQVMsc0JBQXNCLE1BQXFCO0FBQ3pELFNBQU8sWUFBWSxNQUFNLGdCQUFnQjtBQUMzQztBQUVPLFNBQVMsc0JBQXNCLE1BQXFCO0FBQ3pELFNBQU87QUFBQSxJQUFZO0FBQUEsSUFBTSxDQUFDLGVBQ3hCLENBQUMsc0JBQXNCLHVCQUF1QiwyQkFBMkIsU0FBUyxFQUFFLFNBQVMsV0FBVyxJQUFJO0FBQUEsRUFDOUc7QUFDRjtBQUVPLFNBQVMsMEJBQTBCLE1BQXFCO0FBQzdELE1BQUksaUJBQWlCLElBQUksR0FBRztBQUMxQixXQUFPO0FBQUEsRUFDVDtBQUVBLFFBQU0sU0FBUyxVQUFVLElBQUk7QUFDN0IsTUFBSSxDQUFDLFFBQVE7QUFDWCxXQUFPO0FBQUEsRUFDVDtBQUVBLE1BQ0UsT0FBTyxTQUFTLGVBQWUsbUJBQzlCLE9BQU8sU0FBUyxlQUFlLGtCQUFrQixPQUFPLFVBQVUsU0FBUyxJQUEyQixHQUN2RztBQUNBLFdBQU87QUFBQSxFQUNUO0FBR0EsU0FBTywwQkFBMEIsTUFBTTtBQUN6QztBQUVPLFNBQVMscUJBQXFCLE1BQXFCO0FBQ3hELE1BQ0UsS0FBSyxTQUFTLGVBQWUsdUJBQzdCLEtBQUssU0FBUyxlQUFlLHNCQUM3QixLQUFLLFNBQVMsZUFBZSx5QkFDN0I7QUFDQSxXQUFPO0FBQUEsRUFDVDtBQUVBLFFBQU0sU0FBUyxVQUFVLElBQUk7QUFDN0IsTUFBSSxDQUFDLFFBQVE7QUFDWDtBQUFBLEVBQ0Y7QUFDQSxTQUFPLHFCQUFxQixNQUFNO0FBQ3BDO0FBRU8sU0FBUyxrQkFDZCxNQUNpRTtBQUNqRSxNQUFJLENBQUMsTUFBTTtBQUNULFdBQU87QUFBQSxFQUNUO0FBQ0EsU0FBTyxLQUFLLFNBQVMsZUFBZSxvQkFBb0IsS0FBSyxTQUFTLGVBQWUsaUJBQ2pGLE9BQ0Esa0JBQWtCLEtBQUssTUFBTTtBQUNuQzsiLAogICJuYW1lcyI6IFtdCn0K
@@ -1,8 +1,8 @@
1
- // src/fixture/variable.ts
1
+ // src/library/variable.ts
2
2
  function isValidPropertyName(name) {
3
3
  return typeof name === "string" && /^[a-zA-Z_$][a-zA-Z_$0-9]*$/u.test(name);
4
4
  }
5
5
  export {
6
6
  isValidPropertyName
7
7
  };
8
- //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vc3JjL2ZpeHR1cmUvdmFyaWFibGUudHMiXSwKICAibWFwcGluZ3MiOiAiO0FBRU8sU0FBUyxvQkFBb0IsTUFBZTtBQUNqRCxTQUFPLE9BQU8sU0FBUyxZQUFZLDhCQUE4QixLQUFLLElBQUk7QUFDNUU7IiwKICAibmFtZXMiOiBbXQp9Cg==
8
+ //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vc3JjL2xpYnJhcnkvdmFyaWFibGUudHMiXSwKICAibWFwcGluZ3MiOiAiO0FBRU8sU0FBUyxvQkFBb0IsTUFBZTtBQUNqRCxTQUFPLE9BQU8sU0FBUyxZQUFZLDhCQUE4QixLQUFLLElBQUk7QUFDNUU7IiwKICAibmFtZXMiOiBbXQp9Cg==
@@ -0,0 +1,156 @@
1
+ // src/require-resolve-full-response.ts
2
+ import { AST_NODE_TYPES, ESLintUtils } from "@typescript-eslint/utils";
3
+ import { DefinitionType } from "@typescript-eslint/scope-manager";
4
+ import { PLAIN_URL_REGEXP, TOKENIZED_URL_REGEXP } from "./agent/url.mjs";
5
+ import { strict as assert } from "node:assert";
6
+ import getDocumentationUrl from "./get-documentation-url.mjs";
7
+ import { getEnclosingScopeNode } from "./library/ts-tree.mjs";
8
+ var ruleId = "require-resolve-full-response";
9
+ var createRule = ESLintUtils.RuleCreator((name) => getDocumentationUrl(name));
10
+ var rule = createRule({
11
+ name: ruleId,
12
+ meta: {
13
+ type: "suggestion",
14
+ docs: {
15
+ description: "Prefer native fetch over customized service wrapper."
16
+ },
17
+ messages: {
18
+ invalidOptions: '"options" argument should be provided with "resolveWithFullResponse" property set as "true". Otherwise, it indicates that the response body will be obtained without status code assertion which could result in unexpected issue.',
19
+ unknownError: 'Unknown error occurred in file "{{fileName}}": {{ error }}.'
20
+ },
21
+ schema: []
22
+ },
23
+ defaultOptions: [],
24
+ create(context) {
25
+ const sourceCode = context.sourceCode;
26
+ const scopeManager = sourceCode.scopeManager;
27
+ const parserService = ESLintUtils.getParserServices(context);
28
+ const typeChecker = parserService.program.getTypeChecker();
29
+ function isUrlArgumentValid(urlArgument, scope) {
30
+ if (urlArgument?.type === AST_NODE_TYPES.Literal && typeof urlArgument.value === "string" || urlArgument?.type === AST_NODE_TYPES.TemplateLiteral) {
31
+ const urlText = sourceCode.getText(urlArgument);
32
+ return PLAIN_URL_REGEXP.test(urlText) || TOKENIZED_URL_REGEXP.test(urlText);
33
+ }
34
+ if (urlArgument?.type === AST_NODE_TYPES.Identifier) {
35
+ const foundVariable = scope.variables.find((variable) => variable.name === urlArgument.name);
36
+ if (foundVariable) {
37
+ const variableDefinition = foundVariable.defs.find((def) => def.type === DefinitionType.Variable);
38
+ assert.ok(variableDefinition, `Variable "${urlArgument.name}" not defined in scope`);
39
+ const variableDefinitionNode = variableDefinition.node;
40
+ assert.ok(variableDefinitionNode.type === AST_NODE_TYPES.VariableDeclarator);
41
+ assert.ok(variableDefinitionNode.init, "Variable definition node has no init property");
42
+ return isUrlArgumentValid(variableDefinitionNode.init, scope);
43
+ }
44
+ }
45
+ return false;
46
+ }
47
+ function getType(identifier) {
48
+ const variable = parserService.esTreeNodeToTSNodeMap.get(identifier);
49
+ const variableType = typeChecker.getTypeAtLocation(variable);
50
+ return typeChecker.typeToString(variableType);
51
+ }
52
+ function isServiceLikeName(name) {
53
+ return /.*[Ss]ervice$/u.test(name);
54
+ }
55
+ function isCalleeServiceWrapper(serviceCall) {
56
+ const callee = serviceCall.callee;
57
+ if (callee.type !== AST_NODE_TYPES.MemberExpression) {
58
+ return false;
59
+ }
60
+ const endpoint = callee.object;
61
+ if (endpoint.type === AST_NODE_TYPES.Identifier) {
62
+ return getType(endpoint) === "Endpoint" || isServiceLikeName(endpoint.name);
63
+ }
64
+ if (endpoint.type !== AST_NODE_TYPES.CallExpression) {
65
+ return false;
66
+ }
67
+ const [contextArgument] = endpoint.arguments;
68
+ if (contextArgument?.type !== AST_NODE_TYPES.Identifier) {
69
+ return false;
70
+ }
71
+ if (contextArgument.name !== "EMPTY_CONTEXT" && getType(contextArgument) !== "InboundContext") {
72
+ return false;
73
+ }
74
+ const service = endpoint.callee;
75
+ if (service.type === AST_NODE_TYPES.Identifier) {
76
+ return getType(service) === "ResolvedService";
77
+ }
78
+ if (service.type !== AST_NODE_TYPES.MemberExpression) {
79
+ return false;
80
+ }
81
+ const services = service.object;
82
+ if (services.type === AST_NODE_TYPES.Identifier) {
83
+ return getType(services) === "ResolvedServices";
84
+ }
85
+ if (services.type !== AST_NODE_TYPES.MemberExpression) {
86
+ return false;
87
+ }
88
+ const configuration = services.object;
89
+ if (configuration.type === AST_NODE_TYPES.Identifier) {
90
+ return ["Configuration", "Configuration<ResolvedServices>"].includes(getType(configuration));
91
+ }
92
+ if (configuration.type !== AST_NODE_TYPES.MemberExpression) {
93
+ return false;
94
+ }
95
+ const fixture = configuration.object;
96
+ if (fixture.type === AST_NODE_TYPES.Identifier) {
97
+ return fixture.name === "fixture" || getType(fixture) === "Fixture";
98
+ }
99
+ return false;
100
+ }
101
+ return {
102
+ "CallExpression[callee.property.name=/^(head|get|put|post|del|patch)$/]": (serviceCall) => {
103
+ try {
104
+ if (!isCalleeServiceWrapper(serviceCall)) {
105
+ return;
106
+ }
107
+ const enclosingScopeNode = getEnclosingScopeNode(serviceCall);
108
+ assert.ok(enclosingScopeNode, "enclosingScopeNode is undefined");
109
+ const scope = scopeManager?.acquire(enclosingScopeNode);
110
+ assert.ok(scope, "scope is undefined");
111
+ const urlArgument = serviceCall.arguments[0];
112
+ if (!isUrlArgumentValid(urlArgument, scope)) {
113
+ return;
114
+ }
115
+ assert.ok(serviceCall.callee.type === AST_NODE_TYPES.MemberExpression);
116
+ assert.ok(serviceCall.callee.property.type === AST_NODE_TYPES.Identifier);
117
+ const method = serviceCall.callee.property.name;
118
+ const optionsArgument = ["get", "head", "del"].includes(method) ? serviceCall.arguments[1] : serviceCall.arguments[2];
119
+ if (optionsArgument === void 0 || optionsArgument.type !== AST_NODE_TYPES.ObjectExpression) {
120
+ context.report({
121
+ node: serviceCall,
122
+ messageId: "invalidOptions"
123
+ });
124
+ return;
125
+ }
126
+ const resolveWithFullResponseProperty = optionsArgument.properties.find(
127
+ (property) => property.type === AST_NODE_TYPES.Property && property.key.type === AST_NODE_TYPES.Identifier && property.key.name === "resolveWithFullResponse"
128
+ );
129
+ if (resolveWithFullResponseProperty?.type !== AST_NODE_TYPES.Property || resolveWithFullResponseProperty.value.type !== AST_NODE_TYPES.Literal || resolveWithFullResponseProperty.value.value !== true) {
130
+ context.report({
131
+ node: optionsArgument,
132
+ messageId: "invalidOptions"
133
+ });
134
+ return;
135
+ }
136
+ } catch (error) {
137
+ console.error(`Failed to apply ${ruleId} rule for file "${context.filename}":`, error);
138
+ context.report({
139
+ node: serviceCall,
140
+ messageId: "unknownError",
141
+ data: {
142
+ fileName: context.filename,
143
+ error: error instanceof Error ? error.toString() : JSON.stringify(error)
144
+ }
145
+ });
146
+ }
147
+ }
148
+ };
149
+ }
150
+ });
151
+ var require_resolve_full_response_default = rule;
152
+ export {
153
+ require_resolve_full_response_default as default,
154
+ ruleId
155
+ };
156
+ //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vc3JjL3JlcXVpcmUtcmVzb2x2ZS1mdWxsLXJlc3BvbnNlLnRzIl0sCiAgIm1hcHBpbmdzIjogIjtBQVFBLFNBQVMsZ0JBQWdCLG1CQUE2QjtBQUN0RCxTQUFTLHNCQUFrQztBQUMzQyxTQUFTLGtCQUFrQiw0QkFBNEI7QUFDdkQsU0FBUyxVQUFVLGNBQWM7QUFDakMsT0FBTyx5QkFBeUI7QUFDaEMsU0FBUyw2QkFBNkI7QUFFL0IsSUFBTSxTQUFTO0FBRXRCLElBQU0sYUFBYSxZQUFZLFlBQVksQ0FBQyxTQUFTLG9CQUFvQixJQUFJLENBQUM7QUFFOUUsSUFBTSxPQUFPLFdBQVc7QUFBQSxFQUN0QixNQUFNO0FBQUEsRUFDTixNQUFNO0FBQUEsSUFDSixNQUFNO0FBQUEsSUFDTixNQUFNO0FBQUEsTUFDSixhQUFhO0FBQUEsSUFDZjtBQUFBLElBQ0EsVUFBVTtBQUFBLE1BQ1IsZ0JBQ0U7QUFBQSxNQUNGLGNBQWM7QUFBQSxJQUNoQjtBQUFBLElBQ0EsUUFBUSxDQUFDO0FBQUEsRUFDWDtBQUFBLEVBQ0EsZ0JBQWdCLENBQUM7QUFBQSxFQUNqQixPQUFPLFNBQVM7QUFDZCxVQUFNLGFBQWEsUUFBUTtBQUMzQixVQUFNLGVBQWUsV0FBVztBQUNoQyxVQUFNLGdCQUFnQixZQUFZLGtCQUFrQixPQUFPO0FBQzNELFVBQU0sY0FBYyxjQUFjLFFBQVEsZUFBZTtBQUV6RCxhQUFTLG1CQUFtQixhQUF3QyxPQUFjO0FBQ2hGLFVBQ0csYUFBYSxTQUFTLGVBQWUsV0FBVyxPQUFPLFlBQVksVUFBVSxZQUM5RSxhQUFhLFNBQVMsZUFBZSxpQkFDckM7QUFDQSxjQUFNLFVBQVUsV0FBVyxRQUFRLFdBQVc7QUFDOUMsZUFBTyxpQkFBaUIsS0FBSyxPQUFPLEtBQUsscUJBQXFCLEtBQUssT0FBTztBQUFBLE1BQzVFO0FBRUEsVUFBSSxhQUFhLFNBQVMsZUFBZSxZQUFZO0FBQ25ELGNBQU0sZ0JBQWdCLE1BQU0sVUFBVSxLQUFLLENBQUMsYUFBYSxTQUFT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
@@ -12,7 +12,6 @@ declare const _default: {
12
12
  "invalid-json-stringify": import("eslint").Rule.RuleModule;
13
13
  "no-promise-instance-method": import("eslint").Rule.RuleModule;
14
14
  "no-fixture": import("eslint").Rule.RuleModule;
15
- "fetch-header-getter": import("eslint").Rule.RuleModule;
16
15
  "fetch-then": import("eslint").Rule.RuleModule;
17
16
  "no-service-wrapper": import("@typescript-eslint/utils/ts-eslint").RuleModule<"unknownError" | "preferNativeFetch" | "invalidOptions", never[], import("@typescript-eslint/utils/ts-eslint").RuleListener>;
18
17
  "no-status-code": import("@typescript-eslint/utils/ts-eslint").RuleModule<"unknownError" | "replaceStatusCode", never[], import("@typescript-eslint/utils/ts-eslint").RuleListener>;
@@ -20,6 +19,7 @@ declare const _default: {
20
19
  "fetch-response-header-getter-ts": import("@typescript-eslint/utils/ts-eslint").RuleModule<"unknownError" | "useGetter", never[], import("@typescript-eslint/utils/ts-eslint").RuleListener>;
21
20
  "add-url-domain": import("@typescript-eslint/utils/ts-eslint").RuleModule<"addDomain" | "unknownError", never[], import("@typescript-eslint/utils/ts-eslint").RuleListener>;
22
21
  "no-full-response": import("@typescript-eslint/utils/ts-eslint").RuleModule<"unknownError" | "removeFullResponse", never[], import("@typescript-eslint/utils/ts-eslint").RuleListener>;
22
+ "require-resolve-full-response": import("@typescript-eslint/utils/ts-eslint").RuleModule<"unknownError" | "invalidOptions", never[], import("@typescript-eslint/utils/ts-eslint").RuleListener>;
23
23
  };
24
24
  configs: {
25
25
  all: {
@@ -35,6 +35,8 @@ declare const _default: {
35
35
  '@checkdigit/no-test-import': string;
36
36
  "@checkdigit/invalid-json-stringify": string;
37
37
  "@checkdigit/no-promise-instance-method": string;
38
+ "@checkdigit/no-full-response": string;
39
+ "@checkdigit/require-resolve-full-response": string;
38
40
  };
39
41
  };
40
42
  recommended: {
@@ -55,7 +57,6 @@ declare const _default: {
55
57
  agent: {
56
58
  rules: {
57
59
  "@checkdigit/no-fixture": string;
58
- "@checkdigit/fetch-header-getter": string;
59
60
  "@checkdigit/fetch-then": string;
60
61
  "@checkdigit/no-service-wrapper": string;
61
62
  "@checkdigit/no-status-code": string;
@@ -6,3 +6,4 @@ export declare function getEnclosingStatement(node: TSESTree.Node): TSESTree.Nod
6
6
  export declare function getEnclosingScopeNode(node: TSESTree.Node): TSESTree.Node | undefined;
7
7
  export declare function isUsedInArrayOrAsArgument(node: TSESTree.Node): boolean;
8
8
  export declare function getEnclosingFunction(node: TSESTree.Node): TSESTree.ArrowFunctionExpression | TSESTree.FunctionDeclarationWithOptionalName | TSESTree.FunctionExpression | undefined;
9
+ export declare function getTypeParentNode(node: TSESTree.Node | undefined): TSESTree.TSTypeAnnotation | TSESTree.TSAsExpression | undefined;
@@ -0,0 +1,4 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ export declare const ruleId = "require-resolve-full-response";
3
+ declare const rule: ESLintUtils.RuleModule<"unknownError" | "invalidOptions", never[], ESLintUtils.RuleListener>;
4
+ export default rule;
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"@checkdigit/eslint-plugin","version":"6.6.0-PR.75-a513","description":"Check Digit eslint plugins","keywords":["eslint","eslintplugin"],"homepage":"https://github.com/checkdigit/eslint-plugin#readme","bugs":{"url":"https://github.com/checkdigit/eslint-plugin/issues"},"repository":{"type":"git","url":"https://github.com/checkdigit/eslint-plugin"},"license":"MIT","author":"Check Digit, LLC","sideEffects":false,"type":"module","exports":{".":{"types":"./dist-types/index.d.ts","require":"./dist-cjs/index.cjs","import":"./dist-mjs/index.mjs","default":"./dist-mjs/index.mjs"}},"files":["src","dist-types","dist-cjs","dist-mjs","!src/**/*.test.ts","!src/**/*.spec.ts","!dist-types/**/*.test.d.ts","!dist-types/**/*.spec.d.ts","!dist-cjs/**/*.test.cjs","!dist-cjs/**/*.spec.cjs","!dist-mjs/**/*.test.mjs","!dist-mjs/**/*.spec.mjs","SECURITY.md"],"scripts":{"build:dist-cjs":"rimraf dist-cjs && npx builder --type=commonjs --sourceMap --entryPoint=index.ts --outDir=dist-cjs --outFile=index.cjs --external=espree && echo \"module.exports = module.exports.default;\" >> dist-cjs/index.cjs","build:dist-mjs":"rimraf dist-mjs && npx builder --type=module --sourceMap --outDir=dist-mjs && node dist-mjs/index.mjs","build:dist-types":"rimraf dist-types && npx builder --type=types --outDir=dist-types","ci:compile":"tsc --noEmit","ci:coverage":"NODE_OPTIONS=\"--disable-warning ExperimentalWarning --experimental-vm-modules\" jest --coverage=true","ci:lint":"npm run lint","ci:style":"npm run prettier","ci:test":"NODE_OPTIONS=\"--disable-warning ExperimentalWarning --experimental-vm-modules\" jest --coverage=false","lint":"eslint --max-warnings 0 --ignore-path .gitignore .","lint:fix":"eslint --ignore-path .gitignore . --fix","prepublishOnly":"npm run build:dist-types && npm run build:dist-cjs && npm run build:dist-mjs","prettier":"prettier --ignore-path .gitignore --list-different .","prettier:fix":"prettier --ignore-path .gitignore --write .","test":"npm run ci:compile && npm run ci:test && npm run ci:lint && npm run ci:style"},"prettier":"@checkdigit/prettier-config","jest":{"preset":"@checkdigit/jest-config"},"dependencies":{"@typescript-eslint/type-utils":"7.18.0","@typescript-eslint/utils":"7.18.0","ts-api-utils":"^1.3.0"},"devDependencies":{"@checkdigit/jest-config":"^6.0.2","@checkdigit/prettier-config":"^5.5.0","@checkdigit/typescript-config":"6.0.0","@types/eslint":"^8.56.10","@typescript-eslint/eslint-plugin":"^7.18.0","@typescript-eslint/parser":"^7.18.0","@typescript-eslint/rule-tester":"7.18.0","eslint-config-prettier":"^9.1.0","eslint-plugin-eslint-plugin":"^6.2.0","eslint-plugin-import":"^2.29.1","eslint-plugin-no-only-tests":"^3.1.0","eslint-plugin-no-secrets":"^1.0.2","eslint-plugin-node":"^11.1.0","eslint-plugin-sonarjs":"0.24.0"},"peerDependencies":{"eslint":">=8 <9"},"engines":{"node":">=20.14"}}
1
+ {"name":"@checkdigit/eslint-plugin","version":"6.6.0-PR.75-8f9f","description":"Check Digit eslint plugins","keywords":["eslint","eslintplugin"],"homepage":"https://github.com/checkdigit/eslint-plugin#readme","bugs":{"url":"https://github.com/checkdigit/eslint-plugin/issues"},"repository":{"type":"git","url":"https://github.com/checkdigit/eslint-plugin"},"license":"MIT","author":"Check Digit, LLC","sideEffects":false,"type":"module","exports":{".":{"types":"./dist-types/index.d.ts","require":"./dist-cjs/index.cjs","import":"./dist-mjs/index.mjs","default":"./dist-mjs/index.mjs"}},"files":["src","dist-types","dist-cjs","dist-mjs","!src/**/*.test.ts","!src/**/*.spec.ts","!dist-types/**/*.test.d.ts","!dist-types/**/*.spec.d.ts","!dist-cjs/**/*.test.cjs","!dist-cjs/**/*.spec.cjs","!dist-mjs/**/*.test.mjs","!dist-mjs/**/*.spec.mjs","SECURITY.md"],"scripts":{"build:dist-cjs":"rimraf dist-cjs && npx builder --type=commonjs --sourceMap --entryPoint=index.ts --outDir=dist-cjs --outFile=index.cjs --external=espree && echo \"module.exports = module.exports.default;\" >> dist-cjs/index.cjs","build:dist-mjs":"rimraf dist-mjs && npx builder --type=module --sourceMap --outDir=dist-mjs && node dist-mjs/index.mjs","build:dist-types":"rimraf dist-types && npx builder --type=types --outDir=dist-types","ci:compile":"tsc --noEmit","ci:coverage":"NODE_OPTIONS=\"--disable-warning ExperimentalWarning --experimental-vm-modules\" jest --coverage=true","ci:lint":"npm run lint","ci:style":"npm run prettier","ci:test":"NODE_OPTIONS=\"--disable-warning ExperimentalWarning --experimental-vm-modules\" jest --coverage=false","lint":"eslint --max-warnings 0 --ignore-path .gitignore .","lint:fix":"eslint --ignore-path .gitignore . --fix","prepublishOnly":"npm run build:dist-types && npm run build:dist-cjs && npm run build:dist-mjs","prettier":"prettier --ignore-path .gitignore --list-different .","prettier:fix":"prettier --ignore-path .gitignore --write .","test":"npm run ci:compile && npm run ci:test && npm run ci:lint && npm run ci:style"},"prettier":"@checkdigit/prettier-config","jest":{"preset":"@checkdigit/jest-config"},"dependencies":{"@typescript-eslint/type-utils":"7.18.0","@typescript-eslint/utils":"7.18.0","ts-api-utils":"^1.3.0"},"devDependencies":{"@checkdigit/jest-config":"^6.0.2","@checkdigit/prettier-config":"^5.5.0","@checkdigit/typescript-config":"6.0.0","@types/eslint":"^8.56.10","@typescript-eslint/eslint-plugin":"^7.18.0","@typescript-eslint/parser":"^7.18.0","@typescript-eslint/rule-tester":"7.18.0","eslint-config-prettier":"^9.1.0","eslint-plugin-eslint-plugin":"^6.2.0","eslint-plugin-import":"^2.29.1","eslint-plugin-no-only-tests":"^3.1.0","eslint-plugin-no-secrets":"^1.0.2","eslint-plugin-node":"^11.1.0","eslint-plugin-sonarjs":"0.24.0","http-status-codes":"^2.3.0"},"peerDependencies":{"eslint":">=8 <9"},"engines":{"node":">=20.14"}}
@@ -43,8 +43,8 @@ const rule = createRule({
43
43
  return;
44
44
  }
45
45
 
46
- const urlText = sourceCode.getText(basePathDeclarator.init); /*?*/
47
- const replacement = addBasePathUrlDomain(urlText); /*?*/
46
+ const urlText = sourceCode.getText(basePathDeclarator.init);
47
+ const replacement = addBasePathUrlDomain(urlText);
48
48
 
49
49
  if (replacement !== urlText) {
50
50
  context.report({
@@ -10,6 +10,7 @@ import { AST_NODE_TYPES, ESLintUtils, TSESTree } from '@typescript-eslint/utils'
10
10
  import getDocumentationUrl from '../get-documentation-url';
11
11
 
12
12
  export const ruleId = 'fetch-response-header-getter-ts';
13
+ const HEADER_BUILTIN_FUNCTIONS = Object.keys(Headers.prototype);
13
14
 
14
15
  const createRule = ESLintUtils.RuleCreator((name) => getDocumentationUrl(name));
15
16
 
@@ -34,37 +35,38 @@ const rule = createRule({
34
35
  const sourceCode = context.sourceCode;
35
36
 
36
37
  return {
37
- 'MemberExpression[object.property.name="headers"]': (responseHeadersAccess: TSESTree.MemberExpression) => {
38
+ MemberExpression: (responseHeadersAccess: TSESTree.MemberExpression) => {
38
39
  try {
39
40
  if (
40
41
  responseHeadersAccess.property.type === AST_NODE_TYPES.Identifier &&
41
- responseHeadersAccess.property.name === 'get'
42
+ HEADER_BUILTIN_FUNCTIONS.includes(responseHeadersAccess.property.name)
42
43
  ) {
43
- // getter is already being used
44
+ // skip Headers's built-in function calls
44
45
  return;
45
46
  }
46
47
 
47
48
  const responseHeadersTsNode = parserServices.esTreeNodeToTSNodeMap.get(responseHeadersAccess.object);
48
- const responseType = typeChecker.getTypeAtLocation(responseHeadersTsNode);
49
-
50
- const shouldReplace = responseType.getProperties().some((symbol) => symbol.name === 'get');
51
- if (!shouldReplace) {
49
+ let responseHeadersType = typeChecker.getTypeAtLocation(responseHeadersTsNode);
50
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
51
+ responseHeadersType = responseHeadersType.isUnion() ? responseHeadersType.types[0]! : responseHeadersType;
52
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
53
+ const responseHeadersTypeName = // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
54
+ (responseHeadersType.symbol ?? responseHeadersType.aliasSymbol)?.escapedName;
55
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
56
+ if (responseHeadersTypeName !== 'Headers' && responseHeadersTypeName !== 'HeaderGetter') {
52
57
  return;
53
58
  }
54
59
 
55
- // let replacementText = 'xxx';
56
- // if (responseHeadersAccess.property.type === AST_NODE_TYPES.Identifier) {
57
- // replacementText = `${sourceCode.getText(responseHeadersAccess.object)}.get(${sourceCode.getText(responseHeadersAccess.property)})`;
58
- // }
59
60
  let replacementText: string;
60
- if (responseHeadersAccess.property.type === AST_NODE_TYPES.Identifier) {
61
- replacementText = `${sourceCode.getText(responseHeadersAccess.object)}.get(${sourceCode.getText(responseHeadersAccess.property)})`;
62
- } else if (responseHeadersAccess.property.type === AST_NODE_TYPES.TemplateLiteral) {
61
+ if (!responseHeadersAccess.computed) {
62
+ // e.g. headers.etag
63
+ replacementText = `${sourceCode.getText(responseHeadersAccess.object)}.get('${sourceCode.getText(responseHeadersAccess.property)}')`;
64
+ } else if (
65
+ responseHeadersAccess.property.type === AST_NODE_TYPES.Identifier ||
66
+ responseHeadersAccess.property.type === AST_NODE_TYPES.Literal ||
67
+ responseHeadersAccess.property.type === AST_NODE_TYPES.TemplateLiteral
68
+ ) {
63
69
  replacementText = `${sourceCode.getText(responseHeadersAccess.object)}.get(${sourceCode.getText(responseHeadersAccess.property)})`;
64
- } else if (responseHeadersAccess.property.type === AST_NODE_TYPES.Literal) {
65
- replacementText = responseHeadersAccess.computed
66
- ? `${sourceCode.getText(responseHeadersAccess.object)}.get(${sourceCode.getText(responseHeadersAccess.property)})`
67
- : `${sourceCode.getText(responseHeadersAccess.object)}.get('${sourceCode.getText(responseHeadersAccess.property)}')`;
68
70
  } else {
69
71
  throw new Error(`Unexpected property type: ${responseHeadersAccess.property.type}`);
70
72
  }
@@ -89,14 +91,21 @@ const rule = createRule({
89
91
  });
90
92
  }
91
93
  },
92
- 'CallExpression[callee.property.name="get"]:not([callee.object.name="request"])': (
93
- responseHeadersAccess: TSESTree.CallExpression,
94
- ) => {
94
+
95
+ // convert response.get() to response.headers.get()
96
+ 'CallExpression[callee.property.name="get"]': (responseHeadersAccess: TSESTree.CallExpression) => {
95
97
  try {
96
98
  if (responseHeadersAccess.callee.type !== AST_NODE_TYPES.MemberExpression) {
97
99
  return;
98
100
  }
99
101
 
102
+ // skip request-like calls
103
+ if (
104
+ responseHeadersAccess.callee.object.type !== AST_NODE_TYPES.Identifier ||
105
+ responseHeadersAccess.callee.object.name === 'request'
106
+ ) {
107
+ return;
108
+ }
100
109
  const responseNode = responseHeadersAccess.callee.object;
101
110
  const responseHeadersTsNode = parserServices.esTreeNodeToTSNodeMap.get(responseNode);
102
111
  const responseType = typeChecker.getTypeAtLocation(responseHeadersTsNode);
@@ -104,6 +113,8 @@ const rule = createRule({
104
113
  if (typeName === 'InboundContext' || typeName.endsWith('RequestType')) {
105
114
  return;
106
115
  }
116
+
117
+ // make sure the response type has "headers" property
107
118
  const hasHeadersProperty = responseType.getProperties().some((symbol) => symbol.name === 'headers');
108
119
  if (!hasHeadersProperty) {
109
120
  return;
@@ -8,12 +8,12 @@
8
8
 
9
9
  import type { CallExpression, Expression, MemberExpression, SimpleCallExpression } from 'estree';
10
10
  import { type Rule, type Scope, SourceCode } from 'eslint';
11
- import { getEnclosingFunction, getEnclosingStatement, getParent, isUsedInArrayOrAsArgument } from '../ast/tree';
11
+ import { getEnclosingFunction, getEnclosingStatement, getParent, isUsedInArrayOrAsArgument } from '../library/tree';
12
12
  import { hasAssertions, isInvalidResponseHeadersAccess } from './fetch';
13
13
  import { strict as assert } from 'node:assert';
14
14
  import getDocumentationUrl from '../get-documentation-url';
15
- import { getIndentation } from '../ast/format';
16
- import { isValidPropertyName } from './variable';
15
+ import { getIndentation } from '../library/format';
16
+ import { isValidPropertyName } from '../library/variable';
17
17
  import { replaceEndpointUrlPrefixWithBasePath } from './url';
18
18
 
19
19
  export const ruleId = 'fetch-then';
@@ -193,8 +193,7 @@ const rule: Rule.RuleModule = {
193
193
  messages: {
194
194
  preferNativeFetch: 'Prefer native fetch API over customized fixture API.',
195
195
  shouldUseHeaderGetter: 'Getter should be used to access response headers.',
196
- unknownError:
197
- 'Unknown error occurred in file "{{fileName}}": {{ error }}. Please manually convert the fixture API call to fetch API call.',
196
+ unknownError: 'Unknown error occurred in file "{{fileName}}": {{ error }}.',
198
197
  },
199
198
  fixable: 'code',
200
199
  schema: [],
@@ -1,6 +1,6 @@
1
1
  // fixture/fetch.ts
2
2
 
3
- import { getParent, isBlockStatement } from '../ast/tree';
3
+ import { getParent, isBlockStatement } from '../library/tree';
4
4
  import type { Node } from 'estree';
5
5
 
6
6
  export function getResponseBodyRetrievalText(responseVariableName: string) {
@@ -23,13 +23,13 @@ import {
23
23
  getEnclosingStatement,
24
24
  getParent,
25
25
  isUsedInArrayOrAsArgument,
26
- } from '../ast/tree';
26
+ } from '../library/tree';
27
27
  import { getResponseBodyRetrievalText, hasAssertions } from './fetch';
28
28
  import { analyzeResponseReferences } from './response-reference';
29
29
  import { strict as assert } from 'node:assert';
30
30
  import getDocumentationUrl from '../get-documentation-url';
31
- import { getIndentation } from '../ast/format';
32
- import { isValidPropertyName } from './variable';
31
+ import { getIndentation } from '../library/format';
32
+ import { isValidPropertyName } from '../library/variable';
33
33
  import { replaceEndpointUrlPrefixWithBasePath } from './url';
34
34
 
35
35
  export const ruleId = 'no-fixture';
@@ -228,8 +228,7 @@ const rule: Rule.RuleModule = {
228
228
  },
229
229
  messages: {
230
230
  preferNativeFetch: 'Prefer native fetch API over customized fixture API.',
231
- unknownError:
232
- 'Unknown error occurred in file "{{fileName}}": {{ error }}. Please manually convert the fixture API call to fetch API call.',
231
+ unknownError: 'Unknown error occurred in file "{{fileName}}": {{ error }}.',
233
232
  },
234
233
  fixable: 'code',
235
234
  schema: [],
@@ -9,7 +9,7 @@
9
9
  import { ESLintUtils, TSESTree } from '@typescript-eslint/utils';
10
10
  import { strict as assert } from 'node:assert';
11
11
  import getDocumentationUrl from '../get-documentation-url';
12
- import { getTypeParentNode } from './ts-tree';
12
+ import { getTypeParentNode } from '../library/ts-tree';
13
13
 
14
14
  export const ruleId = 'no-full-response';
15
15
 
@@ -23,7 +23,7 @@ const rule = createRule({
23
23
  description: 'Remove the usage of FullResponse type.',
24
24
  },
25
25
  messages: {
26
- removeFullResponse: 'Remove the usage of FullResponse type.',
26
+ removeFullResponse: 'Removing the usage of FullResponse type.',
27
27
  unknownError: 'Unknown error occurred in file "{{fileName}}": {{ error }}.',
28
28
  },
29
29
  fixable: 'code',
@@ -11,8 +11,8 @@ import { DefinitionType, type Scope } from '@typescript-eslint/scope-manager';
11
11
  import { PLAIN_URL_REGEXP, TOKENIZED_URL_REGEXP, replaceEndpointUrlPrefixWithDomain } from './url';
12
12
  import { strict as assert } from 'node:assert';
13
13
  import getDocumentationUrl from '../get-documentation-url';
14
- import { getEnclosingScopeNode } from '../ast/ts-tree';
15
- import { getIndentation } from '../ast/format';
14
+ import { getEnclosingScopeNode } from '../library/ts-tree';
15
+ import { getIndentation } from '../library/format';
16
16
 
17
17
  export const ruleId = 'no-service-wrapper';
18
18
 
@@ -9,7 +9,7 @@
9
9
  import type { MemberExpression, VariableDeclaration } from 'estree';
10
10
  import { type Scope } from 'eslint';
11
11
  import { strict as assert } from 'node:assert';
12
- import { getParent } from '../ast/tree';
12
+ import { getParent } from '../library/tree';
13
13
 
14
14
  /**
15
15
  * analyze response related variables and their references
package/src/index.ts CHANGED
@@ -6,19 +6,21 @@
6
6
  * This code is licensed under the MIT license (see LICENSE.txt for details).
7
7
  */
8
8
 
9
- import addUrlDomain, { ruleId as addUrlDomainRuleId } from './fixture/add-url-domain';
10
- import fetchHeaderGetter, { ruleId as fetchHeaderGetterRuleId } from './fixture/fetch-header-getter';
11
- import fetchResponseBodyJson, { ruleId as fetchResponseBodyJsonRuleId } from './fixture/fetch-response-body-json';
12
- import fetchResponseHeaderGetterTs, {
13
- ruleId as fetchResponseHeaderGetterTsRuleId,
14
- } from './fixture/fetch-response-header-getter-ts';
15
- import fetchThen, { ruleId as fetchThenRuleId } from './fixture/fetch-then';
9
+ import addUrlDomain, { ruleId as addUrlDomainRuleId } from './agent/add-url-domain';
10
+ import fetchResponseBodyJson, { ruleId as fetchResponseBodyJsonRuleId } from './agent/fetch-response-body-json';
11
+ import fetchResponseHeaderGetter, {
12
+ ruleId as fetchResponseHeaderGetterRuleId,
13
+ } from './agent/fetch-response-header-getter';
14
+ import fetchThen, { ruleId as fetchThenRuleId } from './agent/fetch-then';
16
15
  import invalidJsonStringify, { ruleId as invalidJsonStringifyRuleId } from './invalid-json-stringify';
17
- import noFixture, { ruleId as noFixtureRuleId } from './fixture/no-fixture';
18
- import noFullResponse, { ruleId as noFullResponseRuleId } from './fixture/no-full-response';
16
+ import noFixture, { ruleId as noFixtureRuleId } from './agent/no-fixture';
17
+ import noFullResponse, { ruleId as noFullResponseRuleId } from './agent/no-full-response';
19
18
  import noPromiseInstanceMethod, { ruleId as noPromiseInstanceMethodRuleId } from './no-promise-instance-method';
20
- import noServiceWrapper, { ruleId as noServiceWrapperRuleId } from './fixture/no-service-wrapper';
21
- import noStatusCode, { ruleId as noStatusCodeRuleId } from './fixture/no-status-code';
19
+ import noServiceWrapper, { ruleId as noServiceWrapperRuleId } from './agent/no-service-wrapper';
20
+ import noStatusCode, { ruleId as noStatusCodeRuleId } from './agent/no-status-code';
21
+ import requireResolveFullResponse, {
22
+ ruleId as requireResolveFullResponseRuleId,
23
+ } from './require-resolve-full-response';
22
24
  import filePathComment from './file-path-comment';
23
25
  import noCardNumbers from './no-card-numbers';
24
26
  import noTestImport from './no-test-import';
@@ -43,14 +45,14 @@ export default {
43
45
  [invalidJsonStringifyRuleId]: invalidJsonStringify,
44
46
  [noPromiseInstanceMethodRuleId]: noPromiseInstanceMethod,
45
47
  [noFixtureRuleId]: noFixture,
46
- [fetchHeaderGetterRuleId]: fetchHeaderGetter,
47
48
  [fetchThenRuleId]: fetchThen,
48
49
  [noServiceWrapperRuleId]: noServiceWrapper,
49
50
  [noStatusCodeRuleId]: noStatusCode,
50
51
  [fetchResponseBodyJsonRuleId]: fetchResponseBodyJson,
51
- [fetchResponseHeaderGetterTsRuleId]: fetchResponseHeaderGetterTs,
52
+ [fetchResponseHeaderGetterRuleId]: fetchResponseHeaderGetter,
52
53
  [addUrlDomainRuleId]: addUrlDomain,
53
54
  [noFullResponseRuleId]: noFullResponse,
55
+ [requireResolveFullResponseRuleId]: requireResolveFullResponse,
54
56
  },
55
57
  configs: {
56
58
  all: {
@@ -66,6 +68,8 @@ export default {
66
68
  '@checkdigit/no-test-import': 'error',
67
69
  [`@checkdigit/${invalidJsonStringifyRuleId}`]: 'error',
68
70
  [`@checkdigit/${noPromiseInstanceMethodRuleId}`]: 'error',
71
+ [`@checkdigit/${noFullResponseRuleId}`]: 'error',
72
+ [`@checkdigit/${requireResolveFullResponseRuleId}`]: 'error',
69
73
  },
70
74
  },
71
75
  recommended: {
@@ -86,12 +90,11 @@ export default {
86
90
  agent: {
87
91
  rules: {
88
92
  [`@checkdigit/${noFixtureRuleId}`]: 'error',
89
- [`@checkdigit/${fetchHeaderGetterRuleId}`]: 'error',
90
93
  [`@checkdigit/${fetchThenRuleId}`]: 'error',
91
94
  [`@checkdigit/${noServiceWrapperRuleId}`]: 'error',
92
95
  [`@checkdigit/${noStatusCodeRuleId}`]: 'error',
93
96
  [`@checkdigit/${fetchResponseBodyJsonRuleId}`]: 'error',
94
- [`@checkdigit/${fetchResponseHeaderGetterTsRuleId}`]: 'error',
97
+ [`@checkdigit/${fetchResponseHeaderGetterRuleId}`]: 'error',
95
98
  [`@checkdigit/${addUrlDomainRuleId}`]: 'error',
96
99
  [`@checkdigit/${noFullResponseRuleId}`]: 'error',
97
100
  },
@@ -88,3 +88,14 @@ export function getEnclosingFunction(node: TSESTree.Node) {
88
88
  }
89
89
  return getEnclosingFunction(parent);
90
90
  }
91
+
92
+ export function getTypeParentNode(
93
+ node: TSESTree.Node | undefined,
94
+ ): TSESTree.TSTypeAnnotation | TSESTree.TSAsExpression | undefined {
95
+ if (!node) {
96
+ return undefined;
97
+ }
98
+ return node.type === AST_NODE_TYPES.TSTypeAnnotation || node.type === AST_NODE_TYPES.TSAsExpression
99
+ ? node
100
+ : getTypeParentNode(node.parent);
101
+ }