Skip to content

Native Rust scanner misparses string literals as imports after import = syntax on case-insensitive filesystems (macOS) #34644

@rryter

Description

@rryter

Current Behavior

When a TypeScript file contains an import X = Y.Z declaration (namespace import-equals), the Nx native Rust scanner gets into a broken state and starts misinterpreting subsequent string literals as module specifiers. On case-insensitive filesystems (macOS HFS+/APFS default), these string literals then resolve against node_modules, producing
phantom npm dependencies in the project graph.

For example, a file with import KanalEnum = SomeEnum.Enum followed by case 'Open': causes Nx to record npm:open as a static dependency — because node_modules/Open resolves to node_modules/open on a case-insensitive filesystem.

This breaks @nx/dependency-checks: with open listed in package.json, macOS lint passes (the scanner thinks it's used), but Linux CI reports it as "not used by the project".
Removing it from package.json flips the problem — macOS reports it as "missing from dependencies", while Linux CI passes. There is no configuration that satisfies both platforms
without using ignoredDependencies.

This is the same class of bug as #8938, where the old TS-based scanner misidentified template literal strings as import statements (fixed in #10360). The current native Rust scanner has a similar problem, specifically triggered by the import = syntax.

Expected Behavior

The import X = Y.Z syntax is a TypeScript namespace alias — not a module import. The scanner should not treat it as a module specifier, and it should not affect parsing of subsequent code in the file. String literals should never be resolved as npm dependencies.

Expected Behavior

String literals ('Open', "Open") should not be treated as module specifiers. Only actual import/require statements should be analyzed for npm dependency detection.

GitHub Repo

No response

Steps to Reproduce

  1. On macOS (default case-insensitive APFS), create a workspace with a library 2. Ensure a lowercase npm package (e.g. debug) is installed — even as a transitive dependency 3. Add a .ts file with an import = declaration and a PascalCase string literal matching the package name:
    import { Component } from '@angular/core'
    
    export namespace Foo {
      export enum Bar { A = 'A' }
    }
    
    import MyBar = Foo.Bar  // <-- this breaks the scanner's state
    
    export type LogLevel = 'Debug' | 'Info' | 'Warn' | 'Error'
    
    export function getLevel(): LogLevel {
      return 'Debug'  // <-- this gets falsely resolved to npm:debug
    }
  2. Run npx nx reset && npx nx graph --file=output.json
  3. Inspect the output — the project graph includes npm:debug as a "type": "static" dependency

Control test — remove import =, keep everything else identical:

import { Component } from '@angular/core'

export namespace Foo {
  export enum Bar { A = 'A' }
}

// removed: import MyBar = Foo.Bar

export type LogLevel = 'Debug' | 'Info' | 'Warn' | 'Error'

export function getLevel(): LogLevel {
  return 'Debug'  // <-- no longer falsely detected
}

Result: npm:debug disappears from the project graph. The import = syntax is the trigger.

Proof that the filesystem is the second factor:

# macOS (case-insensitive) — resolves:
$ ls node_modules/Debug/package.json
node_modules/Debug/package.json

# Linux (case-sensitive) — would not resolve:
# ls: cannot access 'node_modules/Debug/package.json': No such file or directory

Full proof from a real project:

We traced this to helpers.ts which contains import SomelEnum = RechnungUploadCommit.KanalEnum and string literals 'Open' in switch/case statements. The Nx file map reports:

FILE: invoice.helpers.ts
DEPS: ["npm:@angular/common","@my/api","@my/shared","npm:open"]

The TypeScript compiler confirms NO actual import of open:

Actual imports: ['@angular/common', '@angular/common/http', '@my/api/leistung',
  '@my/shared', '../../statements/...', '../model/invoice.model']
Contains 'open'? false

Renaming 'Open' to 'Xopen' and rebuilding the graph: npm:open disappears. Restoring 'Open': it reappears.

Nx Report

Node           : 22.22.0
  OS             : darwin-arm64
  Native Target  : aarch64-macos
  npm            : 10.9.4
  daemon         : Available

  nx                     : 22.4.1
  lerna                  : 9.0.3
  @nx/js                 : 22.4.1
  @nx/eslint             : 22.4.1
  @nx/workspace          : 22.4.1
  @nx/angular            : 22.4.1
  @nx/jest               : 22.4.1
  @nx/cypress            : 22.4.1
  @nx/devkit             : 22.4.1
  @nx/eslint-plugin      : 22.4.1
  @nx/module-federation  : 22.4.1
  @nx/rspack             : 22.4.1
  @nx/storybook          : 22.4.1
  @nx/web                : 22.4.1
  @nx/webpack            : 22.4.1
  typescript             : 5.9.3
  ---------------------------------------
  Registered Plugins:
  @nx/storybook/plugin
  ---------------------------------------
  Community plugins:
  @storybook/angular : 10.2.8
  angular-eslint     : 21.1.0

Failure Logs

Package Manager Version

npm 10.9.4

Operating System

  • macOS
  • Linux
  • Windows
  • Other (Please specify)

Additional Information

  • Two conditions required: (1) a file contains import X = Y.Z syntax, AND (2) the same file contains a string literal that case-insensitively matches an installed npm package name
  • Only affects case-insensitive filesystems (macOS default). Does not reproduce on Linux CI (case-sensitive ext4/etc.)
  • Impact on @nx/dependency-checks: The rule cannot be satisfied on both platforms. Including the package makes CI fail ("not used"), removing it makes macOS fail ("missing"). The only workaround is ignoredDependencies.
  • Workaround: Add the falsely detected package to ignoredDependencies in the @nx/dependency-checks rule config.

Metadata

Metadata

Assignees

Labels

priority: highHigh Priority (important issues which affect many people severely)scope: corecore nx functionalitytype: bug

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions