Skip to content

fix(directive): input names with lone surrogates are emitted in oxc's internal U+FFFD encoding #505

Description

@Brooooooklyn

A member name written with a lone surrogate escape ('x-\uD800') comes out of OXC as x-<U+FFFD>d800: U+FFFD followed by the hex code unit. This is how oxc_ast stores lone surrogates in StringLiteral::value when lone_surrogates is true. We use value as a normal string and never decode it. ngtsc writes \uD800.

So the binding name at runtime (x-<U+FFFD>d800) doesn't match the class field (x-\uD800).

Repro

import {Directive, Input} from '@angular/core';
@Directive({selector: '[d]'})
export class D {
  @Input() 'x-\uD800': any;
  @Input({transform: (v: string) => 1}) 'y-\uD800': number = 0;
}

OXC (#504 tip, 3e0b07f). � below is the U+FFFD character

static ɵdir = i0.ɵɵdefineDirective({type:D,selectors:[["","d",""]],inputs:{"x-�d800":"x-�d800",
    "y-�d800":[2,"y-�d800","y-�d800",(v) =>1]}});
// setClassMetadata
{"x-�d800":[{type:Input}],"y-�d800":[{type:Input,args:[{transform:(v) =>1}]}]}
static ɵdir: i0.ɵɵDirectiveDeclaration<D, "[d]", never, { "x-�d800": { "alias": "x-�d800"; "required": false; }; "y-�d800": { "alias": "y-�d800"; "required": false; }; }, {}, never, never, true, never>;
static "ngAcceptInputType_y-�d800": string;

ngtsc 22.1.7

static ɵdir = i0.ɵɵdefineDirective({ type: D, selectors: [["", "d", ""]], inputs: { "x-\uD800": "x-\uD800", "y-\uD800": [2, "y-\uD800", "y-\uD800", (v) => 1] } });
// setClassMetadata
{ "x-\uD800": [{ type: Input }], "y-\uD800": [{ type: Input, args: [{ transform: (v) => 1 }] }] }
static ɵdir: i0.ɵɵDirectiveDeclaration<D, "[d]", never, { "x-\uD800": { "alias": "x-\uD800"; "required": false; }; "y-\uD800": { "alias": "y-\uD800"; "required": false; }; }, {}, never, never, true, never>;
static "ngAcceptInputType_y-\uD800": string;

Where

  • Main and the stack both read the key with .value: directive/property_decorators.rs (get_property_key_name) and class_metadata/builders.rs (get_property_key_name). The same helper is copied into the component, pipe, NgModule, injectable and service decorator modules.
  • Strings are printed by the JS emitter and by dts.rs (escape_dts_string, and the ngAcceptInputType_ quoting in generate_input_transform_fields). None of them handle the encoding.

On main?

Yes. On main the field is printed unquoted, static ngAcceptInputType_y-�d800: unknown;, which is not valid TS. The stack (#496) quotes it and types it, but the name still uses the internal encoding.

Direction

Decode lone_surrogates strings when reading names and string values. Keep the lone code units in a form the emitters can write back as \uXXXX.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions