@checkdigit/eslint-plugin 6.6.0-PR.75-a3df → 6.6.0-PR.75-1cd1

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/src/no-fixture.ts CHANGED
@@ -16,7 +16,7 @@ import type {
16
16
  SimpleCallExpression,
17
17
  VariableDeclaration,
18
18
  } from 'estree';
19
- import type { Rule, Scope, SourceCode } from 'eslint';
19
+ import { type Rule, type Scope, SourceCode } from 'eslint';
20
20
  import { getEnclosingScopeNode, getEnclosingStatement, getParent } from './ast/tree';
21
21
  import { strict as assert } from 'node:assert';
22
22
  import getDocumentationUrl from './get-documentation-url';
@@ -36,7 +36,7 @@ interface FixtureCallInformation {
36
36
  }
37
37
 
38
38
  // recursively analyze the fixture/supertest call chain to collect information of request/response
39
- function analyzeFixtureCall(call: SimpleCallExpression, results: FixtureCallInformation) {
39
+ function analyzeFixtureCall(call: SimpleCallExpression, results: FixtureCallInformation, sourceCode: SourceCode) {
40
40
  const parent = getParent(call);
41
41
  assert.ok(parent, 'parent should exist for fixture/supertest call node');
42
42
 
@@ -84,10 +84,10 @@ function analyzeFixtureCall(call: SimpleCallExpression, results: FixtureCallInfo
84
84
  nextCall = setRequestHeaderCall;
85
85
  }
86
86
  } else {
87
- throw new Error(`Unexpected expression in fixture/supertest call ${String(parent)}`);
87
+ throw new Error(`Unexpected expression in fixture/supertest call ${sourceCode.getText(parent)}`);
88
88
  }
89
89
  if (nextCall) {
90
- analyzeFixtureCall(nextCall, results);
90
+ analyzeFixtureCall(nextCall, results, sourceCode);
91
91
  }
92
92
  }
93
93
 
