@composurecdk/eslint-plugin 0.10.3 → 0.11.1

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 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"]): Identifier | undefined;
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 module specifier `name` was imported from, or `undefined` when it
16
- * resolves to anything else — a global, a local declaration, a parameter.
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;AA4B3D,gFAAgF;AAChF,wBAAgB,cAAc,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,CAM5C;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,gBAAgB,CAAC,QAAQ,CAAC,GAAG,UAAU,GAAG,SAAS,CAMlF;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAmBnF;AAED,sDAAsD;AACtD,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEnD"}
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
- current = unwrapWrappers(current.object);
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 module specifier `name` was imported from, or `undefined` when it
44
- * resolves to anything else — a global, a local declaration, a parameter.
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 importSourceOf(scope, name) {
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":";;AA8BA,wCAMC;AAUD,8BAMC;AASD,wCAmBC;AAGD,kCAEC;AArED;;;;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;;;;;;;GAOG;AACH,SAAgB,SAAS,CAAC,IAAgC;IACxD,IAAI,OAAO,GAAG,cAAc,CAAC,IAAI,CAAwB,CAAC;IAC1D,OAAO,OAAO,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;QAC3C,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,MAAM,CAAa,CAAC;IACvD,CAAC;IACD,OAAO,OAAO,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC,CAAE,OAAiC,CAAC,CAAC,CAAC,SAAS,CAAC;AACxF,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,cAAc,CAAC,KAAkB,EAAE,IAAY;IAC7D,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,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;IACzD,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,sDAAsD;AACtD,SAAgB,WAAW,CAAC,MAAc;IACxC,OAAO,MAAM,KAAK,aAAa,IAAI,MAAM,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;AACvE,CAAC"}
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,EAAE,MAAM,QAAQ,CAAC;AAqDnC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,UAiCvB,CAAC"}
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. Add an entry whenever an `aws-cdk-lib` API we reach for turns
13
- * out to postdate the supported floor — this list is expected to grow as the
14
- * floor is pinned and lowered ahead of 1.0.0.
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
- * Seeded with the per-resource `isCfn<Resource>` static type guards, which
17
- * aws-cdk-lib first shipped on every generated L1 in 2.231.0 (verified by
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 FORBIDDEN = [
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}} were added in aws-cdk-lib {{since}}, above the supported floor — they throw on " +
53
- "older versions in the peer range. Use {{use}}.",
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 root = (0, imports_js_1.chainRoot)(node.object);
63
- if (root === undefined)
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 source = (0, imports_js_1.importSourceOf)(ctx.sourceCode.getScope(node), root.name);
66
- if (source === undefined || !(0, imports_js_1.isCdkSource)(source))
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
- const forbidden = FORBIDDEN.find((entry) => entry.matches(property.name));
69
- if (forbidden === undefined)
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
- ctx.report({
72
- node: property,
73
- messageId: "aboveFloor",
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,iDAA0E;AAqB1E;;;;GAIG;AACH,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC,CAAC,eAAe,EAAE,cAAc,CAAC,CAAC,CAAC;AAEtE;;;;;;;;;;GAUG;AACH,MAAM,SAAS,GAAsB;IACnC;QACE,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC;QAC5E,KAAK,EAAE,wEAAwE;QAC/E,KAAK,EAAE,SAAS;QAChB,GAAG,EACD,+FAA+F;YAC/F,qFAAqF;YACrF,iDAAiD;KACpD;CACF,CAAC;AAEF;;;;;;;;;;;GAWG;AACU,QAAA,IAAI,GAAoB;IACnC,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACJ,WAAW,EAAE,qEAAqE;SACnF;QACD,MAAM,EAAE,EAAE;QACV,QAAQ,EAAE;YACR,UAAU,EACR,2FAA2F;gBAC3F,gDAAgD;SACnD;KACF;IACD,MAAM,CAAC,GAAG;QACR,OAAO;YACL,gBAAgB,CAAC,IAAsB;gBACrC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,KAAK,YAAY;oBAAE,OAAO;gBAChD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;gBAC/B,MAAM,IAAI,GAAG,IAAA,sBAAS,EAAC,IAAI,CAAC,MAAM,CAAC,CAAC;gBACpC,IAAI,IAAI,KAAK,SAAS;oBAAE,OAAO;gBAC/B,MAAM,MAAM,GAAG,IAAA,2BAAc,EAAC,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;gBACxE,IAAI,MAAM,KAAK,SAAS,IAAI,CAAC,IAAA,wBAAW,EAAC,MAAM,CAAC;oBAAE,OAAO;gBAEzD,MAAM,SAAS,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;gBAC1E,IAAI,SAAS,KAAK,SAAS;oBAAE,OAAO;gBACpC,GAAG,CAAC,MAAM,CAAC;oBACT,IAAI,EAAE,QAAQ;oBACd,SAAS,EAAE,YAAY;oBACvB,IAAI,EAAE,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,EAAE,GAAG,EAAE,SAAS,CAAC,GAAG,EAAE;iBAC7E,CAAC,CAAC;YACL,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC"}
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"]): Identifier | undefined;
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 module specifier `name` was imported from, or `undefined` when it
16
- * resolves to anything else — a global, a local declaration, a parameter.
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;AA4B3D,gFAAgF;AAChF,wBAAgB,cAAc,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,CAM5C;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,gBAAgB,CAAC,QAAQ,CAAC,GAAG,UAAU,GAAG,SAAS,CAMlF;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAmBnF;AAED,sDAAsD;AACtD,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEnD"}
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
- current = unwrapWrappers(current.object);
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 module specifier `name` was imported from, or `undefined` when it
38
- * resolves to anything else — a global, a local declaration, a parameter.
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 importSourceOf(scope, name) {
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":"AAgBA;;;;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;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CAAC,IAAgC;IACxD,IAAI,OAAO,GAAG,cAAc,CAAC,IAAI,CAAwB,CAAC;IAC1D,OAAO,OAAO,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;QAC3C,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,MAAM,CAAa,CAAC;IACvD,CAAC;IACD,OAAO,OAAO,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC,CAAE,OAAiC,CAAC,CAAC,CAAC,SAAS,CAAC;AACxF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,KAAkB,EAAE,IAAY;IAC7D,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,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;IACzD,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,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
+ {"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,EAAE,MAAM,QAAQ,CAAC;AAqDnC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,UAiCvB,CAAC"}
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. Add an entry whenever an `aws-cdk-lib` API we reach for turns
10
- * out to postdate the supported floor — this list is expected to grow as the
11
- * floor is pinned and lowered ahead of 1.0.0.
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
- * Seeded with the per-resource `isCfn<Resource>` static type guards, which
14
- * aws-cdk-lib first shipped on every generated L1 in 2.231.0 (verified by
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 FORBIDDEN = [
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}} were added in aws-cdk-lib {{since}}, above the supported floor — they throw on " +
50
- "older versions in the peer range. Use {{use}}.",
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 root = chainRoot(node.object);
60
- if (root === undefined)
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 source = importSourceOf(ctx.sourceCode.getScope(node), root.name);
63
- if (source === undefined || !isCdkSource(source))
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
- const forbidden = FORBIDDEN.find((entry) => entry.matches(property.name));
66
- if (forbidden === undefined)
163
+ }
164
+ if (member === undefined)
165
+ return;
166
+ const root = chainRoot(node.object, { throughCalls: member.onInstance });
167
+ if (root === undefined)
67
168
  return;
68
- ctx.report({
69
- node: property,
70
- messageId: "aboveFloor",
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,EAAE,SAAS,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAqB1E;;;;GAIG;AACH,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC,CAAC,eAAe,EAAE,cAAc,CAAC,CAAC,CAAC;AAEtE;;;;;;;;;;GAUG;AACH,MAAM,SAAS,GAAsB;IACnC;QACE,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC;QAC5E,KAAK,EAAE,wEAAwE;QAC/E,KAAK,EAAE,SAAS;QAChB,GAAG,EACD,+FAA+F;YAC/F,qFAAqF;YACrF,iDAAiD;KACpD;CACF,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,IAAI,GAAoB;IACnC,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACJ,WAAW,EAAE,qEAAqE;SACnF;QACD,MAAM,EAAE,EAAE;QACV,QAAQ,EAAE;YACR,UAAU,EACR,2FAA2F;gBAC3F,gDAAgD;SACnD;KACF;IACD,MAAM,CAAC,GAAG;QACR,OAAO;YACL,gBAAgB,CAAC,IAAsB;gBACrC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,KAAK,YAAY;oBAAE,OAAO;gBAChD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;gBAC/B,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;gBACpC,IAAI,IAAI,KAAK,SAAS;oBAAE,OAAO;gBAC/B,MAAM,MAAM,GAAG,cAAc,CAAC,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;gBACxE,IAAI,MAAM,KAAK,SAAS,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;oBAAE,OAAO;gBAEzD,MAAM,SAAS,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;gBAC1E,IAAI,SAAS,KAAK,SAAS;oBAAE,OAAO;gBACpC,GAAG,CAAC,MAAM,CAAC;oBACT,IAAI,EAAE,QAAQ;oBACd,SAAS,EAAE,YAAY;oBACvB,IAAI,EAAE,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,EAAE,GAAG,EAAE,SAAS,CAAC,GAAG,EAAE;iBAC7E,CAAC,CAAC;YACL,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC"}
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` exposes this as an `isCfnAlarm` helper; see [ADR-0011](https://github.com/laazyj/composureCDK/blob/main/docs/adr/0011-cross-component-relationship-guards.md) for the pattern.
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
- Currently one entry: the per-resource `Cfn<Resource>.isCfn<Resource>` L1 static type guards, which `aws-cdk-lib` first shipped on every generated L1 in **2.231.0** (verified by installing real versions — 2.230.0 lacks them). See issue #146.
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
- `isCfnResource` and `isCfnElement` are explicitly allowed: they are core, predate the floor, and `isCfnResource` is the portable replacement.
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 list is expected to grow as floors are pinned and lowered ahead of 1.0.0. Add an entry whenever an API turns out to postdate the floor.
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
- ## Known gaps
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
- Both uncommon in this ESM source:
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
- 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.
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. A call in the chain — `Stack.of(x).isCfnY()` — breaks the root link, since that reads a runtime value rather than the import.
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.10.3",
3
+ "version": "0.11.1",
4
4
  "description": "Internal ESLint plugin encoding ComposureCDK architectural invariants (tagged builders, lifecycle context, builder copy state).",
5
5
  "repository": {
6
6
  "type": "git",
@@ -42,9 +42,9 @@
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/estree": "^1.0.9",
45
- "@types/node": "^26.6.1",
45
+ "@types/node": "^26.6.4",
46
46
  "@typescript-eslint/parser": "^8.70.0",
47
- "eslint": "^10.10.0",
47
+ "eslint": "^10.12.0",
48
48
  "typescript": "^6.0.3",
49
49
  "vitest": "^5.0.0"
50
50
  },