UNPKG

eslint-plugin-sonarjs

Version:
188 lines (187 loc) • 9.49 kB
import type { Rule } from 'eslint'; import type estree from 'estree'; import type { ParserServicesWithTypeInformation } from '@typescript-eslint/utils'; import ts from 'typescript'; import * as Angular from './angular.js'; import * as AwsCdk from './assertions-aws-cdk.js'; import * as Chai from './chai.js'; import { isGlobalTSExpectAssertion, isNodeAssertAssertion, isNodeAssertTSAssertion } from './assertion-detection.js'; import * as NodeTest from './node-test.js'; import * as Sinon from './sinon.js'; import * as Supertest from './supertest.js'; import * as Uvu from './uvu.js'; import * as Vitest from './vitest.js'; /** * Every assertion framework the analyzer can recognise, keyed by a stable profile name. * * This is the single catalog: a framework is added once here — its module specifiers and its AST * and type-aware detectors — and rules opt in by listing its key in a profile. Nothing here decides * what a match *means*; that is the rule's job, expressed as the profile's extension value (see * {@link AssertionExecution} for S8784's script-capable / runner-bound split). A rule that never * lists a key is blind to that framework, both for `imports`/`dependencies` gating and detection. * * `imports` are module specifiers whose presence in the linted file proves the framework is in * play; `dependencies` are packages that expose their assertion API as globals, so a manifest * dependency is enough and no import appears in the file. */ declare const assertionFrameworks: { angular: { imports: string[]; dependencies: never[]; isAssertion: () => false; isTSAssertion: typeof Angular.isTSAssertion; }; chai: { imports: string[]; dependencies: never[]; isAssertion: typeof Chai.isAssertion; isTSAssertion: (services: ParserServicesWithTypeInformation, node: ts.Node) => boolean; }; sinon: { imports: string[]; dependencies: never[]; isAssertion: typeof Sinon.isAssertion; isTSAssertion: typeof Sinon.isTSAssertion; }; supertest: { imports: string[]; dependencies: never[]; isAssertion: typeof Supertest.isAssertion; isTSAssertion: typeof Supertest.isTSAssertion; }; nodeAssert: { imports: string[]; dependencies: never[]; isAssertion: typeof isNodeAssertAssertion; isTSAssertion: typeof isNodeAssertTSAssertion; }; nodeTest: { imports: never[]; dependencies: never[]; isAssertion: typeof NodeTest.isAssertion; isTSAssertion: typeof NodeTest.isTSAssertion; }; uvu: { imports: string[]; dependencies: never[]; isAssertion: typeof Uvu.isAssertion; isTSAssertion: typeof Uvu.isTSAssertion; }; vitest: { imports: string[]; dependencies: never[]; isAssertion: typeof Vitest.isAssertion; isTSAssertion: typeof Vitest.isTSAssertion; }; cypress: { imports: never[]; dependencies: string[]; isAssertion: (_context: Rule.RuleContext, node: estree.Node) => boolean; isTSAssertion: (_services: ParserServicesWithTypeInformation, node: ts.Node) => boolean; }; globalExpect: { imports: string[]; dependencies: string[]; isAssertion: (context: Rule.RuleContext, node: estree.Node) => boolean; isTSAssertion: typeof isGlobalTSExpectAssertion; }; awsCdk: { imports: string[]; dependencies: never[]; isAssertion: typeof AwsCdk.isAssertion; isTSAssertion: typeof AwsCdk.isTSAssertion; }; }; type AssertionFrameworkKey = keyof typeof assertionFrameworks; /** * A rule's selection of frameworks from {@link assertionFrameworks}, each mapped to whatever that * rule needs to know about it (`T`). Rules declare one as a module constant, so the set of * frameworks a rule reacts to is readable in one place instead of being implied by which helper it * happens to call. * * Rules use the two concrete aliases below, never this generic directly: `T` is what makes an * evidence profile and an execution profile different types, and picking `T` per call site is how * they would drift back into being interchangeable. */ type AssertionFrameworkProfile<T> = Partial<Record<AssertionFrameworkKey, T>>; /** * A profile covering every framework in {@link assertionFrameworks}, for rules that react to all of * them. Derived from the catalog rather than written out at the call site, so a framework added to * the catalog reaches those rules instead of being silently invisible to them — an opt-in catalog * makes an omission a false negative with nothing to fail. * * `overrides` carries the extension for the frameworks that need one; every other key gets `{}`. * Call it once per rule, at module scope: {@link getFrameworkEntries} caches on profile identity, * so a fresh object per `create()` would rebuild the entries for every linted file. */ export declare function allAssertionFrameworks(overrides?: AssertionEvidenceProfile): AssertionEvidenceProfile; /** * Extra per-framework detection a rule can bolt onto the catalog's own detectors. * * `isTSAssertionFallback` runs when the catalog's type-aware detector misses. It is * library-agnostic by design: the type-aware resolver only follows declaration initializers, so * any framework whose assertion object is *assigned* in test setup rather than declared with an * initializer needs it. AWS CDK is simply the only one that does today. */ type AssertionEvidenceExtension = { isTSAssertionFallback?: (context: Rule.RuleContext, services: ParserServicesWithTypeInformation, node: ts.Node) => boolean; }; /** * Whether a framework's assertions can run without a test runner. Script-capable ones are ordinary * libraries usable in a plain `node file.js`; runner-bound ones only exist because a runner * executes the file. Used as a profile extension by rules that treat the two differently. */ export type AssertionExecution = 'script-capable' | 'runner-bound'; /** * Profile for rules that only ask whether an assertion is present. `{}` selects a framework with no * extra behaviour; see {@link AssertionEvidenceExtension} for what an entry may carry. */ export type AssertionEvidenceProfile = AssertionFrameworkProfile<AssertionEvidenceExtension>; /** * Profile for rules that additionally reason about {@link AssertionExecution}. * * Deliberately not interchangeable with {@link AssertionEvidenceProfile}: an execution profile * carries strings, which do not satisfy the evidence extension's object shape, so passing one to an * evidence entry point is a compile error rather than a silently ignored extension. * * Like evidence profiles, this is opt-in: a rule only classifies frameworks whose assertion * detectors it can use. */ export type AssertionExecutionProfile = AssertionFrameworkProfile<AssertionExecution>; /** * Whether the linted file imports, or the project depends on, any framework in `profile`. Rules * gate on this to stay silent in files that are not tests, before doing any per-node work. */ export declare function hasAssertionEvidenceSource(context: Rule.RuleContext, profile: AssertionEvidenceProfile): boolean; /** * Whether `node` is an assertion call from any framework in `profile`. Pure-AST: needs no type * information. Frameworks outside the profile are invisible, so this answers "did the rule see an * assertion it cares about?", not "is this an assertion?". */ export declare function isAssertionEvidence(context: Rule.RuleContext, node: estree.Node, profile: AssertionEvidenceProfile): boolean; /** * Type-checker-aware counterpart of {@link isAssertionEvidence}, operating on TypeScript AST nodes. * Used when parser services are available to follow resolved types across declarations, and falls * back to each profile entry's {@link AssertionEvidenceExtension.isTSAssertionFallback} when the * catalog's own type-aware detector misses. */ export declare function isTypeScriptAssertionEvidence(context: Rule.RuleContext, services: ParserServicesWithTypeInformation, node: ts.Node, profile: AssertionEvidenceProfile): boolean; /** * {@link hasAssertionEvidenceSource} for profiles carrying an {@link AssertionExecution}. Same * check — the separate name keeps a rule's gating call and its per-node call reading against the * same vocabulary. */ export declare function hasAssertionExecutionSource(context: Rule.RuleContext, profile: AssertionExecutionProfile): boolean; /** * The `extension` of the first profile framework that claims `node`, or `undefined` if none does. * * Classification is per *node*, not per statement, and a single assertion statement often has * several matching nodes that do not agree. A chai `expect(x).to.equal(y)` matches the Chai * detector on the inner `chai.expect(...)` call and the name-based global-`expect` detector on * the outer `.to.equal(...)` call, so with an {@link AssertionExecution} profile the same * statement yields both `script-capable` and `runner-bound`. Callers that reason about the * classification must combine the nodes of a statement themselves — see how S8784 ORs * `script-capable` across a statement rather than trusting any single node. */ export declare function getAssertionExecution(context: Rule.RuleContext, node: estree.Node, profile: AssertionExecutionProfile): AssertionExecution | undefined; export {};