Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion packages/policy/src/linter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,19 @@ export const defaultRules: Rule[] = [
];

export async function lint(ctx: LintContext, rules: Rule[] = defaultRules): Promise<LintResult> {
const findings = (await Promise.all(rules.map((r) => r.run(ctx)))).flat();
// Filter rules to those that apply to at least one target kind present in
// the manifest. Rules with no appliesTo constraint run against all targets.
// Without this filter, mobile-only rules (e.g. mobile/bundle-id) fire
// false-positive errors on web/api targets that happen to carry a bundleId
// config field for unrelated purposes.
const manifestKinds = new Set(
Object.values(ctx.manifest.targets ?? {}).map((t) => t.kind),
);
const applicable = rules.filter(
(r) => !r.appliesTo || r.appliesTo.length === 0 || r.appliesTo.some((k) => manifestKinds.has(k)),
);
Comment on lines +24 to +29
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 t.kind does not exist on TargetSpec — manifestKinds will always be Set{undefined}

ctx.manifest.targets is typed as Record<string, TargetSpec> (see manifest.ts line 57). TargetSpec is inferred from targetSpecSchema and has only use, enabled, config, and distribute fields — there is no kind property. The kind field belongs to the Target<Config> adapter interface in target.ts, not to the manifest spec.

As a result, Object.values(...).map((t) => t.kind) produces [undefined, undefined, ...] at runtime (and is a TypeScript compilation error under strict mode). manifestKinds will be Set { undefined }, and r.appliesTo.some((k) => manifestKinds.has(k)) will always be false, meaning every rule that declares an appliesTo constraint — including bundleId and iconSizes — will be silently skipped for all manifests, not just web/api ones. Mobile and TV manifests that carry a malformed bundleId will receive no errors, which is the opposite of the intent.


const findings = (await Promise.all(applicable.map((r) => r.run(ctx)))).flat();
const errors = findings.filter((f) => f.severity === 'error').length;
const warnings = findings.filter((f) => f.severity === 'warn').length;
return { findings, errors, warnings, passed: errors === 0 };
Expand Down