eslint-plugin-sonarjs
Version:
188 lines (187 loc) • 9.49 kB
TypeScript
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 {};