diff --git a/internal/bundler/bundler.go b/internal/bundler/bundler.go index 569eb361e60..f702c119d0c 100644 --- a/internal/bundler/bundler.go +++ b/internal/bundler/bundler.go @@ -2062,11 +2062,8 @@ func (s *scanner) processScannedFiles(entryPointMeta []graph.EntryPoint) []scann stubKey.Text = canonicalFileSystemPathForWindows(stubKey.Text) } sourceIndex := s.allocateSourceIndex(stubKey, cache.SourceIndexJSStubForCSS) - source := logger.Source{ - Index: sourceIndex, - PrettyPath: otherFile.inputFile.Source.PrettyPath, - IdentifierName: otherFile.inputFile.Source.IdentifierName, - } + source := otherFile.inputFile.Source + source.Index = sourceIndex // Export all local CSS names for JavaScript to use exports := js_ast.EObject{} @@ -2074,9 +2071,10 @@ func (s *scanner) processScannedFiles(entryPointMeta []graph.EntryPoint) []scann for innerIndex, symbol := range css.AST.Symbols { if symbol.Kind == ast.SymbolLocalCSS { ref := ast.Ref{SourceIndex: cssSourceIndex, InnerIndex: uint32(innerIndex)} + loc := css.AST.DefineLocs[ref] exports.Properties = append(exports.Properties, js_ast.Property{ - Key: js_ast.Expr{Data: &js_ast.EString{Value: helpers.StringToUTF16(symbol.OriginalName)}}, - ValueOrNil: js_ast.Expr{Data: &js_ast.ENameOfSymbol{Ref: ref}}, + Key: js_ast.Expr{Loc: loc, Data: &js_ast.EString{Value: helpers.StringToUTF16(symbol.OriginalName)}}, + ValueOrNil: js_ast.Expr{Loc: loc, Data: &js_ast.ENameOfSymbol{Ref: ref}}, }) } } @@ -2085,6 +2083,7 @@ func (s *scanner) processScannedFiles(entryPointMeta []graph.EntryPoint) []scann file: scannerFile{ inputFile: graph.InputFile{ Source: source, + Loader: otherFile.inputFile.Loader, Repr: &graph.JSRepr{ AST: js_parser.LazyExportAST(s.log, source, js_parser.OptionsFromConfig(&s.options), js_ast.Expr{Data: &exports}, ""), diff --git a/internal/bundler_tests/snapshots/snapshots_css.txt b/internal/bundler_tests/snapshots/snapshots_css.txt index 5e1b7e78037..341bca53183 100644 --- a/internal/bundler_tests/snapshots/snapshots_css.txt +++ b/internal/bundler_tests/snapshots/snapshots_css.txt @@ -1110,13 +1110,13 @@ console.log("file 1", t, l.a); // dir2/style.css var e = "n"; -var f = { +var n = { b: "e", button: e }; // b.js -console.log("file 2", e, f.b); +console.log("file 2", e, n.b); ---------- /out/entry.css ---------- /* dir1/style.css */ @@ -1154,7 +1154,7 @@ TestImportLocalCSSFromJSMinifyIdentifiersAvoidGlobalNames ================================================================================ TestMetafileCSSBundleTwoToOne ----------- /out/js/UOATE6K4.js ---------- +---------- /out/js/2PSDKYWE.js ---------- // foo/entry.js console.log("foo"); @@ -1164,7 +1164,7 @@ body { color: red; } ----------- /out/js/6ZCNL5VY.js ---------- +---------- /out/js/MA6C7ZBK.js ---------- // bar/entry.js console.log("bar"); ---------- metafile.json ---------- @@ -1198,7 +1198,7 @@ console.log("bar"); } }, "outputs": { - "out/js/UOATE6K4.js": { + "out/js/2PSDKYWE.js": { "imports": [], "exports": [], "entryPoint": "foo/entry.js", @@ -1222,7 +1222,7 @@ console.log("bar"); }, "bytes": 40 }, - "out/js/6ZCNL5VY.js": { + "out/js/MA6C7ZBK.js": { "imports": [], "exports": [], "entryPoint": "bar/entry.js", diff --git a/internal/bundler_tests/snapshots/snapshots_default.txt b/internal/bundler_tests/snapshots/snapshots_default.txt index 6332c6224d9..265928bf67d 100644 --- a/internal/bundler_tests/snapshots/snapshots_default.txt +++ b/internal/bundler_tests/snapshots/snapshots_default.txt @@ -1170,13 +1170,13 @@ console.log((init_types(), __toCommonJS(types_exports))); ================================================================================ TestEntryNamesChunkNamesExtPlaceholder ----------- /out/main/js/entry1-L7KI5G7A.js ---------- +---------- /out/main/js/entry1-4X3SO762.js ---------- import "../../common/js/chunk-XHGYOYUR.js"; // src/entries/entry1.js console.log("entry1"); ----------- /out/main/js/entry2-KTHKWVT2.js ---------- +---------- /out/main/js/entry2-URQRHZS5.js ---------- import "../../common/js/chunk-XHGYOYUR.js"; // src/entries/entry2.js diff --git a/internal/config/config.go b/internal/config/config.go index 6bb6c5f5be9..c309f5cc095 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -229,9 +229,17 @@ func (loader Loader) IsTypeScript() bool { switch loader { case LoaderTS, LoaderTSNoAmbiguousLessThan, LoaderTSX: return true - default: - return false } + return false +} + +func (loader Loader) IsCSS() bool { + switch loader { + case + LoaderCSS, LoaderGlobalCSS, LoaderLocalCSS: + return true + } + return false } func (loader Loader) CanHaveSourceMap() bool { @@ -242,9 +250,8 @@ func (loader Loader) CanHaveSourceMap() bool { LoaderCSS, LoaderGlobalCSS, LoaderLocalCSS, LoaderJSON, LoaderText: return true - default: - return false } + return false } type Format uint8 diff --git a/internal/css_ast/css_ast.go b/internal/css_ast/css_ast.go index 2aaae2d14fd..01b3cd51a55 100644 --- a/internal/css_ast/css_ast.go +++ b/internal/css_ast/css_ast.go @@ -30,6 +30,7 @@ type AST struct { Rules []Rule SourceMapComment logger.Span ApproximateLineCount int32 + DefineLocs map[ast.Ref]logger.Loc } // We create a lot of tokens, so make sure this layout is memory-efficient. diff --git a/internal/css_lexer/css_lexer.go b/internal/css_lexer/css_lexer.go index 8a21ed9b996..c077f7cc303 100644 --- a/internal/css_lexer/css_lexer.go +++ b/internal/css_lexer/css_lexer.go @@ -598,6 +598,54 @@ func WouldStartIdentifierWithoutEscapes(text string) bool { return false } +func RangeOfIdentifier(source logger.Source, loc logger.Loc) logger.Range { + text := source.Contents[loc.Start:] + if len(text) == 0 { + return logger.Range{Loc: loc, Len: 0} + } + + i := 0 + n := len(text) + + for { + c, width := utf8.DecodeRuneInString(text[i:]) + if IsNameContinue(c) { + i += width + continue + } + + // Handle an escape + if c == '\\' && i+1 < n && !isNewline(rune(text[i+1])) { + i += width // Skip the backslash + c, width = utf8.DecodeRuneInString(text[i:]) + if _, ok := isHex(c); ok { + i += width + c, width = utf8.DecodeRuneInString(text[i:]) + for j := 0; j < 5; j++ { + if _, ok := isHex(c); !ok { + break + } + i += width + c, width = utf8.DecodeRuneInString(text[i:]) + } + if isWhitespace(c) { + i += width + } + } + continue + } + + break + } + + // Don't end with a whitespace + if i > 0 && isWhitespace(rune(text[i-1])) { + i-- + } + + return logger.Range{Loc: loc, Len: int32(i)} +} + func (lexer *lexer) wouldStartNumber() bool { if lexer.codePoint >= '0' && lexer.codePoint <= '9' { return true diff --git a/internal/css_parser/css_parser.go b/internal/css_parser/css_parser.go index 6b5d150e227..e20ccaf52cd 100644 --- a/internal/css_parser/css_parser.go +++ b/internal/css_parser/css_parser.go @@ -24,6 +24,7 @@ type parser struct { stack []css_lexer.T importRecords []ast.ImportRecord symbols []ast.Symbol + defineLocs map[ast.Ref]logger.Loc localSymbolMap map[string]ast.Ref globalSymbolMap map[string]ast.Ref nestingWarnings map[logger.Loc]struct{} @@ -125,6 +126,7 @@ func Parse(log logger.Log, source logger.Source, options Options) css_ast.AST { allComments: result.AllComments, legalComments: result.LegalComments, prevError: logger.Loc{Start: -1}, + defineLocs: make(map[ast.Ref]logger.Loc), localSymbolMap: make(map[string]ast.Ref), globalSymbolMap: make(map[string]ast.Ref), makeLocalSymbols: options.symbolMode == symbolModeLocal, @@ -142,6 +144,7 @@ func Parse(log logger.Log, source logger.Source, options Options) css_ast.AST { ImportRecords: p.importRecords, ApproximateLineCount: result.ApproximateLineCount, SourceMapComment: result.SourceMapComment, + DefineLocs: p.defineLocs, } } @@ -301,7 +304,7 @@ func (p *parser) unexpected() { } } -func (p *parser) symbolForName(name string) ast.Ref { +func (p *parser) symbolForName(loc logger.Loc, name string) ast.Ref { var kind ast.SymbolKind var scope map[string]ast.Ref @@ -325,6 +328,7 @@ func (p *parser) symbolForName(name string) ast.Ref { Link: ast.InvalidRef, }) scope[name] = ref + p.defineLocs[ref] = loc } p.symbols[ref.InnerIndex].UseCountEstimate++ diff --git a/internal/css_parser/css_parser_selector.go b/internal/css_parser/css_parser_selector.go index 898cbd9d1e0..ad1140080d8 100644 --- a/internal/css_parser/css_parser_selector.go +++ b/internal/css_parser/css_parser_selector.go @@ -366,7 +366,7 @@ subclassSelectors: sel.SubclassSelectors = append(sel.SubclassSelectors, css_ast.SubclassSelector{ Loc: subclassToken.Range.Loc, Data: &css_ast.SSHash{ - Name: ast.LocRef{Loc: nameLoc, Ref: p.symbolForName(name)}, + Name: ast.LocRef{Loc: nameLoc, Ref: p.symbolForName(nameLoc, name)}, }, }) p.advance() @@ -378,7 +378,7 @@ subclassSelectors: sel.SubclassSelectors = append(sel.SubclassSelectors, css_ast.SubclassSelector{ Loc: subclassToken.Range.Loc, Data: &css_ast.SSClass{ - Name: ast.LocRef{Loc: nameLoc, Ref: p.symbolForName(name)}, + Name: ast.LocRef{Loc: nameLoc, Ref: p.symbolForName(nameLoc, name)}, }, }) if !p.expect(css_lexer.TIdent) { diff --git a/internal/linker/linker.go b/internal/linker/linker.go index 337b3993d4b..421b0148271 100644 --- a/internal/linker/linker.go +++ b/internal/linker/linker.go @@ -24,6 +24,7 @@ import ( "github.com/evanw/esbuild/internal/compat" "github.com/evanw/esbuild/internal/config" "github.com/evanw/esbuild/internal/css_ast" + "github.com/evanw/esbuild/internal/css_lexer" "github.com/evanw/esbuild/internal/css_parser" "github.com/evanw/esbuild/internal/css_printer" "github.com/evanw/esbuild/internal/fs" @@ -2672,7 +2673,12 @@ func (c *linkerContext) maybeCorrectObviousTypo(repr *graph.JSRepr, name string, // happen with automatically-generated exports from non-JavaScript files. note.Text = text } else { - r := js_lexer.RangeOfIdentifier(importedFile.InputFile.Source, export.NameLoc) + var r logger.Range + if importedFile.InputFile.Loader.IsCSS() { + r = css_lexer.RangeOfIdentifier(importedFile.InputFile.Source, export.NameLoc) + } else { + r = js_lexer.RangeOfIdentifier(importedFile.InputFile.Source, export.NameLoc) + } note = importedFile.LineColumnTracker().MsgData(r, text) } msg.Notes = append(msg.Notes, note) diff --git a/scripts/end-to-end-tests.js b/scripts/end-to-end-tests.js index 4db2d948f5a..9286feb368b 100644 --- a/scripts/end-to-end-tests.js +++ b/scripts/end-to-end-tests.js @@ -7845,6 +7845,34 @@ tests.push( }), ) +// Test CSS-related warning ranges +tests.push( + test(['in.js', '--outfile=node.js', '--bundle', '--loader:.css=local-css'], { + 'in.js': ` + import * as ns from './styles.css' + if (ns.buton !== void 0) throw 'fail' + `, + 'styles.css': ` + .bu\\74 ton { color: red } + `, + }, { + expectedStderr: `▲ [WARNING] Import "buton" will always be undefined because there is no matching export in "styles.css" [import-is-undefined] + + in.js:3:13: + 3 │ if (ns.buton !== void 0) throw 'fail' + │ ~~~~~ + ╵ button + + Did you mean to import "button" instead? + + styles.css:2:7: + 2 │ .bu\\74 ton { color: red } + ╵ ~~~~~~~~~ + +`, + }), +) + // Test writing to stdout tests.push( // These should succeed