88 lines
2.7 KiB
Go
88 lines
2.7 KiB
Go
package bundler
|
|
|
|
// esbuild silently resolves a `.js` import to a sibling `.ts`/`.tsx`/`.jsx`,
|
|
// which lets misnamed specifiers slip through. This check catches them up
|
|
// front so every import path names the file that actually exists on disk.
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
type importViolation struct {
|
|
file string
|
|
line int
|
|
spec string // the written specifier, e.g. "./Icons.js"
|
|
actual string // the corrected specifier, e.g. "./Icons.ts"
|
|
}
|
|
|
|
// reImportSpec matches a quoted relative module specifier ending in ".js".
|
|
var reImportSpec = regexp.MustCompile(`(["'])((?:\.\.?/)[^"']*?\.js)["']`)
|
|
|
|
// tsExtensions are the non-.js source extensions a .js specifier may
|
|
// actually resolve to, in esbuild's resolution order.
|
|
var tsExtensions = []string{".ts", ".tsx", ".jsx"}
|
|
|
|
// checkImportExtensions scans every JS/TS source under frontend/src for
|
|
// relative import specifiers written with a ".js" extension whose literal
|
|
// target does not exist but a sibling ".ts"/".tsx"/".jsx" does. esbuild
|
|
// resolves these transparently, so they bundle fine — but the import path
|
|
// lies about the file it points to. Specifiers that resolve to a real ".js"
|
|
// file, and dangling specifiers with no sibling at all, are left for esbuild.
|
|
func checkImportExtensions() []importViolation {
|
|
var violations []importViolation
|
|
srcRoot := filepath.Join(frontendDir, "src")
|
|
|
|
filepath.WalkDir(srcRoot, func(path string, d os.DirEntry, err error) error {
|
|
if err != nil || d.IsDir() {
|
|
return err
|
|
}
|
|
switch filepath.Ext(path) {
|
|
case ".js", ".ts", ".tsx", ".jsx":
|
|
default:
|
|
return nil
|
|
}
|
|
|
|
raw, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dir := filepath.Dir(path)
|
|
|
|
for i, line := range strings.Split(string(raw), "\n") {
|
|
for _, m := range reImportSpec.FindAllStringSubmatch(line, -1) {
|
|
spec := m[2]
|
|
if _, statErr := os.Stat(filepath.Join(dir, spec)); statErr == nil {
|
|
continue // resolves to a real .js file — fine
|
|
}
|
|
base := strings.TrimSuffix(spec, ".js")
|
|
for _, ext := range tsExtensions {
|
|
if _, statErr := os.Stat(filepath.Join(dir, base+ext)); statErr == nil {
|
|
violations = append(violations, importViolation{
|
|
file: path,
|
|
line: i + 1,
|
|
spec: spec,
|
|
actual: base + ext,
|
|
})
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
|
|
return violations
|
|
}
|
|
|
|
func reportImportViolations(violations []importViolation) {
|
|
fmt.Fprintf(os.Stderr, "\nImport extension check failed: %d import(s) use a .js extension for a non-.js file.\n", len(violations))
|
|
for _, v := range violations {
|
|
fmt.Fprintf(os.Stderr, " %s:%d: %q should be %q\n", v.file, v.line, v.spec, v.actual)
|
|
}
|
|
fmt.Fprintln(os.Stderr, "Rename each specifier to match the file's real extension.")
|
|
}
|