Skip to content

style(eslint): enable prefer-nullish-coalescing rule - #12340

Merged
pkuczynski merged 8 commits into
masterfrom
style/eslint-prefer-nullish-coalescing-full
Apr 10, 2026
Merged

pkuczynski merged 8 commits into
masterfrom
style/eslint-prefer-nullish-coalescing-full

Conversation

@pkuczynski

Copy link
Copy Markdown
Member

Enable @typescript-eslint/prefer-nullish-coalescing as suggested in #12255, replacing || with ?? / ??= where the intent is null/undefined checking rather than falsy checking.

…-nullish-coalescing-full

# Conflicts:
#	eslint.config.mjs
#	src/data-source/DataSource.ts
#	src/driver/sqlserver/SqlServerDriver.ts
#	test/github-issues/5684/issue-5684.test.ts
@pkg-pr-new

pkg-pr-new Bot commented Apr 10, 2026 •

Copy link
Copy Markdown
  • typeorm-sql-js-example

    npm i https://pkg.pr.new/typeorm/typeorm@12340
    

commit: aeeb8f8

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Apr 10, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2)   📘 Rule violations (0)   📎 Requirement gaps (0)   🎨 UX Issues (0)
🐞\ ≡ Correctness (2)

Grey Divider


Action required

1. Cache enablement regression ☑ 🐞 ≡
Description
SelectQueryBuilder.loadRawResults now uses ?? between two boolean conditions, so when
cacheOptions.alwaysEnabled is explicitly false the expression short-circuits to false and
ignores this.expressionMap.cache === true, breaking per-query caching via .cache(...). This is a
functional change from the prior || logic and can silently disable caching for users who set
alwaysEnabled: false.
Code

src/query-builder/SelectQueryBuilder.ts[R3857-3862]

   const isCachingEnabled =
       // Caching is enabled globally and isn't disabled locally.
       (cacheOptions.alwaysEnabled &&
-                this.expressionMap.cache !== false) ||
+                this.expressionMap.cache !== false) ??
       // ...or it's enabled locally explicitly.
       this.expressionMap.cache === true
Evidence
alwaysEnabled is an optional boolean (commonly absent/undefined), but when present and set to
false, the left-hand side of the new expression evaluates to the boolean false (not
null/undefined), so ?? prevents falling back to the right-hand side that checks local cache
enablement. The API explicitly sets expressionMap.cache = true for per-query caching, which is no
longer respected under alwaysEnabled: false.

src/query-builder/SelectQueryBuilder.ts[3857-3864]
src/query-builder/SelectQueryBuilder.ts[2101-2116]
src/data-source/BaseDataSourceOptions.ts[176-197]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`isCachingEnabled` uses `??` where `||` is required. When `cacheOptions.alwaysEnabled` is explicitly `false`, the expression becomes `false ?? <rhs>` which evaluates to `false`, incorrectly disabling local caching even when `.cache(true)` / `.cache(ms)` was set.
### Issue Context
The LHS is a boolean (or undefined when `alwaysEnabled` is unset). `??` only falls back on `null`/`undefined`, not on `false`, so it changes behavior compared to `||`.
### Fix Focus Areas
- src/query-builder/SelectQueryBuilder.ts[3857-3862]
### Suggested fix
Change back to the original boolean logic:

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Empty schema not defaulted 🐞 ≡
Description
PostgresQueryRunner.hasTable/hasColumn now uses `parsedTableName.schema ??= await
this.getCurrentSchema()`, which no longer treats an empty-string schema as “unset” and will pass
"" to information_schema queries. This can make schema-scoped table/column lookups incorrectly
return false when schema is an empty string (e.g., via config/env parsing).
Code

src/driver/postgres/PostgresQueryRunner.ts[R439-444]

   const parsedTableName = this.driver.parseTableName(tableOrName)
