Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions crates/oxc_angular_compiler/src/linker/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,9 @@ pub struct LinkResult {
/// Scans the code for `ɵɵngDeclare*` calls and replaces them with their
/// fully compiled equivalents.
pub fn link(allocator: &Allocator, code: &str, filename: &str) -> LinkResult {
// Quick check: if no declarations, return early
if !code.contains("\u{0275}\u{0275}ngDeclare") {
// Quick check: if no declarations, return early. Match without the `ɵɵ` prefix because
// minifiers may write it as `\u0275` escapes; the AST below has them decoded.
if !code.contains("ngDeclare") {
return LinkResult { code: code.to_string(), map: None, linked: false };
}

Expand Down Expand Up @@ -4053,4 +4054,22 @@ MyDir.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.0.0
result.code
);
}

#[test]
fn test_link_unicode_escaped_identifiers() {
// esbuild's default `--charset=ascii` writes `ɵ` in identifiers as `\u0275`.
let utf8 = r#"
import * as e from "@angular/core";
class Hi { static ɵfac = e.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: e, type: Hi, deps: [], target: e.ɵɵFactoryTarget.Directive }); static ɵdir = e.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.0", type: Hi, isStandalone: true, selector: "[libHi]", inputs: { on: "on" }, host: { properties: { "class.on": "on" } }, ngImport: e }); }
e.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: e, type: Hi, decorators: [{ type: Directive, args: [{ selector: "[libHi]" }] }] });
class Greet { static ɵcmp = e.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.0", type: Greet, isStandalone: true, selector: "lib-greet", ngImport: e, template: '<p [title]="name">Hello {{ name }}</p>' }); }
"#;
let escaped = utf8.replace('\u{0275}', "\\u0275");
let allocator = Allocator::default();
let expected = link(&allocator, utf8, "test.mjs");
let result = link(&allocator, &escaped, "test.mjs");
assert!(expected.linked);
assert!(result.linked, "Escaped input should be linked, got:\n{}", result.code);
assert_eq!(result.code.replace("\\u0275", "\u{0275}"), expected.code);
}
}
15 changes: 15 additions & 0 deletions napi/angular-compiler/test/linker.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { describe, it, expect } from 'vitest'

import { linkAngularPackageSync } from '../index.js'
import { angularLinkerPlugin } from '../vite-plugin/angular-linker-plugin.js'

/**
* Minimal Angular partial declaration fixtures that simulate the structure
Expand Down Expand Up @@ -126,6 +127,20 @@ describe('Angular linker - chunk file linking', () => {
expect(result.code).not.toContain('\u0275\u0275ngDeclare')
})

it('should link through the Vite plugin when \u0275 is written as an escape', async () => {
// esbuild's default `--charset=ascii` minify writes `ɵ` in identifiers as `\u0275`.
const code = PIPE_CHUNK.replaceAll('\u0275', '\\u0275')
const plugin = angularLinkerPlugin() as any
const result = await plugin.transform.handler.call(
{},
code,
'node_modules/@angular/common/fesm2022/_pipes-chunk.mjs',
)

expect(result?.code).toContain('definePipe')
expect(result.code).not.toContain('ngDeclare')
})

it('should return linked: false for files without declarations', () => {
const code = `
export function helper() { return 42; }
Expand Down
3 changes: 2 additions & 1 deletion napi/angular-compiler/vite-plugin/angular-linker-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import type { Plugin } from 'vite'
* 2. During Vite's transform pipeline for non-optimized node_modules files
*/

const LINKER_DECLARATION_PREFIX = '\u0275\u0275ngDeclare'
// No `ɵɵ` prefix: minifiers may write it as `\u0275` escapes.
const LINKER_DECLARATION_PREFIX = 'ngDeclare'

// Skip these packages - they don't need linking
const SKIP_REGEX = /[\\/]@angular[\\/](?:compiler|core)[\\/]/
Expand Down
Loading