From 27a7f37494f9b3e315bb8b89196d71b6f541b31f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Tue, 30 Jun 2026 11:31:42 -0700 Subject: [PATCH] fix(lint): recognize Three.js loaded via ESM URL/path imports (#1805) The missing_three_script rule only treated a bare import from 'three' as loading Three.js, so ESM imports whose specifier is a URL or path (e.g. .../+esm CDN builds, esm.sh/three, unpkg three.module.js, or a local three.module.js) were not recognized. Compositions using THREE. with such an import got a false blocking error, pushing authors onto the deprecated UMD global build. Generalize the module-import detection to count any import/from whose specifier contains "three" (case-insensitive), matching the existing loose /three/i treatment of +`; + const result = await lintHyperframeHtml(html); + const finding = result.findings.find((f) => f.code === "missing_three_script"); + expect(finding).toBeUndefined(); + }); + + it("does not report missing_three_script for an esm.sh/three import", async () => { + const html = ` + +
+ +`; + const result = await lintHyperframeHtml(html); + const finding = result.findings.find((f) => f.code === "missing_three_script"); + expect(finding).toBeUndefined(); + }); + + it("does not report missing_three_script for a local three.module.js import", async () => { + const html = ` + +
+ +`; + const result = await lintHyperframeHtml(html); + const finding = result.findings.find((f) => f.code === "missing_three_script"); + expect(finding).toBeUndefined(); + }); + + it("does not report missing_three_script for a bare 'three' import (regression)", async () => { + const html = ` + +
+ +`; + const result = await lintHyperframeHtml(html); + const finding = result.findings.find((f) => f.code === "missing_three_script"); + expect(finding).toBeUndefined(); + }); + + it("still reports missing_three_script when THREE is used with no three loaded", async () => { + const html = ` + +
+ +`; + const result = await lintHyperframeHtml(html); + const finding = result.findings.find((f) => f.code === "missing_three_script"); + expect(finding).toBeDefined(); + expect(finding?.severity).toBe("error"); + }); + it("does not report any adapter errors for composition with no adapter usage", async () => { const html = ` diff --git a/packages/lint/src/rules/adapters.ts b/packages/lint/src/rules/adapters.ts index 2a9b05bc2..4de4b330b 100644 --- a/packages/lint/src/rules/adapters.ts +++ b/packages/lint/src/rules/adapters.ts @@ -36,8 +36,11 @@ export const adapterRules: Array<(ctx: LintContext) => HyperframeLintFinding[]> /["']three["']/.test(t) && /importmap/.test(scripts.find((s) => s.content === t)?.attrs || ""), ); - const hasThreeModuleImport = texts.some( - (t) => /\bimport\b.*['"]three['"]/.test(t) || /\bfrom\s+['"]three['"]/.test(t), + // Matches any import/from whose specifier contains "three" (bare 'three', or a + // URL/path like .../+esm, esm.sh/three, three.module.js), mirroring the loose + // /three/i treatment of