-        if (!parsedTableName.schema) {
-            parsedTableName.schema = await this.getCurrentSchema()
-        }
+        parsedTableName.schema ??= await this.getCurrentSchema()
   const sql = `SELECT * FROM "information_schema"."tables" WHERE "table_schema" = $1 AND "table_name" = $2`
   const result = await this.query(sql, [
Evidence
The query runner uses parsedTableName.schema directly as the table_schema predicate.
parseTableName propagates target.schema via ?? (so an empty string is preserved rather than
falling back to the driver schema), while the driver’s buildTableName uses a truthy check (`if
(schema)`) indicating empty string is treated like “no schema” elsewhere—making the new behavior
inconsistent and prone to false negatives in schema lookups.

src/driver/postgres/PostgresQueryRunner.ts[438-469]
src/driver/postgres/PostgresDriver.ts[1091-1098]
src/driver/postgres/PostgresDriver.ts[1070-1077]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`parsedTableName.schema ??= ...` does not overwrite `""`, but this code path historically treated falsy schema values as missing. Passing an empty schema into `information_schema` predicates can cause `hasTable`/`hasColumn` to return false incorrectly.
### Issue Context
`parseTableName` can preserve empty string schemas (`target.schema ?? ...`). Other code paths treat empty schema as absent (e.g., `buildTableName` checks `if (schema)`), so the behavior is inconsistent.
### Fix Focus Areas
- src/driver/postgres/PostgresQueryRunner.ts[438-465]
- src/driver/postgres/PostgresDriver.ts[1091-1098]
### Suggested fix
Use an explicit empty-string-aware check instead of `??=`:

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Empty cache table name ☑ 🐞 ☼
Description
DbQueryResultCache now uses nullish coalescing for cache.tableName, so an empty string ("") will no
longer fall back to "query-result-cache" and will be used to build the cache table path. This can
break cache table synchronization and subsequent cache operations due to an invalid table name/path.
Code

src/cache/DbQueryResultCache.ts[R35-38]

+        const cacheTableName = cacheOptions.tableName ?? "query-result-cache"
     this.queryResultCacheDatabase = database
     this.queryResultCacheSchema = schema
Evidence
DbQueryResultCache builds the physical cache table name by passing the configured cache.tableName
into driver.buildTableName(); because buildTableName composes the output directly from
tableName, an empty string can create an empty/invalid path used in hasTable/createTable and
queries. The PR’s own empty-string normalization pattern elsewhere indicates these defaults likely
should continue treating "" as unset.

src/cache/DbQueryResultCache.ts[28-43]
src/data-source/BaseDataSourceOptions.ts[153-180]
src/driver/postgres/PostgresDriver.ts[1063-1078]
src/cache/RedisQueryResultCache.ts[124-132]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`DbQueryResultCache` now defaults `cache.tableName` via `??`, which preserves `""` and can propagate an empty table name into `driver.buildTableName()`.
### Issue Context
Cache table name is documented as configurable with a non-empty default, and other parts of this PR normalize `""` before `??` to preserve intended fallback behavior.
### Fix Focus Areas
- src/cache/DbQueryResultCache.ts[31-43]
### Suggested change
Normalize empty string to `undefined` before coalescing:

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Empty index name kept 🐞 ≡
Description
Index/foreign key name generation now uses ??= so an empty-string name is no longer treated as
“missing” and will be interpolated into DDL as an empty quoted identifier. This is a behavior change
from the previous falsy check and can generate invalid/undesired SQL when name is "".
Code

src/driver/postgres/PostgresQueryRunner.ts[R643-649]

           // new index may be passed without name. In this case we generate index name manually.
-                if (!index.name)
-                    index.name = this.dataSource.namingStrategy.indexName(
-                        table,
-                        index.columnNames,
-                        index.where,
-                    )
+                index.name ??= this.dataSource.namingStrategy.indexName(
+                    table,
+                    index.columnNames,
+                    index.where,
+                )
           upQueries.push(this.createIndexSql(table, index))
Evidence
createIndexSql interpolates index.name directly into SQL within quotes. With `index.name ??=
..., an empty string won’t be replaced with a generated name, resulting in ... INDEX "" ...` being
emitted. Previously this code path treated falsy names as missing (as suggested by the comment and
prior logic), so this is a regression for empty-string inputs.

src/driver/postgres/PostgresQueryRunner.ts[641-651]
src/driver/postgres/PostgresQueryRunner.ts[4685-4697]
src/schema-builder/table/TableIndex.ts[15-21]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`index.name ??= ...` does not handle `index.name === ""`, which can lead to emitting SQL with an empty quoted identifier.
### Issue Context
Index names are optional (`name?: string`). The DDL builder uses the name as-is in SQL.
### Fix Focus Areas
- src/driver/postgres/PostgresQueryRunner.ts[641-649]
- src/driver/postgres/PostgresQueryRunner.ts[4685-4697]
### Suggested fix
Use an explicit empty-string-aware assignment:

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@coveralls

coveralls commented Apr 10, 2026 •

Copy link
Copy Markdown

Coverage Status

coverage: 73.67% (-0.07%) from 73.737% — style/eslint-prefer-nullish-coalescing-full into master


const portUrlPart =
schemaUrlPart === "mongodb+srv" ? "" : `:${options.port || "27017"}`
schemaUrlPart === "mongodb+srv" ? "" : `:${options.port ?? "27017"}`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Do we have the default port for other DBs?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Other drivers don't construct connection URLs manually — they pass options to their client libraries (e.g. pg, mysql2) which handle default ports internally. MongoDB is unique here because the driver builds the connection string by hand.

@alumni alumni left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Prefer optional chaining would be great to have (IMO in the same PR)

- SelectQueryBuilder: rewrite cache enablement as ternary to preserve
  boolean OR semantics (false should fall through to per-query cache)
- DriverUtils: normalize empty database string to undefined before ??
- RedisQueryResultCache: normalize empty identifier to undefined before ??
@pkuczynski

Copy link
Copy Markdown
Member Author

optional chainin

Already merged yesterday in #12331 :)

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Persistent review updated to latest commit 4d9d6de

- JoinTable: simplify negated equality to !== operator
- SpannerQueryRunner: flip negated condition to positive-first
- test-utils: simplify ternary to optional chaining
@sonarqubecloud

Copy link
Copy Markdown

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Persistent review updated to latest commit aeeb8f8

@pkuczynski
pkuczynski merged commit cab8fcb into master Apr 10, 2026
45 checks passed
@pkuczynski
pkuczynski deleted the style/eslint-prefer-nullish-coalescing-full branch April 10, 2026 17:47
@github-actions github-actions Bot added this to the 1.0 milestone Apr 10, 2026
Nosfistis pushed a commit to Nosfistis/typeorm that referenced this pull request Sep 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

5 participants