Problem / use case
composurecdk/no-realm-bound-instanceof flags instanceof against any imported class. But the two kinds of import it flags have different hazard conditions, and the rule has one on/off switch for both.
Bare specifier — x instanceof Bucket from aws-cdk-lib, or StatementBuilder from @composurecdk/iam. Whether that class object exists twice in the process is a property of the dependency and the install: two versions npm could not dedup, a bundler that duplicated it, or a dual-published package where one part of the graph reached the CommonJS half and another the ESM half. None of that is visible from the linted source, so the flag is right in any project.
Relative import — x instanceof StatementBuilder from ./statement-builder.js. That module loads once per copy of the containing package, so the check only breaks if the package itself loads twice — which needs it to be dual-published (#385, where the ESM and CommonJS halves each evaluated their own ./statement-builder.js) or shipped as two installed versions. A project that builds a single format and one version cannot hit it, and the report is a false positive.
So the rule's premise is per-import-source while the preset boundary is per-project. That mismatch surfaced when the presets were split in #409: no-realm-bound-instanceof sits in recommended, which over-reports relative imports for a single-format consumer. Putting it in dualPublishing instead was worse — it would go silent on the bare-specifier case too, which is the one that shipped #384 and #385. Neither preset is correct for an application author.
The current workaround is the blunt one, and the README says so: turn the rule off for the affected files. That also switches off the bare-specifier protection there, which was the half that applied.
Proposed solution
Make the distinction an option rather than a preset choice. The rule already computes what it needs — importSourceOf yields the specifier and isCdkSource already branches on one class of source:
// A single-format app: still catch duplicable dependencies, stop flagging its own relative imports.
"composurecdk/no-realm-bound-instanceof": ["error", { duplicableSources: "dependencies" }]
"all" (default) — today's behaviour, correct for a dual-published package.
"dependencies" — bare specifiers only; relative imports are treated as same-realm.
recommended then holds the rule for everyone and nobody disables it to silence half of it. Worth deciding as part of this whether the option is better expressed as the positive assertion it really is — "this package ships a single format and one version" — since that is the fact the consumer actually knows.
Also worth folding in: the README's "turning the rule off for those files is reasonable" note and the corresponding docblock caveat both go away once the option exists.
Alternatives considered
- Do nothing. The status quo errs toward a false positive, which is the safer error — visible, and dismissable per line — where the
dualPublishing placement would have erred toward a false negative that ships the bug. Tolerable, but it still trains a consumer to disable a rule that is half right for them.
- Split the rule in two (
no-realm-bound-instanceof-dependency / -relative), one per preset. Puts the axis in the rule names rather than an option, at the cost of two rules to document, two messages to keep in step, and a preset boundary that still cannot express "single-format package".
- Infer it from the linted package's own
package.json (exports conditions, or the absence of a CommonJS dialect). Removes the configuration entirely, but makes a syntactic rule read the filesystem and guess, and it is wrong for a package mid-migration. Every other rule in this plugin is deliberately syntactic.
Follow-up to #409, and additive — a new option is a minor, so this is not blocked on the first publish.
Problem / use case
composurecdk/no-realm-bound-instanceofflagsinstanceofagainst any imported class. But the two kinds of import it flags have different hazard conditions, and the rule has one on/off switch for both.Bare specifier —
x instanceof Bucketfromaws-cdk-lib, orStatementBuilderfrom@composurecdk/iam. Whether that class object exists twice in the process is a property of the dependency and the install: two versions npm could not dedup, a bundler that duplicated it, or a dual-published package where one part of the graph reached the CommonJS half and another the ESM half. None of that is visible from the linted source, so the flag is right in any project.Relative import —
x instanceof StatementBuilderfrom./statement-builder.js. That module loads once per copy of the containing package, so the check only breaks if the package itself loads twice — which needs it to be dual-published (#385, where the ESM and CommonJS halves each evaluated their own./statement-builder.js) or shipped as two installed versions. A project that builds a single format and one version cannot hit it, and the report is a false positive.So the rule's premise is per-import-source while the preset boundary is per-project. That mismatch surfaced when the presets were split in #409:
no-realm-bound-instanceofsits inrecommended, which over-reports relative imports for a single-format consumer. Putting it indualPublishinginstead was worse — it would go silent on the bare-specifier case too, which is the one that shipped #384 and #385. Neither preset is correct for an application author.The current workaround is the blunt one, and the README says so: turn the rule off for the affected files. That also switches off the bare-specifier protection there, which was the half that applied.
Proposed solution
Make the distinction an option rather than a preset choice. The rule already computes what it needs —
importSourceOfyields the specifier andisCdkSourcealready branches on one class of source:"all"(default) — today's behaviour, correct for a dual-published package."dependencies"— bare specifiers only; relative imports are treated as same-realm.recommendedthen holds the rule for everyone and nobody disables it to silence half of it. Worth deciding as part of this whether the option is better expressed as the positive assertion it really is — "this package ships a single format and one version" — since that is the fact the consumer actually knows.Also worth folding in: the README's "turning the rule off for those files is reasonable" note and the corresponding docblock caveat both go away once the option exists.
Alternatives considered
dualPublishingplacement would have erred toward a false negative that ships the bug. Tolerable, but it still trains a consumer to disable a rule that is half right for them.no-realm-bound-instanceof-dependency/-relative), one per preset. Puts the axis in the rule names rather than an option, at the cost of two rules to document, two messages to keep in step, and a preset boundary that still cannot express "single-format package".package.json(exportsconditions, or the absence of a CommonJS dialect). Removes the configuration entirely, but makes a syntactic rule read the filesystem and guess, and it is wrong for a package mid-migration. Every other rule in this plugin is deliberately syntactic.Follow-up to #409, and additive — a new option is a minor, so this is not blocked on the first publish.