@@ -100,6 +100,7 @@ function analyzeResponseReferences(fixtureInformation: FixtureCallInformation, s
100
100
  statusReferences: MemberExpression[];
101
101
  destructuringBodyVariable?: Scope.Variable;
102
102
  destructuringHeadersVariable?: Scope.Variable;
103
+ destructuringHeadersReferences?: MemberExpression[] | undefined;
103
104
  } = {
104
105
  bodyReferences: [],
105
106
  headersReferences: [],
@@ -109,6 +110,7 @@ function analyzeResponseReferences(fixtureInformation: FixtureCallInformation, s
109
110
  if (fixtureInformation.variableDeclaration) {
110
111
  const responseVariables = scopeManager.getDeclaredVariables(fixtureInformation.variableDeclaration);
111
112
  for (const responseVariable of responseVariables) {
113
+ const scope = responseVariable.scope;
112
114
  const identifier = responseVariable.identifiers[0];
113
115
  assert.ok(identifier);
114
116
  const identifierParent = getParent(identifier);
@@ -116,39 +118,36 @@ function analyzeResponseReferences(fixtureInformation: FixtureCallInformation, s
116
118
  if (identifierParent.type === 'VariableDeclarator') {
117
119
  // e.g. const response = ...
118
120
  results.variable = responseVariable;
121
+ const responseReferences = responseVariable.references.map((responseReference) =>
122
+ getParent(responseReference.identifier),
123
+ );
119
124
  // e.g. response.body
120
- results.bodyReferences = responseVariable.references
121
- .map((responseBodyReference) => getParent(responseBodyReference.identifier))
122
- .filter(
123
- (node): node is MemberExpression =>
124
- node !== null &&
125
- node !== undefined &&
126
- node.type === 'MemberExpression' &&
127
- node.property.type === 'Identifier' &&
128
- node.property.name === 'body',
129
- );
125
+ results.bodyReferences = responseReferences.filter(
126
+ (node): node is MemberExpression =>
127
+ node !== null &&
128
+ node !== undefined &&
129
+ node.type === 'MemberExpression' &&
130
+ node.property.type === 'Identifier' &&
131
+ node.property.name === 'body',
132
+ );
130
133
  // e.g. response.headers / response.header / response.get()
131
- results.headersReferences = responseVariable.references
132
- .map((responseHeadersReference) => getParent(responseHeadersReference.identifier))
133
- .filter(
134
- (node): node is MemberExpression =>
135
- node !== null &&
136
- node !== undefined &&
137
- node.type === 'MemberExpression' &&
138
- node.property.type === 'Identifier' &&
139
- (node.property.name === 'header' || node.property.name === 'headers' || node.property.name === 'get'),
140
- );
134
+ results.headersReferences = responseReferences.filter(
135
+ (node): node is MemberExpression =>
136
+ node !== null &&
137
+ node !== undefined &&
138
+ node.type === 'MemberExpression' &&
139
+ node.property.type === 'Identifier' &&
140
+ (node.property.name === 'header' || node.property.name === 'headers' || node.property.name === 'get'),
141
+ );
141
142
  // e.g. response.status / response.statusCode
142
- results.statusReferences = responseVariable.references
143
- .map((responseHeadersReference) => getParent(responseHeadersReference.identifier))
144
- .filter(
145
- (node): node is MemberExpression =>
146
- node !== null &&
147
- node !== undefined &&
148
- node.type === 'MemberExpression' &&
149
- node.property.type === 'Identifier' &&
150
- (node.property.name === 'status' || node.property.name === 'statusCode'),
151
- );
143
+ results.statusReferences = responseReferences.filter(
144
+ (node): node is MemberExpression =>
145
+ node !== null &&
146
+ node !== undefined &&
147
+ node.type === 'MemberExpression' &&
148
+ node.property.type === 'Identifier' &&
149
+ (node.property.name === 'status' || node.property.name === 'statusCode'),
150
+ );
152
151
  } else if (
153
152
  // body reference through destruction/renaming, e.g. "const { body } = ..."
154
153
  identifierParent.type === 'Property' &&
@@ -163,6 +162,17 @@ function analyzeResponseReferences(fixtureInformation: FixtureCallInformation, s
163
162
  identifierParent.key.name === 'headers'
164
163
  ) {
165
164
  results.destructuringHeadersVariable = responseVariable;
165
+ results.destructuringHeadersReferences = scope.set
166
+ .get(responseVariable.name)
167
+ ?.references.map((reference) => reference.identifier)
168
+ .map(getParent)
169
+ .filter(
170
+ (parent): parent is MemberExpression =>
171
+ parent?.type === 'MemberExpression' &&
172
+ parent.property.type === 'Identifier' &&
173
+ parent.property.name !== 'get' &&
174
+ getParent(parent)?.type !== 'CallExpression',
175
+ );
166
176
  } else {
167
177
  throw new Error(`Unknown response variable reference: ${responseVariable.name}`);
168
178
  }
@@ -186,6 +196,7 @@ function createResponseAssertions(
186
196
  fixtureCallInformation: FixtureCallInformation,
187
197
  sourceCode: SourceCode,
188
198
  responseVariableName: string,
199
+ destructuringResponseHeadersVariable: Scope.Variable | undefined,
189
200
  ) {
190
201
  let statusAssertion: string | undefined;
191
202
  const nonStatusAssertions: string[] = [];
@@ -231,13 +242,17 @@ function createResponseAssertions(
231
242
  // header assertion
232
243
  const [headerName, headerValue] = expectArguments;
233
244
  assert.ok(headerName && headerValue);
245
+ const headersReference =
246
+ destructuringResponseHeadersVariable !== undefined
247
+ ? destructuringResponseHeadersVariable.name
248
+ : `${responseVariableName}.headers`;
234
249
  if (headerValue.type === 'Literal' && headerValue.value instanceof RegExp) {
235
250
  nonStatusAssertions.push(
236
- `assert.ok(${responseVariableName}.headers.get(${sourceCode.getText(headerName)}).match(${sourceCode.getText(headerValue)}))`,
251
+ `assert.ok(${headersReference}.get(${sourceCode.getText(headerName)}).match(${sourceCode.getText(headerValue)}))`,
237
252
  );
238
253
  } else {
239
254
  nonStatusAssertions.push(
240
- `assert.equal(${responseVariableName}.headers.get(${sourceCode.getText(headerName)}), ${sourceCode.getText(headerValue)})`,
255
+ `assert.equal(${headersReference}.get(${sourceCode.getText(headerName)}), ${sourceCode.getText(headerValue)})`,
241
256
  );
242
257
  }
243
258
  }
@@ -255,7 +270,6 @@ function getResponseVariableNameToUse(
255
270
  ) {
256
271
  if (fixtureCallInformation.variableDeclaration) {
257
272
  const firstDeclaration = fixtureCallInformation.variableDeclaration.declarations[0];
258
- // [TODO:] double check if it works for destruction/rename declaration
259
273
  if (firstDeclaration && firstDeclaration.id.type === 'Identifier') {
260
274
  return firstDeclaration.id.name;
261
275
  }
@@ -316,6 +330,7 @@ const rule: Rule.RuleModule = {
316
330
  const scopeVariablesMap = new Map<Scope.Scope, string[]>();
317
331
 
318
332
  return {
333
+ // eslint-disable-next-line max-lines-per-function
319
334
  'CallExpression[callee.object.object.name="fixture"][callee.object.property.name="api"]': (
320
335
  fixtureCall: CallExpression,
321
336
  ) => {
@@ -329,7 +344,7 @@ const rule: Rule.RuleModule = {
329
344
  assert.ok(urlArgumentNode !== undefined);
330
345
 
331
346
  const fixtureCallInformation = {} as FixtureCallInformation;
332
- analyzeFixtureCall(fixtureCall, fixtureCallInformation);
347
+ analyzeFixtureCall(fixtureCall, fixtureCallInformation, sourceCode);
333
348
 
334
349
  const {
335
350
  variable: responseVariable,
@@ -338,6 +353,7 @@ const rule: Rule.RuleModule = {
338
353
  statusReferences: responseStatusReferences,
339
354
  destructuringBodyVariable: destructuringResponseBodyVariable,
340
355
  destructuringHeadersVariable: destructuringResponseHeadersVariable,
356
+ // destructuringHeadersReferences: destructuringResponseHeadersReferences,
341
357
  } = analyzeResponseReferences(fixtureCallInformation, scopeManager);
342
358
 
343
359
  // convert url from `/sample-service/v1/ping` to `${BASE_PATH}/ping`
@@ -405,6 +421,7 @@ const rule: Rule.RuleModule = {
405
421
  fixtureCallInformation,
406
422
  sourceCode,
407
423
  responseVariableNameToUse,
424
+ destructuringResponseHeadersVariable,
408
425
  );
409
426
 
410
427
  // add variable declaration if needed
@@ -426,6 +443,7 @@ const rule: Rule.RuleModule = {
426
443
  context.report({
427
444
  node: fixtureCall,
428
445
  messageId: 'preferNativeFetch',
446
+ // eslint-disable-next-line sonarjs/cognitive-complexity
429
447
  *fix(fixer) {
430
448
  if (fixtureCallInformation.inlineStatementNode) {
431
449
  const preInlineDeclaration = [
@@ -470,9 +488,22 @@ const rule: Rule.RuleModule = {
470
488
  const headerNameNode = parent.arguments[0];
471
489
  headerName = sourceCode.getText(headerNameNode);
472
490
  }
473
- assert.ok(headerName);
491
+ assert.ok(headerName !== undefined);
474
492
  yield fixer.replaceText(parent, `${responseVariableNameToUse}.headers.get(${headerName})`);
475
493
  }
494
+ // if (destructuringResponseHeadersVariable !== undefined) {
495
+ // for (const destructuringResponseHeadersReference of destructuringResponseHeadersReferences ?? []) {
496
+ // const headerNameNode = destructuringResponseHeadersReference.property;
497
+ // const headerName = destructuringResponseHeadersReference.computed
498
+ // ? sourceCode.getText(headerNameNode)
499
+ // : `'${sourceCode.getText(headerNameNode)}'`;
500
+
501
+ // yield fixer.replaceText(
502
+ // destructuringResponseHeadersReference,
503
+ // `${destructuringResponseHeadersVariable.name}.get(${headerName})`,
504
+ // );
505
+ // }
506
+ // }
476
507
 
477
508
  // convert response.statusCode to response.status
478
509
  for (const responseStatusReference of responseStatusReferences) {