diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b0b8379ff5..c0a6f5e478f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,18 @@ I suspect this fix is actually incorrect, and is the cause of issue [#407](https://github.com/evanw/esbuild/issues/407). The problem seems to be that if you change the version of a package using `yarn add esbuild@version`, yarn doesn't clear out the installation directory before reinstalling the package so the package ends up with a mix of files from both package versions. This is not how npm behaves and seems like a pretty severe bug in yarn. I am reverting PR [#91](https://github.com/evanw/esbuild/pull/91) in an attempt to fix this issue. +* Disable some warnings for code inside `node_modules` directories ([#395](https://github.com/evanw/esbuild/issues/395) and [#402](https://github.com/evanw/esbuild/issues/402)) + + Using esbuild to build code with certain suspicious-looking syntax may generate a warning. These warnings don't fail the build (the build still succeeds) but they point out code that is very likely to not behave as intended. This has caught real bugs in the past: + + * [rollup/rollup#3729](https://github.com/rollup/rollup/issues/3729): Invalid dead code removal for return statement due to ASI + * [aws/aws-sdk-js#3325](https://github.com/aws/aws-sdk-js/issues/3325): Array equality bug in the Node.js XML parser + * [olifolkerd/tabulator#2962](https://github.com/olifolkerd/tabulator/issues/2962): Nonsensical comparisons with typeof and "null" + * [mrdoob/three.js#11183](https://github.com/mrdoob/three.js/pull/11183): Comparison with -0 in Math.js + * [mrdoob/three.js#11182](https://github.com/mrdoob/three.js/pull/11182): Cperator precedence bug in WWOBJLoader2.js + + However, it's not esbuild's job to find bugs in other libraries, and these warnings are problematic for people using these libraries with esbuild. The only fix is to either disable all esbuild warnings and not get warnings about your own code, or to try to get the warning fixed in the affected library. This is especially annoying if the warning is a false positive as was the case in https://github.com/firebase/firebase-js-sdk/issues/3814. So these warnings are now disabled for code inside `node_modules` directories. + ## 0.7.3 * Fix compile error due to missing `unix.SYS_IOCTL` in the latest `golang.org/x/sys` ([#396](https://github.com/evanw/esbuild/pull/396)) diff --git a/internal/bundler/bundler.go b/internal/bundler/bundler.go index 3bc9713f748..ae038c038c2 100644 --- a/internal/bundler/bundler.go +++ b/internal/bundler/bundler.go @@ -73,11 +73,8 @@ type Bundle struct { } type parseFlags struct { - jsxFactory []string - jsxFragment []string - isEntryPoint bool - ignoreIfUnused bool - strictClassFields bool + isEntryPoint bool + ignoreIfUnused bool } type parseArgs struct { @@ -155,17 +152,6 @@ func parseFile(args parseArgs) { loader = config.LoaderJS } - // Allow certain properties to be overridden - if len(args.flags.jsxFactory) > 0 { - args.options.JSX.Factory = args.flags.jsxFactory - } - if len(args.flags.jsxFragment) > 0 { - args.options.JSX.Fragment = args.flags.jsxFragment - } - if args.flags.strictClassFields { - args.options.Strict.ClassFields = true - } - result := parseResult{ file: file{ source: source, @@ -474,17 +460,27 @@ func ScanBundle(log logger.Log, fs fs.FS, res resolver.Resolver, entryPaths []st visited[visitedKey] = sourceIndex results = append(results, parseResult{}) flags := parseFlags{ - isEntryPoint: kind == inputKindEntryPoint, - ignoreIfUnused: resolveResult.IgnoreIfUnused, - jsxFactory: resolveResult.JSXFactory, - jsxFragment: resolveResult.JSXFragment, - strictClassFields: resolveResult.StrictClassFields, + isEntryPoint: kind == inputKindEntryPoint, + ignoreIfUnused: resolveResult.IgnoreIfUnused, } remaining++ optionsClone := options if kind != inputKindStdin { optionsClone.Stdin = nil } + optionsClone.SuppressWarningsAboutWeirdCode = resolveResult.SuppressWarningsAboutWeirdCode + + // Allow certain properties to be overridden + if len(resolveResult.JSXFactory) > 0 { + optionsClone.JSX.Factory = resolveResult.JSXFactory + } + if len(resolveResult.JSXFragment) > 0 { + optionsClone.JSX.Fragment = resolveResult.JSXFragment + } + if resolveResult.StrictClassFields { + optionsClone.Strict.ClassFields = true + } + go parseFile(parseArgs{ fs: fs, log: log, diff --git a/internal/bundler/bundler_default_test.go b/internal/bundler/bundler_default_test.go index 218447d205d..2d1b704f690 100644 --- a/internal/bundler/bundler_default_test.go +++ b/internal/bundler/bundler_default_test.go @@ -2741,3 +2741,73 @@ func TestMinifyArguments(t *testing.T) { }, }) } + +func TestWarningsInsideNodeModules(t *testing.T) { + default_suite.expectBundled(t, bundled{ + files: map[string]string{ + "/entry.js": ` + import "./dup-case.js"; import "./node_modules/dup-case.js" + import "./not-in.js"; import "./node_modules/not-in.js" + import "./not-instanceof.js"; import "./node_modules/not-instanceof.js" + import "./return-asi.js"; import "./node_modules/return-asi.js" + import "./bad-typeof.js"; import "./node_modules/bad-typeof.js" + import "./equals-neg-zero.js"; import "./node_modules/equals-neg-zero.js" + import "./equals-nan.js"; import "./node_modules/equals-nan.js" + import "./equals-object.js"; import "./node_modules/equals-object.js" + import "./write-getter.js"; import "./node_modules/write-getter.js" + import "./read-setter.js"; import "./node_modules/read-setter.js" + import "./delete-super.js"; import "./node_modules/delete-super.js" + `, + + "/dup-case.js": "switch (x) { case 0: case 0: }", + "/node_modules/dup-case.js": "switch (x) { case 0: case 0: }", + + "/not-in.js": "!a in b", + "/node_modules/not-in.js": "!a in b", + + "/not-instanceof.js": "!a instanceof b", + "/node_modules/not-instanceof.js": "!a instanceof b", + + "/return-asi.js": "return\n123", + "/node_modules/return-asi.js": "return\n123", + + "/bad-typeof.js": "typeof x == 'null'", + "/node_modules/bad-typeof.js": "typeof x == 'null'", + + "/equals-neg-zero.js": "x === -0", + "/node_modules/equals-neg-zero.js": "x === -0", + + "/equals-nan.js": "x === NaN", + "/node_modules/equals-nan.js": "x === NaN", + + "/equals-object.js": "x === []", + "/node_modules/equals-object.js": "x === []", + + "/write-getter.js": "class Foo { get #foo() {} foo() { this.#foo = 123 } }", + "/node_modules/write-getter.js": "class Foo { get #foo() {} foo() { this.#foo = 123 } }", + + "/read-setter.js": "class Foo { set #foo(x) {} foo() { return this.#foo } }", + "/node_modules/read-setter.js": "class Foo { set #foo(x) {} foo() { return this.#foo } }", + + "/delete-super.js": "class Foo extends Bar { foo() { delete super.foo } }", + "/node_modules/delete-super.js": "class Foo extends Bar { foo() { delete super.foo } }", + }, + entryPaths: []string{"/entry.js"}, + options: config.Options{ + Mode: config.ModeBundle, + AbsOutputFile: "/out.js", + }, + expectedScanLog: `/bad-typeof.js: warning: The "typeof" operator will never evaluate to "null" +/delete-super.js: warning: Attempting to delete a property of "super" will throw a ReferenceError +/dup-case.js: warning: This case clause will never be evaluated because it duplicates an earlier case clause +/equals-nan.js: warning: Comparison with NaN using the "===" operator here is always false +/equals-neg-zero.js: warning: Comparison with -0 using the "===" operator will also match 0 +/equals-object.js: warning: Comparison using the "===" operator here is always false +/not-in.js: warning: Suspicious use of the "!" operator inside the "in" operator +/not-instanceof.js: warning: Suspicious use of the "!" operator inside the "instanceof" operator +/read-setter.js: warning: Reading from setter-only property "#foo" will throw +/return-asi.js: warning: The following expression is not returned because of an automatically-inserted semicolon +/write-getter.js: warning: Writing to getter-only property "#foo" will throw +`, + }) +} diff --git a/internal/bundler/bundler_tsconfig_test.go b/internal/bundler/bundler_tsconfig_test.go index cde24df7b07..31b161260ff 100644 --- a/internal/bundler/bundler_tsconfig_test.go +++ b/internal/bundler/bundler_tsconfig_test.go @@ -563,3 +563,27 @@ func TestTsconfigJsonNodeModulesImplicitFile(t *testing.T) { }, }) } + +func TestTsconfigWarningsInsideNodeModules(t *testing.T) { + tsconfig_suite.expectBundled(t, bundled{ + files: map[string]string{ + "/Users/user/project/src/entry.tsx": ` + import "./foo" + import "bar" + `, + + "/Users/user/project/src/foo/tsconfig.json": `{ "extends": "extends for foo" }`, + "/Users/user/project/src/foo/index.js": ``, + + "/Users/user/project/src/node_modules/bar/tsconfig.json": `{ "extends": "extends for bar" }`, + "/Users/user/project/src/node_modules/bar/index.js": ``, + }, + entryPaths: []string{"/Users/user/project/src/entry.tsx"}, + options: config.Options{ + Mode: config.ModeBundle, + AbsOutputFile: "/Users/user/project/out.js", + }, + expectedScanLog: `/Users/user/project/src/foo/tsconfig.json: warning: Cannot find base config file "extends for foo" +`, + }) +} diff --git a/internal/bundler/snapshots/snapshots_default.txt b/internal/bundler/snapshots/snapshots_default.txt index e7da2b04dad..3c6a730ff48 100644 --- a/internal/bundler/snapshots/snapshots_default.txt +++ b/internal/bundler/snapshots/snapshots_default.txt @@ -1984,6 +1984,121 @@ TestUseStrictDirectiveMinifyNoBundle ---------- /out.js ---------- "use strict";a,b; +================================================================================ +TestWarningsInsideNodeModules +---------- /out.js ---------- +// /return-asi.js +var require_return_asi = __commonJS(() => { + return; +}); + +// /node_modules/return-asi.js +var require_return_asi2 = __commonJS(() => { + return; +}); + +// /dup-case.js +switch (x) { + case 0: + case 0: +} + +// /node_modules/dup-case.js +switch (x) { + case 0: + case 0: +} + +// /not-in.js +!a in b; + +// /node_modules/not-in.js +!a in b; + +// /not-instanceof.js +!a instanceof b; + +// /node_modules/not-instanceof.js +!a instanceof b; + +// /bad-typeof.js +typeof x == "null"; + +// /node_modules/bad-typeof.js +typeof x == "null"; + +// /equals-neg-zero.js +x === -0; + +// /node_modules/equals-neg-zero.js +x === -0; + +// /equals-nan.js +x === NaN; + +// /node_modules/equals-nan.js +x === NaN; + +// /equals-object.js +x === []; + +// /node_modules/equals-object.js +x === []; + +// /write-getter.js +class Foo { + get #foo() { + } + foo() { + this.#foo = 123; + } +} + +// /node_modules/write-getter.js +class Foo2 { + get #foo() { + } + foo() { + this.#foo = 123; + } +} + +// /read-setter.js +class Foo3 { + set #foo(x2) { + } + foo() { + return this.#foo; + } +} + +// /node_modules/read-setter.js +class Foo4 { + set #foo(x2) { + } + foo() { + return this.#foo; + } +} + +// /delete-super.js +class Foo5 extends Bar { + foo() { + delete super.foo; + } +} + +// /node_modules/delete-super.js +class Foo6 extends Bar { + foo() { + delete super.foo; + } +} + +// /entry.js +const return_asi = __toModule(require_return_asi()); +const return_asi2 = __toModule(require_return_asi2()); + ================================================================================ TestWithStatementTaintingNoBundle ---------- /out.js ---------- diff --git a/internal/bundler/snapshots/snapshots_tsconfig.txt b/internal/bundler/snapshots/snapshots_tsconfig.txt index ab73caffda4..68e7fa3afa8 100644 --- a/internal/bundler/snapshots/snapshots_tsconfig.txt +++ b/internal/bundler/snapshots/snapshots_tsconfig.txt @@ -181,3 +181,12 @@ var require_util = __commonJS((exports, module) => { // /Users/user/project/src/app/entry.js const util = __toModule(require_util()); console.log(util.default()); + +================================================================================ +TestTsconfigWarningsInsideNodeModules +---------- /Users/user/project/out.js ---------- +// /Users/user/project/src/foo/index.js + +// /Users/user/project/src/node_modules/bar/index.js + +// /Users/user/project/src/entry.tsx diff --git a/internal/config/config.go b/internal/config/config.go index 35f659d1571..24d98d6b94d 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -169,6 +169,18 @@ type Options struct { MangleSyntax bool CodeSplitting bool + // Setting this to true disables warnings about code that is very likely to + // be a bug. This is used to ignore issues inside "node_modules" directories. + // This has caught real issues in the past. However, it's not esbuild's job + // to find bugs in other libraries, and these warnings are problematic for + // people using these libraries with esbuild. The only fix is to either + // disable all esbuild warnings and not get warnings about your own code, or + // to try to get the warning fixed in the affected library. This is + // especially annoying if the warning is a false positive as was the case in + // https://github.com/firebase/firebase-js-sdk/issues/3814. So these warnings + // are now disabled for code inside "node_modules" directories. + SuppressWarningsAboutWeirdCode bool + // If true, make sure to generate a single file that can be written to stdout WriteToStdout bool diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go index 9d7151d1fb7..e1a06976197 100644 --- a/internal/js_parser/js_parser.go +++ b/internal/js_parser/js_parser.go @@ -301,6 +301,10 @@ func (dc *duplicateCaseChecker) reset() { } func (dc *duplicateCaseChecker) check(p *parser, expr js_ast.Expr) { + if p.SuppressWarningsAboutWeirdCode { + return + } + if hash, ok := duplicateCaseHash(expr); ok { bucket := hash % bloomFilterSize entry := &dc.bloomFilter[bucket/8] @@ -3349,9 +3353,11 @@ func (p *parser) parseSuffix(left js_ast.Expr, level js_ast.L, errors *deferredE } // Warn about "!a in b" instead of "!(a in b)" - if e, ok := left.Data.(*js_ast.EUnary); ok && e.Op == js_ast.UnOpNot { - p.log.AddWarning(&p.source, left.Loc, - "Suspicious use of the \"!\" operator inside the \"in\" operator") + if !p.SuppressWarningsAboutWeirdCode { + if e, ok := left.Data.(*js_ast.EUnary); ok && e.Op == js_ast.UnOpNot { + p.log.AddWarning(&p.source, left.Loc, + "Suspicious use of the \"!\" operator inside the \"in\" operator") + } } p.lexer.Next() @@ -3362,10 +3368,13 @@ func (p *parser) parseSuffix(left js_ast.Expr, level js_ast.L, errors *deferredE return left } - // Warn about "!a instanceof b" instead of "!(a instanceof b)" - if e, ok := left.Data.(*js_ast.EUnary); ok && e.Op == js_ast.UnOpNot { - p.log.AddWarning(&p.source, left.Loc, - "Suspicious use of the \"!\" operator inside the \"instanceof\" operator") + // Warn about "!a instanceof b" instead of "!(a instanceof b)". Here's an + // example of code with this problem: https://github.com/mrdoob/three.js/pull/11182. + if !p.SuppressWarningsAboutWeirdCode { + if e, ok := left.Data.(*js_ast.EUnary); ok && e.Op == js_ast.UnOpNot { + p.log.AddWarning(&p.source, left.Loc, + "Suspicious use of the \"!\" operator inside the \"instanceof\" operator") + } } p.lexer.Next() @@ -5572,17 +5581,20 @@ func (p *parser) parseStmtsUpTo(end js_lexer.T, opts parseStmtOpts) []js_ast.Stm stmts = append(stmts, stmt) - // Warn about ASI and return statements - if s, ok := stmt.Data.(*js_ast.SReturn); ok && s.Value == nil && !p.latestReturnHadSemicolon { - returnWithoutSemicolonStart = stmt.Loc.Start - } else { - if returnWithoutSemicolonStart != -1 { - if _, ok := stmt.Data.(*js_ast.SExpr); ok { - p.log.AddWarning(&p.source, logger.Loc{Start: returnWithoutSemicolonStart + 6}, - "The following expression is not returned because of an automatically-inserted semicolon") + // Warn about ASI and return statements. Here's an example of code with + // this problem: https://github.com/rollup/rollup/issues/3729 + if !p.SuppressWarningsAboutWeirdCode { + if s, ok := stmt.Data.(*js_ast.SReturn); ok && s.Value == nil && !p.latestReturnHadSemicolon { + returnWithoutSemicolonStart = stmt.Loc.Start + } else { + if returnWithoutSemicolonStart != -1 { + if _, ok := stmt.Data.(*js_ast.SExpr); ok { + p.log.AddWarning(&p.source, logger.Loc{Start: returnWithoutSemicolonStart + 6}, + "The following expression is not returned because of an automatically-inserted semicolon") + } } + returnWithoutSemicolonStart = -1 } - returnWithoutSemicolonStart = -1 } } @@ -7532,8 +7544,13 @@ func (p *parser) checkForTypeofAndString(a js_ast.Expr, b js_ast.Expr) bool { switch value { case "undefined", "object", "boolean", "number", "bigint", "string", "symbol", "function", "unknown": default: - r := p.source.RangeOfString(b.Loc) - p.log.AddRangeWarning(&p.source, r, fmt.Sprintf("The \"typeof\" operator will never evaluate to %q", value)) + // Warn about typeof comparisons with values that will never be + // returned. Here's an example of code with this problem: + // https://github.com/olifolkerd/tabulator/issues/2962 + if !p.SuppressWarningsAboutWeirdCode { + r := p.source.RangeOfString(b.Loc) + p.log.AddRangeWarning(&p.source, r, fmt.Sprintf("The \"typeof\" operator will never evaluate to %q", value)) + } } return true } @@ -7542,16 +7559,21 @@ func (p *parser) checkForTypeofAndString(a js_ast.Expr, b js_ast.Expr) bool { } func (p *parser) warnAboutEqualityCheck(op string, value js_ast.Expr, afterOpLoc logger.Loc) bool { + if p.SuppressWarningsAboutWeirdCode { + return false + } + switch e := value.Data.(type) { case *js_ast.ENumber: - // "0 === -0" is true in JavaScript + // "0 === -0" is true in JavaScript. Here's an example of code with this + // problem: https://github.com/mrdoob/three.js/pull/11183 if e.Value == 0 && math.Signbit(e.Value) { r := logger.Range{Loc: value.Loc, Len: 0} if int(r.Loc.Start) < len(p.source.Contents) && p.source.Contents[r.Loc.Start] == '-' { zeroRange := p.source.RangeOfNumber(logger.Loc{Start: r.Loc.Start + 1}) r.Len = zeroRange.Len + 1 } - text := fmt.Sprintf("Comparison with -0 using the %s operator will also match 0", op) + text := fmt.Sprintf("Comparison with -0 using the %q operator will also match 0", op) if op == "case" { text = "Comparison with -0 using a case clause will also match 0" } @@ -7561,7 +7583,7 @@ func (p *parser) warnAboutEqualityCheck(op string, value js_ast.Expr, afterOpLoc // "NaN === NaN" is false in JavaScript if math.IsNaN(e.Value) { - text := fmt.Sprintf("Comparison with NaN using the %s operator here is always %v", op, op[0] == '!') + text := fmt.Sprintf("Comparison with NaN using the %q operator here is always %v", op, op[0] == '!') if op == "case" { text = "This case clause will never be evaluated because equality with NaN is always false" } @@ -7573,9 +7595,10 @@ func (p *parser) warnAboutEqualityCheck(op string, value js_ast.Expr, afterOpLoc *js_ast.EFunction, *js_ast.EObject, *js_ast.ERegExp: // This warning only applies to strict equality because loose equality can // cause string conversions. For example, "x == []" is true if x is the - // empty string. + // empty string. Here's an example of code with this problem: + // https://github.com/aws/aws-sdk-js/issues/3325 if len(op) > 2 { - text := fmt.Sprintf("Comparison using the %s operator here is always %v", op, op[0] == '!') + text := fmt.Sprintf("Comparison using the %q operator here is always %v", op, op[0] == '!') if op == "case" { text = "This case clause will never be evaluated because the comparison is always false" } @@ -8495,12 +8518,14 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO if !kind.IsPrivate() { r := logger.Range{Loc: e.Index.Loc, Len: int32(len(name))} p.log.AddRangeError(&p.source, r, fmt.Sprintf("Private name %q must be declared in an enclosing class", name)) - } else if in.assignTarget != js_ast.AssignTargetNone && (kind == js_ast.SymbolPrivateGet || kind == js_ast.SymbolPrivateStaticGet) { - r := logger.Range{Loc: e.Index.Loc, Len: int32(len(name))} - p.log.AddRangeWarning(&p.source, r, fmt.Sprintf("Writing to getter-only property %q will throw", name)) - } else if in.assignTarget != js_ast.AssignTargetReplace && (kind == js_ast.SymbolPrivateSet || kind == js_ast.SymbolPrivateStaticSet) { - r := logger.Range{Loc: e.Index.Loc, Len: int32(len(name))} - p.log.AddRangeWarning(&p.source, r, fmt.Sprintf("Reading from setter-only property %q will throw", name)) + } else if !p.SuppressWarningsAboutWeirdCode { + if in.assignTarget != js_ast.AssignTargetNone && (kind == js_ast.SymbolPrivateGet || kind == js_ast.SymbolPrivateStaticGet) { + r := logger.Range{Loc: e.Index.Loc, Len: int32(len(name))} + p.log.AddRangeWarning(&p.source, r, fmt.Sprintf("Writing to getter-only property %q will throw", name)) + } else if in.assignTarget != js_ast.AssignTargetReplace && (kind == js_ast.SymbolPrivateSet || kind == js_ast.SymbolPrivateStaticSet) { + r := logger.Range{Loc: e.Index.Loc, Len: int32(len(name))} + p.log.AddRangeWarning(&p.source, r, fmt.Sprintf("Reading from setter-only property %q will throw", name)) + } } // Lower private member access only if we're sure the target isn't needed @@ -8591,7 +8616,7 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO superPropLoc = e2.Target.Loc } } - if superPropLoc.Start != 0 { + if !p.SuppressWarningsAboutWeirdCode && superPropLoc.Start != 0 { r := js_lexer.RangeOfIdentifier(p.source, superPropLoc) p.log.AddRangeWarning(&p.source, r, "Attempting to delete a property of \"super\" will throw a ReferenceError") } diff --git a/internal/js_parser/js_parser_test.go b/internal/js_parser/js_parser_test.go index 8b6aefc9d7f..f655cc484a5 100644 --- a/internal/js_parser/js_parser_test.go +++ b/internal/js_parser/js_parser_test.go @@ -1542,44 +1542,44 @@ func TestCatch(t *testing.T) { } func TestWarningEqualsNegativeZero(t *testing.T) { - expectParseError(t, "x === -0", ": warning: Comparison with -0 using the === operator will also match 0\n") - expectParseError(t, "x == -0", ": warning: Comparison with -0 using the == operator will also match 0\n") - expectParseError(t, "x !== -0", ": warning: Comparison with -0 using the !== operator will also match 0\n") - expectParseError(t, "x != -0", ": warning: Comparison with -0 using the != operator will also match 0\n") + expectParseError(t, "x === -0", ": warning: Comparison with -0 using the \"===\" operator will also match 0\n") + expectParseError(t, "x == -0", ": warning: Comparison with -0 using the \"==\" operator will also match 0\n") + expectParseError(t, "x !== -0", ": warning: Comparison with -0 using the \"!==\" operator will also match 0\n") + expectParseError(t, "x != -0", ": warning: Comparison with -0 using the \"!=\" operator will also match 0\n") expectParseError(t, "switch (x) { case -0: }", ": warning: Comparison with -0 using a case clause will also match 0\n") - expectParseError(t, "-0 === x", ": warning: Comparison with -0 using the === operator will also match 0\n") - expectParseError(t, "-0 == x", ": warning: Comparison with -0 using the == operator will also match 0\n") - expectParseError(t, "-0 !== x", ": warning: Comparison with -0 using the !== operator will also match 0\n") - expectParseError(t, "-0 != x", ": warning: Comparison with -0 using the != operator will also match 0\n") + expectParseError(t, "-0 === x", ": warning: Comparison with -0 using the \"===\" operator will also match 0\n") + expectParseError(t, "-0 == x", ": warning: Comparison with -0 using the \"==\" operator will also match 0\n") + expectParseError(t, "-0 !== x", ": warning: Comparison with -0 using the \"!==\" operator will also match 0\n") + expectParseError(t, "-0 != x", ": warning: Comparison with -0 using the \"!=\" operator will also match 0\n") expectParseError(t, "switch (-0) { case x: }", "") // Don't bother to handle this case } func TestWarningEqualsNewObject(t *testing.T) { - expectParseError(t, "x === []", ": warning: Comparison using the === operator here is always false\n") - expectParseError(t, "x !== []", ": warning: Comparison using the !== operator here is always true\n") + expectParseError(t, "x === []", ": warning: Comparison using the \"===\" operator here is always false\n") + expectParseError(t, "x !== []", ": warning: Comparison using the \"!==\" operator here is always true\n") expectParseError(t, "x == []", "") expectParseError(t, "x != []", "") expectParseError(t, "switch (x) { case []: }", ": warning: This case clause will never be evaluated because the comparison is always false\n") - expectParseError(t, "[] === x", ": warning: Comparison using the === operator here is always false\n") - expectParseError(t, "[] !== x", ": warning: Comparison using the !== operator here is always true\n") + expectParseError(t, "[] === x", ": warning: Comparison using the \"===\" operator here is always false\n") + expectParseError(t, "[] !== x", ": warning: Comparison using the \"!==\" operator here is always true\n") expectParseError(t, "[] == x", "") expectParseError(t, "[] != x", "") expectParseError(t, "switch ([]) { case x: }", "") // Don't bother to handle this case } func TestWarningEqualsNaN(t *testing.T) { - expectParseError(t, "x === NaN", ": warning: Comparison with NaN using the === operator here is always false\n") - expectParseError(t, "x !== NaN", ": warning: Comparison with NaN using the !== operator here is always true\n") - expectParseError(t, "x == NaN", ": warning: Comparison with NaN using the == operator here is always false\n") - expectParseError(t, "x != NaN", ": warning: Comparison with NaN using the != operator here is always true\n") + expectParseError(t, "x === NaN", ": warning: Comparison with NaN using the \"===\" operator here is always false\n") + expectParseError(t, "x !== NaN", ": warning: Comparison with NaN using the \"!==\" operator here is always true\n") + expectParseError(t, "x == NaN", ": warning: Comparison with NaN using the \"==\" operator here is always false\n") + expectParseError(t, "x != NaN", ": warning: Comparison with NaN using the \"!=\" operator here is always true\n") expectParseError(t, "switch (x) { case NaN: }", ": warning: This case clause will never be evaluated because equality with NaN is always false\n") - expectParseError(t, "NaN === x", ": warning: Comparison with NaN using the === operator here is always false\n") - expectParseError(t, "NaN !== x", ": warning: Comparison with NaN using the !== operator here is always true\n") - expectParseError(t, "NaN == x", ": warning: Comparison with NaN using the == operator here is always false\n") - expectParseError(t, "NaN != x", ": warning: Comparison with NaN using the != operator here is always true\n") + expectParseError(t, "NaN === x", ": warning: Comparison with NaN using the \"===\" operator here is always false\n") + expectParseError(t, "NaN !== x", ": warning: Comparison with NaN using the \"!==\" operator here is always true\n") + expectParseError(t, "NaN == x", ": warning: Comparison with NaN using the \"==\" operator here is always false\n") + expectParseError(t, "NaN != x", ": warning: Comparison with NaN using the \"!=\" operator here is always true\n") expectParseError(t, "switch (NaN) { case x: }", "") // Don't bother to handle this case } diff --git a/internal/resolver/resolver.go b/internal/resolver/resolver.go index 13cf044117a..42603617f67 100644 --- a/internal/resolver/resolver.go +++ b/internal/resolver/resolver.go @@ -87,6 +87,9 @@ type ResolveResult struct { // If true, the class field transform should use Object.defineProperty(). StrictClassFields bool + + // This is true if the file is inside a "node_modules" directory + SuppressWarningsAboutWeirdCode bool } type Resolver interface { @@ -150,12 +153,31 @@ func (r *resolver) ResolveAbs(absPath string) *ResolveResult { return r.finalizeResolve(ResolveResult{PathPair: PathPair{Primary: logger.Path{Text: absPath, Namespace: "file"}}}) } +func isInsideNodeModules(fs fs.FS, path string) bool { + dir := fs.Dir(path) + for { + if fs.Base(dir) == "node_modules" { + return true + } + parent := fs.Dir(dir) + if dir == parent { + return false + } + dir = parent + } +} + func (r *resolver) finalizeResolve(result ResolveResult) *ResolveResult { for _, path := range result.PathPair.iter() { if path.Namespace == "file" { if dirInfo := r.dirInfoCached(r.fs.Dir(path.Text)); dirInfo != nil { base := r.fs.Base(path.Text) + // Don't emit warnings for code inside a "node_modules" directory + if isInsideNodeModules(r.fs, path.Text) { + result.SuppressWarningsAboutWeirdCode = true + } + // Look up this file in the "sideEffects" map in the nearest enclosing // directory with a "package.json" file for info := dirInfo; info != nil; info = info.parent { @@ -537,7 +559,8 @@ func (r *resolver) parseJsTsConfig(file string, visited map[string]bool) (*tsCon } } - if !found { + // Suppress warnings about missing base config files inside "node_modules" + if !found && !isInsideNodeModules(r.fs, file) { r.log.AddRangeWarning(&tsConfigSource, warnRange, fmt.Sprintf("Cannot find base config file %q", extends)) }