@composurecdk/eslint-plugin 0.10.2 → 0.11.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/README.md +1 -1
- package/dist/commonjs/rules/lib/imports.d.ts +19 -5
- package/dist/commonjs/rules/lib/imports.d.ts.map +1 -1
- package/dist/commonjs/rules/lib/imports.js +20 -8
- package/dist/commonjs/rules/lib/imports.js.map +1 -1
- package/dist/commonjs/rules/no-cdk-api-above-floor.d.ts.map +1 -1
- package/dist/commonjs/rules/no-cdk-api-above-floor.js +123 -24
- package/dist/commonjs/rules/no-cdk-api-above-floor.js.map +1 -1
- package/dist/esm/rules/lib/imports.d.ts +19 -5
- package/dist/esm/rules/lib/imports.d.ts.map +1 -1
- package/dist/esm/rules/lib/imports.js +19 -8
- package/dist/esm/rules/lib/imports.js.map +1 -1
- package/dist/esm/rules/no-cdk-api-above-floor.d.ts.map +1 -1
- package/dist/esm/rules/no-cdk-api-above-floor.js +124 -25
- package/dist/esm/rules/no-cdk-api-above-floor.js.map +1 -1
- package/docs/rules/no-cdk-api-above-floor.md +32 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -84,7 +84,7 @@ Each rule has a documentation page with its rationale, examples, deliberate excl
|
|
|
84
84
|
| [`composurecdk/constraint-metadata-required`](docs/rules/constraint-metadata-required.md) | `internal` | A `stringConstraint({ … })` call with an empty `name`, `allowed` or `source`, degrading every error it produces. |
|
|
85
85
|
| [`composurecdk/lifecycle-build-context-required`](docs/rules/lifecycle-build-context-required.md) | `recommended` | `Lifecycle.build()` missing the `context` param when the class uses `Resolvable<…>`. |
|
|
86
86
|
| [`composurecdk/lifecycle-build-must-forward-context`](docs/rules/lifecycle-build-must-forward-context.md) | `libraryAuthor` | A two-argument `builder.build(scope, id)` — the sub-builder gets no context, so refs cannot resolve. |
|
|
87
|
-
| [`composurecdk/no-cdk-api-above-floor`](docs/rules/no-cdk-api-above-floor.md) | `internal` | `aws-cdk-lib` APIs newer than the supported peer floor — they throw on older versions in the range.
|
|
87
|
+
| [`composurecdk/no-cdk-api-above-floor`](docs/rules/no-cdk-api-above-floor.md) | `internal` | `aws-cdk-lib` APIs newer than the supported peer floor — they throw on older versions in the range. **Takes an option.** |
|
|
88
88
|
| [`composurecdk/no-cjs-incompatible-syntax`](docs/rules/no-cjs-incompatible-syntax.md) | `dualPublishing` | `import.meta` / top-level `await` in library `src/` — neither emits to CommonJS. |
|
|
89
89
|
| [`composurecdk/no-realm-bound-instanceof`](docs/rules/no-realm-bound-instanceof.md) | `recommended` | `instanceof` against an imported class — realm-bound, so it silently returns false across the dual-package boundary. **Takes an option.** |
|
|
90
90
|
| [`composurecdk/no-typescript-private-modifier`](docs/rules/no-typescript-private-modifier.md) | `libraryAuthor` | The TypeScript `private` modifier — it stays in `keyof T` and leaks through mapped types into the emitted `.d.ts`. |
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Scope } from "eslint";
|
|
2
|
-
import type { Identifier, MemberExpression } from "estree";
|
|
2
|
+
import type { Identifier, ImportDeclaration, MemberExpression } from "estree";
|
|
3
3
|
/** Strip any TS-only wrappers from an expression, returning the node inside. */
|
|
4
4
|
export declare function unwrapWrappers<T>(expr: T): T;
|
|
5
5
|
/**
|
|
@@ -8,16 +8,30 @@ export declare function unwrapWrappers<T>(expr: T): T;
|
|
|
8
8
|
*
|
|
9
9
|
* Returns `undefined` when a call or other expression breaks the chain:
|
|
10
10
|
* `f().CfnAlarm` reads a runtime value rather than the imported binding, so no
|
|
11
|
-
* rule that reasons about imports should fire on it.
|
|
11
|
+
* rule that reasons about imports should fire on it. `throughCalls` walks into
|
|
12
|
+
* a call's callee instead, for a rule that wants the import an instance was
|
|
13
|
+
* obtained from — `Annotations` in `Annotations.of(scope).addWarningV2`.
|
|
12
14
|
*/
|
|
13
|
-
export declare function chainRoot(expr: MemberExpression["object"]
|
|
15
|
+
export declare function chainRoot(expr: MemberExpression["object"], { throughCalls }?: {
|
|
16
|
+
throughCalls?: boolean;
|
|
17
|
+
}): Identifier | undefined;
|
|
18
|
+
/** The import a name is bound by: its module specifier and the specifier node. */
|
|
19
|
+
export interface ImportBinding {
|
|
20
|
+
source: string;
|
|
21
|
+
specifier: ImportDeclaration["specifiers"][number];
|
|
22
|
+
}
|
|
14
23
|
/**
|
|
15
|
-
* The
|
|
16
|
-
*
|
|
24
|
+
* The import `name` is bound by, or `undefined` when it resolves to anything
|
|
25
|
+
* else — a global, a local declaration, a parameter.
|
|
17
26
|
*
|
|
18
27
|
* Walks outward through the scope chain so a local that shadows an import name
|
|
19
28
|
* wins, matching what the runtime would do.
|
|
20
29
|
*/
|
|
30
|
+
export declare function importBindingOf(scope: Scope.Scope, name: string): ImportBinding | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* The module specifier `name` was imported from, or `undefined` when it
|
|
33
|
+
* resolves to anything else — see {@link importBindingOf}.
|
|
34
|
+
*/
|
|
21
35
|
export declare function importSourceOf(scope: Scope.Scope, name: string): string | undefined;
|
|
22
36
|
/** True for `aws-cdk-lib` and any submodule of it. */
|
|
23
37
|
export declare function isCdkSource(source: string): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"imports.d.ts","sourceRoot":"","sources":["../../../../src/rules/lib/imports.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"imports.d.ts","sourceRoot":"","sources":["../../../../src/rules/lib/imports.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AA6B9E,gFAAgF;AAChF,wBAAgB,cAAc,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,CAM5C;AAED;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,gBAAgB,CAAC,QAAQ,CAAC,EAChC,EAAE,YAAoB,EAAE,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAO,GACxD,UAAU,GAAG,SAAS,CAUxB;AAED,kFAAkF;AAClF,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,iBAAiB,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;CACpD;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAmB3F;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEnF;AAED,sDAAsD;AACtD,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEnD"}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.unwrapWrappers = unwrapWrappers;
|
|
4
4
|
exports.chainRoot = chainRoot;
|
|
5
|
+
exports.importBindingOf = importBindingOf;
|
|
5
6
|
exports.importSourceOf = importSourceOf;
|
|
6
7
|
exports.isCdkSource = isCdkSource;
|
|
7
8
|
/**
|
|
@@ -30,23 +31,27 @@ function unwrapWrappers(expr) {
|
|
|
30
31
|
*
|
|
31
32
|
* Returns `undefined` when a call or other expression breaks the chain:
|
|
32
33
|
* `f().CfnAlarm` reads a runtime value rather than the imported binding, so no
|
|
33
|
-
* rule that reasons about imports should fire on it.
|
|
34
|
+
* rule that reasons about imports should fire on it. `throughCalls` walks into
|
|
35
|
+
* a call's callee instead, for a rule that wants the import an instance was
|
|
36
|
+
* obtained from — `Annotations` in `Annotations.of(scope).addWarningV2`.
|
|
34
37
|
*/
|
|
35
|
-
function chainRoot(expr) {
|
|
38
|
+
function chainRoot(expr, { throughCalls = false } = {}) {
|
|
36
39
|
let current = unwrapWrappers(expr);
|
|
37
|
-
while (current.type === "MemberExpression"
|
|
38
|
-
|
|
40
|
+
while (current.type === "MemberExpression" ||
|
|
41
|
+
(throughCalls && current.type === "CallExpression")) {
|
|
42
|
+
const next = current.type === "CallExpression" ? current.callee : current.object;
|
|
43
|
+
current = unwrapWrappers(next);
|
|
39
44
|
}
|
|
40
45
|
return current.type === "Identifier" ? current : undefined;
|
|
41
46
|
}
|
|
42
47
|
/**
|
|
43
|
-
* The
|
|
44
|
-
*
|
|
48
|
+
* The import `name` is bound by, or `undefined` when it resolves to anything
|
|
49
|
+
* else — a global, a local declaration, a parameter.
|
|
45
50
|
*
|
|
46
51
|
* Walks outward through the scope chain so a local that shadows an import name
|
|
47
52
|
* wins, matching what the runtime would do.
|
|
48
53
|
*/
|
|
49
|
-
function
|
|
54
|
+
function importBindingOf(scope, name) {
|
|
50
55
|
for (let current = scope; current !== null; current = current.upper) {
|
|
51
56
|
const variable = current.set.get(name);
|
|
52
57
|
if (variable === undefined)
|
|
@@ -65,10 +70,17 @@ function importSourceOf(scope, name) {
|
|
|
65
70
|
if (parent.type !== "ImportDeclaration")
|
|
66
71
|
return undefined;
|
|
67
72
|
const source = parent.source?.value;
|
|
68
|
-
return typeof source === "string" ? source : undefined;
|
|
73
|
+
return typeof source === "string" ? { source, specifier: def.node } : undefined;
|
|
69
74
|
}
|
|
70
75
|
return undefined;
|
|
71
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* The module specifier `name` was imported from, or `undefined` when it
|
|
79
|
+
* resolves to anything else — see {@link importBindingOf}.
|
|
80
|
+
*/
|
|
81
|
+
function importSourceOf(scope, name) {
|
|
82
|
+
return importBindingOf(scope, name)?.source;
|
|
83
|
+
}
|
|
72
84
|
/** True for `aws-cdk-lib` and any submodule of it. */
|
|
73
85
|
function isCdkSource(source) {
|
|
74
86
|
return source === "aws-cdk-lib" || source.startsWith("aws-cdk-lib/");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"imports.js","sourceRoot":"","sources":["../../../../src/rules/lib/imports.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"imports.js","sourceRoot":"","sources":["../../../../src/rules/lib/imports.ts"],"names":[],"mappings":";;AA+BA,wCAMC;AAYD,8BAaC;AAeD,0CAmBC;AAMD,wCAEC;AAGD,kCAEC;AA5FD;;;;GAIG;AACH,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC;IAC1B,iBAAiB;IACjB,gBAAgB;IAChB,qBAAqB;IACrB,uBAAuB;IACvB,iBAAiB;CAClB,CAAC,CAAC;AAEH,gFAAgF;AAChF,SAAgB,cAAc,CAAI,IAAO;IACvC,IAAI,OAAO,GAAG,IAA2B,CAAC;IAC1C,OAAO,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,OAAO,GAAG,OAAO,CAAC,UAAsB,CAAC;IAC3C,CAAC;IACD,OAAO,OAAuB,CAAC;AACjC,CAAC;AAED;;;;;;;;;GASG;AACH,SAAgB,SAAS,CACvB,IAAgC,EAChC,EAAE,YAAY,GAAG,KAAK,KAAiC,EAAE;IAEzD,IAAI,OAAO,GAAG,cAAc,CAAC,IAAI,CAAwB,CAAC;IAC1D,OACE,OAAO,CAAC,IAAI,KAAK,kBAAkB;QACnC,CAAC,YAAY,IAAI,OAAO,CAAC,IAAI,KAAK,gBAAgB,CAAC,EACnD,CAAC;QACD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,KAAK,gBAAgB,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;QACjF,OAAO,GAAG,cAAc,CAAC,IAAI,CAAa,CAAC;IAC7C,CAAC;IACD,OAAO,OAAO,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC,CAAE,OAAiC,CAAC,CAAC,CAAC,SAAS,CAAC;AACxF,CAAC;AAQD;;;;;;GAMG;AACH,SAAgB,eAAe,CAAC,KAAkB,EAAE,IAAY;IAC9D,KAAK,IAAI,OAAO,GAAuB,KAAK,EAAE,OAAO,KAAK,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;QACxF,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,QAAQ,KAAK,SAAS;YAAE,SAAS;QACrC,qEAAqE;QACrE,2EAA2E;QAC3E,iEAAiE;QACjE,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAChC,IAAI,GAAG,EAAE,IAAI,KAAK,eAAe;YAAE,OAAO,SAAS,CAAC;QACpD,8DAA8D;QAC9D,iEAAiE;QACjE,uEAAuE;QACvE,wEAAwE;QACxE,MAAM,MAAM,GAAG,GAAG,CAAC,MAAwD,CAAC;QAC5E,IAAI,MAAM,CAAC,IAAI,KAAK,mBAAmB;YAAE,OAAO,SAAS,CAAC;QAC1D,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;QACpC,OAAO,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IAClF,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,SAAgB,cAAc,CAAC,KAAkB,EAAE,IAAY;IAC7D,OAAO,eAAe,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC;AAC9C,CAAC;AAED,sDAAsD;AACtD,SAAgB,WAAW,CAAC,MAAc;IACxC,OAAO,MAAM,KAAK,aAAa,IAAI,MAAM,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;AACvE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-cdk-api-above-floor.d.ts","sourceRoot":"","sources":["../../../src/rules/no-cdk-api-above-floor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,
|
|
1
|
+
{"version":3,"file":"no-cdk-api-above-floor.d.ts","sourceRoot":"","sources":["../../../src/rules/no-cdk-api-above-floor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAS,MAAM,QAAQ,CAAC;AAsI1C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,UA6EvB,CAAC"}
|
|
@@ -9,18 +9,17 @@ const imports_js_1 = require("./lib/imports.js");
|
|
|
9
9
|
*/
|
|
10
10
|
const ALLOWED_CFN_GUARDS = new Set(["isCfnResource", "isCfnElement"]);
|
|
11
11
|
/**
|
|
12
|
-
* The ban list
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* The ban list, as members and exports: facts about `aws-cdk-lib`, each true whatever the floor. The
|
|
13
|
+
* rule reports an entry only where the configured floor predates its `since`,
|
|
14
|
+
* so an entry costs nothing in a package whose floor is already above it.
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* installing real versions: 2.230.0 lacks them, 2.231.0 has them). Calling
|
|
19
|
-
* them (e.g. `CfnAlarm.isCfnAlarm(node)`) throws `TypeError` on the older
|
|
20
|
-
* versions in our `^2` peer range — see issue #146.
|
|
16
|
+
* Every `since` is the first release carrying the API, verified by installing
|
|
17
|
+
* real versions either side of it.
|
|
21
18
|
*/
|
|
22
|
-
const
|
|
19
|
+
const BANNED_MEMBERS = [
|
|
23
20
|
{
|
|
21
|
+
// First shipped on every generated L1 in 2.231.0; calling one (e.g.
|
|
22
|
+
// `CfnAlarm.isCfnAlarm(node)`) throws `TypeError` below that — issue #146.
|
|
24
23
|
matches: (name) => /^isCfn[A-Z]/.test(name) && !ALLOWED_CFN_GUARDS.has(name),
|
|
25
24
|
label: "the per-resource `Cfn<Resource>.isCfn<Resource>` L1 static type guards",
|
|
26
25
|
since: "2.231.0",
|
|
@@ -28,7 +27,64 @@ const FORBIDDEN = [
|
|
|
28
27
|
"(what the static does internally, but valid across the whole peer range), e.g. the " +
|
|
29
28
|
"`isCfnAlarm` helper in @composurecdk/cloudwatch",
|
|
30
29
|
},
|
|
30
|
+
{
|
|
31
|
+
matches: (name) => name === "stringLikeRegexp",
|
|
32
|
+
label: "`Match.stringLikeRegexp`",
|
|
33
|
+
since: "2.9.0",
|
|
34
|
+
use: "an exact value, or a plain regular expression tested against the value read from `Template.toJSON()`",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
matches: (name) => name === "addWarningV2",
|
|
38
|
+
onInstance: true,
|
|
39
|
+
label: "`Annotations.addWarningV2`",
|
|
40
|
+
since: "2.93.0",
|
|
41
|
+
use: "`addWarning`, or feature-detect `addWarningV2` and fall back to `addWarning` where it is " +
|
|
42
|
+
"missing, as @composurecdk/cloudformation's template-text policy does",
|
|
43
|
+
},
|
|
31
44
|
];
|
|
45
|
+
const BANNED_EXPORTS = [
|
|
46
|
+
{
|
|
47
|
+
module: "aws-cdk-lib/assertions",
|
|
48
|
+
name: "Annotations",
|
|
49
|
+
label: "`Annotations` from `aws-cdk-lib/assertions`",
|
|
50
|
+
since: "2.10.0",
|
|
51
|
+
use: "the construct metadata it reads — `node.metadata` entries of type `aws:cdk:warning` or " +
|
|
52
|
+
"`aws:cdk:error`",
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
/**
|
|
56
|
+
* The `aws-cdk-lib` module an expression names as a namespace, or `undefined`.
|
|
57
|
+
*
|
|
58
|
+
* `cw` in `import * as cw from "aws-cdk-lib/aws-cloudwatch"` names that
|
|
59
|
+
* module; so do the submodule re-exports off the root —
|
|
60
|
+
* `import { assertions } from "aws-cdk-lib"` and `cdk.aws_cloudwatch` — whose
|
|
61
|
+
* underscores are the specifier's hyphens.
|
|
62
|
+
*/
|
|
63
|
+
function namespaceModule(scope, expr) {
|
|
64
|
+
const node = (0, imports_js_1.unwrapWrappers)(expr);
|
|
65
|
+
if (node.type === "Identifier") {
|
|
66
|
+
const binding = (0, imports_js_1.importBindingOf)(scope, node.name);
|
|
67
|
+
if (binding === undefined || !(0, imports_js_1.isCdkSource)(binding.source))
|
|
68
|
+
return undefined;
|
|
69
|
+
const { specifier } = binding;
|
|
70
|
+
if (specifier.type !== "ImportSpecifier")
|
|
71
|
+
return binding.source;
|
|
72
|
+
return binding.source === "aws-cdk-lib" ? submodule(exportedName(specifier)) : undefined;
|
|
73
|
+
}
|
|
74
|
+
if (node.type === "MemberExpression" && !node.computed && node.property.type === "Identifier") {
|
|
75
|
+
return namespaceModule(scope, node.object) === "aws-cdk-lib"
|
|
76
|
+
? submodule(node.property.name)
|
|
77
|
+
: undefined;
|
|
78
|
+
}
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
/** `aws-cdk-lib/aws-cloudwatch` for the root's `aws_cloudwatch` re-export. */
|
|
82
|
+
function submodule(name) {
|
|
83
|
+
return `aws-cdk-lib/${name.replaceAll("_", "-")}`;
|
|
84
|
+
}
|
|
85
|
+
function exportedName({ imported }) {
|
|
86
|
+
return imported.type === "Identifier" ? imported.name : String(imported.value);
|
|
87
|
+
}
|
|
32
88
|
/**
|
|
33
89
|
* Flags an `aws-cdk-lib` API newer than the oldest CDK version this package
|
|
34
90
|
* supports.
|
|
@@ -47,32 +103,75 @@ exports.rule = {
|
|
|
47
103
|
docs: {
|
|
48
104
|
description: "Ban aws-cdk-lib APIs newer than the supported peer-dependency floor",
|
|
49
105
|
},
|
|
50
|
-
schema: [
|
|
106
|
+
schema: [
|
|
107
|
+
{
|
|
108
|
+
type: "object",
|
|
109
|
+
properties: {
|
|
110
|
+
floor: {
|
|
111
|
+
type: "string",
|
|
112
|
+
pattern: "^\\d+\\.\\d+\\.\\d+$",
|
|
113
|
+
description: "The lowest aws-cdk-lib version the linted code supports, as major.minor.patch. " +
|
|
114
|
+
"Only APIs added after it are reported. Unset, every banned API is.",
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
additionalProperties: false,
|
|
118
|
+
},
|
|
119
|
+
],
|
|
51
120
|
messages: {
|
|
52
|
-
aboveFloor: "{{label}}
|
|
53
|
-
"
|
|
121
|
+
aboveFloor: "{{label}} arrived in aws-cdk-lib {{since}}, above {{floor}} — it is missing from older " +
|
|
122
|
+
"versions in the peer range, where it throws or fails to compile. Use {{use}}.",
|
|
54
123
|
},
|
|
55
124
|
},
|
|
56
125
|
create(ctx) {
|
|
126
|
+
const { floor } = (ctx.options[0] ?? {});
|
|
127
|
+
const floorText = floor === undefined ? "the supported floor" : `the floor of ${floor}`;
|
|
128
|
+
// `floor` is schema-validated `major.minor.patch`, so numeric collation
|
|
129
|
+
// orders it as a version.
|
|
130
|
+
const applies = (entry) => floor === undefined || floor.localeCompare(entry.since, "en", { numeric: true }) < 0;
|
|
131
|
+
const members = BANNED_MEMBERS.filter(applies);
|
|
132
|
+
const exports = BANNED_EXPORTS.filter(applies);
|
|
133
|
+
if (members.length === 0 && exports.length === 0)
|
|
134
|
+
return {};
|
|
135
|
+
const report = (node, entry) => {
|
|
136
|
+
ctx.report({
|
|
137
|
+
node,
|
|
138
|
+
messageId: "aboveFloor",
|
|
139
|
+
data: { label: entry.label, since: entry.since, floor: floorText, use: entry.use },
|
|
140
|
+
});
|
|
141
|
+
};
|
|
57
142
|
return {
|
|
143
|
+
ImportDeclaration(node) {
|
|
144
|
+
const source = node.source.value;
|
|
145
|
+
for (const specifier of node.specifiers) {
|
|
146
|
+
if (specifier.type !== "ImportSpecifier")
|
|
147
|
+
continue;
|
|
148
|
+
const name = exportedName(specifier);
|
|
149
|
+
const banned = exports.find((entry) => entry.module === source && entry.name === name);
|
|
150
|
+
if (banned !== undefined)
|
|
151
|
+
report(specifier, banned);
|
|
152
|
+
}
|
|
153
|
+
},
|
|
58
154
|
MemberExpression(node) {
|
|
59
|
-
if (node.property.type !== "Identifier")
|
|
155
|
+
if (node.computed || node.property.type !== "Identifier")
|
|
60
156
|
return;
|
|
61
157
|
const property = node.property;
|
|
62
|
-
const
|
|
63
|
-
|
|
158
|
+
const exported = exports.find((entry) => entry.name === property.name);
|
|
159
|
+
const member = members.find((entry) => entry.matches(property.name));
|
|
160
|
+
if (exported === undefined && member === undefined)
|
|
64
161
|
return;
|
|
65
|
-
const
|
|
66
|
-
if (
|
|
162
|
+
const scope = ctx.sourceCode.getScope(node);
|
|
163
|
+
if (exported !== undefined && namespaceModule(scope, node.object) === exported.module) {
|
|
164
|
+
report(property, exported);
|
|
67
165
|
return;
|
|
68
|
-
|
|
69
|
-
if (
|
|
166
|
+
}
|
|
167
|
+
if (member === undefined)
|
|
168
|
+
return;
|
|
169
|
+
const root = (0, imports_js_1.chainRoot)(node.object, { throughCalls: member.onInstance });
|
|
170
|
+
if (root === undefined)
|
|
70
171
|
return;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
data: { label: forbidden.label, since: forbidden.since, use: forbidden.use },
|
|
75
|
-
});
|
|
172
|
+
const source = (0, imports_js_1.importSourceOf)(scope, root.name);
|
|
173
|
+
if (source !== undefined && (0, imports_js_1.isCdkSource)(source))
|
|
174
|
+
report(property, member);
|
|
76
175
|
},
|
|
77
176
|
};
|
|
78
177
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-cdk-api-above-floor.js","sourceRoot":"","sources":["../../../src/rules/no-cdk-api-above-floor.ts"],"names":[],"mappings":";;;AAEA,
|
|
1
|
+
{"version":3,"file":"no-cdk-api-above-floor.js","sourceRoot":"","sources":["../../../src/rules/no-cdk-api-above-floor.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Scope } from "eslint";
|
|
2
|
-
import type { Identifier, MemberExpression } from "estree";
|
|
2
|
+
import type { Identifier, ImportDeclaration, MemberExpression } from "estree";
|
|
3
3
|
/** Strip any TS-only wrappers from an expression, returning the node inside. */
|
|
4
4
|
export declare function unwrapWrappers<T>(expr: T): T;
|
|
5
5
|
/**
|
|
@@ -8,16 +8,30 @@ export declare function unwrapWrappers<T>(expr: T): T;
|
|
|
8
8
|
*
|
|
9
9
|
* Returns `undefined` when a call or other expression breaks the chain:
|
|
10
10
|
* `f().CfnAlarm` reads a runtime value rather than the imported binding, so no
|
|
11
|
-
* rule that reasons about imports should fire on it.
|
|
11
|
+
* rule that reasons about imports should fire on it. `throughCalls` walks into
|
|
12
|
+
* a call's callee instead, for a rule that wants the import an instance was
|
|
13
|
+
* obtained from — `Annotations` in `Annotations.of(scope).addWarningV2`.
|
|
12
14
|
*/
|
|
13
|
-
export declare function chainRoot(expr: MemberExpression["object"]
|
|
15
|
+
export declare function chainRoot(expr: MemberExpression["object"], { throughCalls }?: {
|
|
16
|
+
throughCalls?: boolean;
|
|
17
|
+
}): Identifier | undefined;
|
|
18
|
+
/** The import a name is bound by: its module specifier and the specifier node. */
|
|
19
|
+
export interface ImportBinding {
|
|
20
|
+
source: string;
|
|
21
|
+
specifier: ImportDeclaration["specifiers"][number];
|
|
22
|
+
}
|
|
14
23
|
/**
|
|
15
|
-
* The
|
|
16
|
-
*
|
|
24
|
+
* The import `name` is bound by, or `undefined` when it resolves to anything
|
|
25
|
+
* else — a global, a local declaration, a parameter.
|
|
17
26
|
*
|
|
18
27
|
* Walks outward through the scope chain so a local that shadows an import name
|
|
19
28
|
* wins, matching what the runtime would do.
|
|
20
29
|
*/
|
|
30
|
+
export declare function importBindingOf(scope: Scope.Scope, name: string): ImportBinding | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* The module specifier `name` was imported from, or `undefined` when it
|
|
33
|
+
* resolves to anything else — see {@link importBindingOf}.
|
|
34
|
+
*/
|
|
21
35
|
export declare function importSourceOf(scope: Scope.Scope, name: string): string | undefined;
|
|
22
36
|
/** True for `aws-cdk-lib` and any submodule of it. */
|
|
23
37
|
export declare function isCdkSource(source: string): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"imports.d.ts","sourceRoot":"","sources":["../../../../src/rules/lib/imports.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"imports.d.ts","sourceRoot":"","sources":["../../../../src/rules/lib/imports.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AA6B9E,gFAAgF;AAChF,wBAAgB,cAAc,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,CAM5C;AAED;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,gBAAgB,CAAC,QAAQ,CAAC,EAChC,EAAE,YAAoB,EAAE,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAO,GACxD,UAAU,GAAG,SAAS,CAUxB;AAED,kFAAkF;AAClF,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,iBAAiB,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;CACpD;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAmB3F;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEnF;AAED,sDAAsD;AACtD,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEnD"}
|
|
@@ -24,23 +24,27 @@ export function unwrapWrappers(expr) {
|
|
|
24
24
|
*
|
|
25
25
|
* Returns `undefined` when a call or other expression breaks the chain:
|
|
26
26
|
* `f().CfnAlarm` reads a runtime value rather than the imported binding, so no
|
|
27
|
-
* rule that reasons about imports should fire on it.
|
|
27
|
+
* rule that reasons about imports should fire on it. `throughCalls` walks into
|
|
28
|
+
* a call's callee instead, for a rule that wants the import an instance was
|
|
29
|
+
* obtained from — `Annotations` in `Annotations.of(scope).addWarningV2`.
|
|
28
30
|
*/
|
|
29
|
-
export function chainRoot(expr) {
|
|
31
|
+
export function chainRoot(expr, { throughCalls = false } = {}) {
|
|
30
32
|
let current = unwrapWrappers(expr);
|
|
31
|
-
while (current.type === "MemberExpression"
|
|
32
|
-
|
|
33
|
+
while (current.type === "MemberExpression" ||
|
|
34
|
+
(throughCalls && current.type === "CallExpression")) {
|
|
35
|
+
const next = current.type === "CallExpression" ? current.callee : current.object;
|
|
36
|
+
current = unwrapWrappers(next);
|
|
33
37
|
}
|
|
34
38
|
return current.type === "Identifier" ? current : undefined;
|
|
35
39
|
}
|
|
36
40
|
/**
|
|
37
|
-
* The
|
|
38
|
-
*
|
|
41
|
+
* The import `name` is bound by, or `undefined` when it resolves to anything
|
|
42
|
+
* else — a global, a local declaration, a parameter.
|
|
39
43
|
*
|
|
40
44
|
* Walks outward through the scope chain so a local that shadows an import name
|
|
41
45
|
* wins, matching what the runtime would do.
|
|
42
46
|
*/
|
|
43
|
-
export function
|
|
47
|
+
export function importBindingOf(scope, name) {
|
|
44
48
|
for (let current = scope; current !== null; current = current.upper) {
|
|
45
49
|
const variable = current.set.get(name);
|
|
46
50
|
if (variable === undefined)
|
|
@@ -59,10 +63,17 @@ export function importSourceOf(scope, name) {
|
|
|
59
63
|
if (parent.type !== "ImportDeclaration")
|
|
60
64
|
return undefined;
|
|
61
65
|
const source = parent.source?.value;
|
|
62
|
-
return typeof source === "string" ? source : undefined;
|
|
66
|
+
return typeof source === "string" ? { source, specifier: def.node } : undefined;
|
|
63
67
|
}
|
|
64
68
|
return undefined;
|
|
65
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* The module specifier `name` was imported from, or `undefined` when it
|
|
72
|
+
* resolves to anything else — see {@link importBindingOf}.
|
|
73
|
+
*/
|
|
74
|
+
export function importSourceOf(scope, name) {
|
|
75
|
+
return importBindingOf(scope, name)?.source;
|
|
76
|
+
}
|
|
66
77
|
/** True for `aws-cdk-lib` and any submodule of it. */
|
|
67
78
|
export function isCdkSource(source) {
|
|
68
79
|
return source === "aws-cdk-lib" || source.startsWith("aws-cdk-lib/");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"imports.js","sourceRoot":"","sources":["../../../../src/rules/lib/imports.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"imports.js","sourceRoot":"","sources":["../../../../src/rules/lib/imports.ts"],"names":[],"mappings":"AAiBA;;;;GAIG;AACH,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC;IAC1B,iBAAiB;IACjB,gBAAgB;IAChB,qBAAqB;IACrB,uBAAuB;IACvB,iBAAiB;CAClB,CAAC,CAAC;AAEH,gFAAgF;AAChF,MAAM,UAAU,cAAc,CAAI,IAAO;IACvC,IAAI,OAAO,GAAG,IAA2B,CAAC;IAC1C,OAAO,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,OAAO,GAAG,OAAO,CAAC,UAAsB,CAAC;IAC3C,CAAC;IACD,OAAO,OAAuB,CAAC;AACjC,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,SAAS,CACvB,IAAgC,EAChC,EAAE,YAAY,GAAG,KAAK,KAAiC,EAAE;IAEzD,IAAI,OAAO,GAAG,cAAc,CAAC,IAAI,CAAwB,CAAC;IAC1D,OACE,OAAO,CAAC,IAAI,KAAK,kBAAkB;QACnC,CAAC,YAAY,IAAI,OAAO,CAAC,IAAI,KAAK,gBAAgB,CAAC,EACnD,CAAC;QACD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,KAAK,gBAAgB,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;QACjF,OAAO,GAAG,cAAc,CAAC,IAAI,CAAa,CAAC;IAC7C,CAAC;IACD,OAAO,OAAO,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC,CAAE,OAAiC,CAAC,CAAC,CAAC,SAAS,CAAC;AACxF,CAAC;AAQD;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,KAAkB,EAAE,IAAY;IAC9D,KAAK,IAAI,OAAO,GAAuB,KAAK,EAAE,OAAO,KAAK,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;QACxF,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,QAAQ,KAAK,SAAS;YAAE,SAAS;QACrC,qEAAqE;QACrE,2EAA2E;QAC3E,iEAAiE;QACjE,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAChC,IAAI,GAAG,EAAE,IAAI,KAAK,eAAe;YAAE,OAAO,SAAS,CAAC;QACpD,8DAA8D;QAC9D,iEAAiE;QACjE,uEAAuE;QACvE,wEAAwE;QACxE,MAAM,MAAM,GAAG,GAAG,CAAC,MAAwD,CAAC;QAC5E,IAAI,MAAM,CAAC,IAAI,KAAK,mBAAmB;YAAE,OAAO,SAAS,CAAC;QAC1D,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;QACpC,OAAO,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IAClF,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,KAAkB,EAAE,IAAY;IAC7D,OAAO,eAAe,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC;AAC9C,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,WAAW,CAAC,MAAc;IACxC,OAAO,MAAM,KAAK,aAAa,IAAI,MAAM,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;AACvE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-cdk-api-above-floor.d.ts","sourceRoot":"","sources":["../../../src/rules/no-cdk-api-above-floor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,
|
|
1
|
+
{"version":3,"file":"no-cdk-api-above-floor.d.ts","sourceRoot":"","sources":["../../../src/rules/no-cdk-api-above-floor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAS,MAAM,QAAQ,CAAC;AAsI1C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,UA6EvB,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { chainRoot, importSourceOf, isCdkSource } from "./lib/imports.js";
|
|
1
|
+
import { chainRoot, importBindingOf, importSourceOf, isCdkSource, unwrapWrappers, } from "./lib/imports.js";
|
|
2
2
|
/**
|
|
3
3
|
* Foundational `isCfn*` guards that predate the floor (core, since v2.0) and
|
|
4
4
|
* are therefore allowed — `isCfnResource` is in fact the portable replacement
|
|
@@ -6,18 +6,17 @@ import { chainRoot, importSourceOf, isCdkSource } from "./lib/imports.js";
|
|
|
6
6
|
*/
|
|
7
7
|
const ALLOWED_CFN_GUARDS = new Set(["isCfnResource", "isCfnElement"]);
|
|
8
8
|
/**
|
|
9
|
-
* The ban list
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* The ban list, as members and exports: facts about `aws-cdk-lib`, each true whatever the floor. The
|
|
10
|
+
* rule reports an entry only where the configured floor predates its `since`,
|
|
11
|
+
* so an entry costs nothing in a package whose floor is already above it.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* installing real versions: 2.230.0 lacks them, 2.231.0 has them). Calling
|
|
16
|
-
* them (e.g. `CfnAlarm.isCfnAlarm(node)`) throws `TypeError` on the older
|
|
17
|
-
* versions in our `^2` peer range — see issue #146.
|
|
13
|
+
* Every `since` is the first release carrying the API, verified by installing
|
|
14
|
+
* real versions either side of it.
|
|
18
15
|
*/
|
|
19
|
-
const
|
|
16
|
+
const BANNED_MEMBERS = [
|
|
20
17
|
{
|
|
18
|
+
// First shipped on every generated L1 in 2.231.0; calling one (e.g.
|
|
19
|
+
// `CfnAlarm.isCfnAlarm(node)`) throws `TypeError` below that — issue #146.
|
|
21
20
|
matches: (name) => /^isCfn[A-Z]/.test(name) && !ALLOWED_CFN_GUARDS.has(name),
|
|
22
21
|
label: "the per-resource `Cfn<Resource>.isCfn<Resource>` L1 static type guards",
|
|
23
22
|
since: "2.231.0",
|
|
@@ -25,7 +24,64 @@ const FORBIDDEN = [
|
|
|
25
24
|
"(what the static does internally, but valid across the whole peer range), e.g. the " +
|
|
26
25
|
"`isCfnAlarm` helper in @composurecdk/cloudwatch",
|
|
27
26
|
},
|
|
27
|
+
{
|
|
28
|
+
matches: (name) => name === "stringLikeRegexp",
|
|
29
|
+
label: "`Match.stringLikeRegexp`",
|
|
30
|
+
since: "2.9.0",
|
|
31
|
+
use: "an exact value, or a plain regular expression tested against the value read from `Template.toJSON()`",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
matches: (name) => name === "addWarningV2",
|
|
35
|
+
onInstance: true,
|
|
36
|
+
label: "`Annotations.addWarningV2`",
|
|
37
|
+
since: "2.93.0",
|
|
38
|
+
use: "`addWarning`, or feature-detect `addWarningV2` and fall back to `addWarning` where it is " +
|
|
39
|
+
"missing, as @composurecdk/cloudformation's template-text policy does",
|
|
40
|
+
},
|
|
28
41
|
];
|
|
42
|
+
const BANNED_EXPORTS = [
|
|
43
|
+
{
|
|
44
|
+
module: "aws-cdk-lib/assertions",
|
|
45
|
+
name: "Annotations",
|
|
46
|
+
label: "`Annotations` from `aws-cdk-lib/assertions`",
|
|
47
|
+
since: "2.10.0",
|
|
48
|
+
use: "the construct metadata it reads — `node.metadata` entries of type `aws:cdk:warning` or " +
|
|
49
|
+
"`aws:cdk:error`",
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
/**
|
|
53
|
+
* The `aws-cdk-lib` module an expression names as a namespace, or `undefined`.
|
|
54
|
+
*
|
|
55
|
+
* `cw` in `import * as cw from "aws-cdk-lib/aws-cloudwatch"` names that
|
|
56
|
+
* module; so do the submodule re-exports off the root —
|
|
57
|
+
* `import { assertions } from "aws-cdk-lib"` and `cdk.aws_cloudwatch` — whose
|
|
58
|
+
* underscores are the specifier's hyphens.
|
|
59
|
+
*/
|
|
60
|
+
function namespaceModule(scope, expr) {
|
|
61
|
+
const node = unwrapWrappers(expr);
|
|
62
|
+
if (node.type === "Identifier") {
|
|
63
|
+
const binding = importBindingOf(scope, node.name);
|
|
64
|
+
if (binding === undefined || !isCdkSource(binding.source))
|
|
65
|
+
return undefined;
|
|
66
|
+
const { specifier } = binding;
|
|
67
|
+
if (specifier.type !== "ImportSpecifier")
|
|
68
|
+
return binding.source;
|
|
69
|
+
return binding.source === "aws-cdk-lib" ? submodule(exportedName(specifier)) : undefined;
|
|
70
|
+
}
|
|
71
|
+
if (node.type === "MemberExpression" && !node.computed && node.property.type === "Identifier") {
|
|
72
|
+
return namespaceModule(scope, node.object) === "aws-cdk-lib"
|
|
73
|
+
? submodule(node.property.name)
|
|
74
|
+
: undefined;
|
|
75
|
+
}
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
/** `aws-cdk-lib/aws-cloudwatch` for the root's `aws_cloudwatch` re-export. */
|
|
79
|
+
function submodule(name) {
|
|
80
|
+
return `aws-cdk-lib/${name.replaceAll("_", "-")}`;
|
|
81
|
+
}
|
|
82
|
+
function exportedName({ imported }) {
|
|
83
|
+
return imported.type === "Identifier" ? imported.name : String(imported.value);
|
|
84
|
+
}
|
|
29
85
|
/**
|
|
30
86
|
* Flags an `aws-cdk-lib` API newer than the oldest CDK version this package
|
|
31
87
|
* supports.
|
|
@@ -44,32 +100,75 @@ export const rule = {
|
|
|
44
100
|
docs: {
|
|
45
101
|
description: "Ban aws-cdk-lib APIs newer than the supported peer-dependency floor",
|
|
46
102
|
},
|
|
47
|
-
schema: [
|
|
103
|
+
schema: [
|
|
104
|
+
{
|
|
105
|
+
type: "object",
|
|
106
|
+
properties: {
|
|
107
|
+
floor: {
|
|
108
|
+
type: "string",
|
|
109
|
+
pattern: "^\\d+\\.\\d+\\.\\d+$",
|
|
110
|
+
description: "The lowest aws-cdk-lib version the linted code supports, as major.minor.patch. " +
|
|
111
|
+
"Only APIs added after it are reported. Unset, every banned API is.",
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
additionalProperties: false,
|
|
115
|
+
},
|
|
116
|
+
],
|
|
48
117
|
messages: {
|
|
49
|
-
aboveFloor: "{{label}}
|
|
50
|
-
"
|
|
118
|
+
aboveFloor: "{{label}} arrived in aws-cdk-lib {{since}}, above {{floor}} — it is missing from older " +
|
|
119
|
+
"versions in the peer range, where it throws or fails to compile. Use {{use}}.",
|
|
51
120
|
},
|
|
52
121
|
},
|
|
53
122
|
create(ctx) {
|
|
123
|
+
const { floor } = (ctx.options[0] ?? {});
|
|
124
|
+
const floorText = floor === undefined ? "the supported floor" : `the floor of ${floor}`;
|
|
125
|
+
// `floor` is schema-validated `major.minor.patch`, so numeric collation
|
|
126
|
+
// orders it as a version.
|
|
127
|
+
const applies = (entry) => floor === undefined || floor.localeCompare(entry.since, "en", { numeric: true }) < 0;
|
|
128
|
+
const members = BANNED_MEMBERS.filter(applies);
|
|
129
|
+
const exports = BANNED_EXPORTS.filter(applies);
|
|
130
|
+
if (members.length === 0 && exports.length === 0)
|
|
131
|
+
return {};
|
|
132
|
+
const report = (node, entry) => {
|
|
133
|
+
ctx.report({
|
|
134
|
+
node,
|
|
135
|
+
messageId: "aboveFloor",
|
|
136
|
+
data: { label: entry.label, since: entry.since, floor: floorText, use: entry.use },
|
|
137
|
+
});
|
|
138
|
+
};
|
|
54
139
|
return {
|
|
140
|
+
ImportDeclaration(node) {
|
|
141
|
+
const source = node.source.value;
|
|
142
|
+
for (const specifier of node.specifiers) {
|
|
143
|
+
if (specifier.type !== "ImportSpecifier")
|
|
144
|
+
continue;
|
|
145
|
+
const name = exportedName(specifier);
|
|
146
|
+
const banned = exports.find((entry) => entry.module === source && entry.name === name);
|
|
147
|
+
if (banned !== undefined)
|
|
148
|
+
report(specifier, banned);
|
|
149
|
+
}
|
|
150
|
+
},
|
|
55
151
|
MemberExpression(node) {
|
|
56
|
-
if (node.property.type !== "Identifier")
|
|
152
|
+
if (node.computed || node.property.type !== "Identifier")
|
|
57
153
|
return;
|
|
58
154
|
const property = node.property;
|
|
59
|
-
const
|
|
60
|
-
|
|
155
|
+
const exported = exports.find((entry) => entry.name === property.name);
|
|
156
|
+
const member = members.find((entry) => entry.matches(property.name));
|
|
157
|
+
if (exported === undefined && member === undefined)
|
|
61
158
|
return;
|
|
62
|
-
const
|
|
63
|
-
if (
|
|
159
|
+
const scope = ctx.sourceCode.getScope(node);
|
|
160
|
+
if (exported !== undefined && namespaceModule(scope, node.object) === exported.module) {
|
|
161
|
+
report(property, exported);
|
|
64
162
|
return;
|
|
65
|
-
|
|
66
|
-
if (
|
|
163
|
+
}
|
|
164
|
+
if (member === undefined)
|
|
165
|
+
return;
|
|
166
|
+
const root = chainRoot(node.object, { throughCalls: member.onInstance });
|
|
167
|
+
if (root === undefined)
|
|
67
168
|
return;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
data: { label: forbidden.label, since: forbidden.since, use: forbidden.use },
|
|
72
|
-
});
|
|
169
|
+
const source = importSourceOf(scope, root.name);
|
|
170
|
+
if (source !== undefined && isCdkSource(source))
|
|
171
|
+
report(property, member);
|
|
73
172
|
},
|
|
74
173
|
};
|
|
75
174
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-cdk-api-above-floor.js","sourceRoot":"","sources":["../../../src/rules/no-cdk-api-above-floor.ts"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"no-cdk-api-above-floor.js","sourceRoot":"","sources":["../../../src/rules/no-cdk-api-above-floor.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# composurecdk/no-cdk-api-above-floor
|
|
2
2
|
|
|
3
|
-
Flags use of an `aws-cdk-lib` API newer than the package's supported peer-dependency floor.
|
|
3
|
+
Flags use of an `aws-cdk-lib` API newer than the package's supported peer-dependency floor. **Takes an option.**
|
|
4
4
|
|
|
5
5
|
- **Preset:** `internal` (`error`)
|
|
6
6
|
- **Decision:** [ADR-0008](https://github.com/laazyj/composureCDK/blob/main/docs/adr/0008-aws-cdk-lib-version-floors.md)
|
|
@@ -32,25 +32,48 @@ if (CfnResource.isCfnResource(node) && node.cfnResourceType === CfnAlarm.CFN_RES
|
|
|
32
32
|
}
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
`@composurecdk/cloudwatch`
|
|
35
|
+
`@composurecdk/cloudwatch` does this internally, in its unexported `isCfnAlarm`; see [ADR-0011](https://github.com/laazyj/composureCDK/blob/main/docs/adr/0011-cross-component-relationship-guards.md) for the pattern.
|
|
36
|
+
|
|
37
|
+
## Options
|
|
38
|
+
|
|
39
|
+
### `floor`
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
"composurecdk/no-cdk-api-above-floor": ["error", { floor: "2.93.0" }]
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The lowest `aws-cdk-lib` version the linted code supports, as `major.minor.patch` — normally the bottom of your `peerDependencies` range. An API is reported only where it arrived **after** the floor, so the same ban list is correct for every package whatever its floor, and an entry costs nothing where the floor is already above it.
|
|
46
|
+
|
|
47
|
+
**Unset, every banned API is reported**, as though the floor were the bottom of `aws-cdk-lib` v2. That is the conservative reading of a package that has not said what it supports.
|
|
48
|
+
|
|
49
|
+
Set it per package where floors differ. This repo reads each package's floor from its `cdk-floors.json` manifest and configures the rule once per package, for `test/` as well as `src/`: its floor guard runs each package's whole suite — typecheck included — against that floor, so a test is bound by it exactly as source is.
|
|
36
50
|
|
|
37
51
|
## The ban list
|
|
38
52
|
|
|
39
|
-
|
|
53
|
+
Each entry is a fact about `aws-cdk-lib`, not a policy about a package. Every version is the first release carrying the API, verified by installing the releases either side of it.
|
|
40
54
|
|
|
41
|
-
|
|
55
|
+
| API | Since | Use instead |
|
|
56
|
+
| -------------------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
57
|
+
| `Cfn<Resource>.isCfn<Resource>` static type guards | 2.231.0 | `CfnResource.isCfnResource(x) && x.cfnResourceType === Cfn<Resource>.CFN_RESOURCE_TYPE_NAME` — see below. |
|
|
58
|
+
| `Match.stringLikeRegexp` | 2.9.0 | An exact value, or a plain regular expression tested against the value read from `Template.toJSON()`. |
|
|
59
|
+
| `Annotations.addWarningV2` | 2.93.0 | `addWarning`, or feature-detect `addWarningV2` and fall back — `@composurecdk/cloudformation`'s template-text policy. |
|
|
60
|
+
| `Annotations` from `aws-cdk-lib/assertions` | 2.10.0 | The construct metadata it reads: `node.metadata` entries of type `aws:cdk:warning` or `aws:cdk:error`. |
|
|
42
61
|
|
|
43
|
-
The
|
|
62
|
+
The `isCfn*` guards shipped on every generated L1 in 2.231.0 (2.230.0 lacks them); see issue #146. `isCfnResource` and `isCfnElement` are explicitly allowed: they are core, predate every floor, and `isCfnResource` is the portable replacement.
|
|
44
63
|
|
|
45
|
-
|
|
64
|
+
Add an entry whenever an API turns out to postdate a floor. Adding one makes the rule report where it did not, which is a breaking change to the rule ([Versioning](../../README.md#versioning)); with `floor` set, it reaches only the packages whose floor is below the new entry.
|
|
46
65
|
|
|
47
|
-
|
|
66
|
+
## Known gaps
|
|
48
67
|
|
|
49
68
|
- Computed bracket access — `CfnAlarm["isCfnAlarm"](x)`.
|
|
50
69
|
- `import = require()` bindings.
|
|
70
|
+
- An instance member reached through a variable — `const a = Annotations.of(x); a.addWarningV2(…)`. The rule follows the chain through `Annotations.of(x)` back to the import, but not through an assignment. That same blind spot is what lets the feature-detecting fallback through, which is correct.
|
|
71
|
+
- A re-export — `export { Annotations } from "aws-cdk-lib/assertions"`.
|
|
51
72
|
|
|
52
73
|
## How it works
|
|
53
74
|
|
|
54
|
-
|
|
75
|
+
A banned **member** is matched by the accessed member name rather than the owning class identifier, so it holds regardless of how the class is imported or aliased — `CfnAlarm.isCfnAlarm`, `cw.CfnAlarm.isCfnAlarm`, `cdk.aws_cloudwatch.CfnAlarm.isCfnAlarm` all match. An instance member (`addWarningV2`) is traced back through the call that produced the instance, `Annotations.of(x)`.
|
|
76
|
+
|
|
77
|
+
A banned **export** is matched where it is imported by name (type-only imports included — the floor's typecheck rejects those too) and where it is read off a namespace for its module: `import * as assertions from "aws-cdk-lib/assertions"`, the root's `assertions` re-export, or `cdk.assertions`.
|
|
55
78
|
|
|
56
|
-
Resolution runs through ESLint's scope manager, so a local shadowing the import name (a parameter called `CfnAlarm`, say) is correctly treated as a non-CDK binding. TS-only wrappers (`as`, `satisfies`, `!`, angle-bracket assertion) are unwrapped so they cannot smuggle a call past the rule.
|
|
79
|
+
Resolution runs through ESLint's scope manager, so a local shadowing the import name (a parameter called `CfnAlarm`, say) is correctly treated as a non-CDK binding. TS-only wrappers (`as`, `satisfies`, `!`, angle-bracket assertion) are unwrapped so they cannot smuggle a call past the rule. For a static member, a call in the chain — `Stack.of(x).isCfnY()` — breaks the root link, since that reads a runtime value rather than the import.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@composurecdk/eslint-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Internal ESLint plugin encoding ComposureCDK architectural invariants (tagged builders, lifecycle context, builder copy state).",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|