mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
refactor(compiler-cli): escape quotes used in string expressions
TypeScript has functionality that automatically escapes quotes in string literals. These changes update the places where we may need to do the same ourselves.
This commit is contained in:
committed by
Jessica Janiuk
parent
b37fce6066
commit
f26308813b
@@ -39,7 +39,7 @@ import {
|
||||
Unary,
|
||||
VoidExpression,
|
||||
} from '@angular/compiler';
|
||||
import {TcbExpr} from './ops/codegen';
|
||||
import {quoteAndEscape, TcbExpr} from './ops/codegen';
|
||||
import {TypeCheckingConfig} from '../api';
|
||||
|
||||
/**
|
||||
@@ -165,7 +165,8 @@ class TcbExprTranslator implements AstVisitor {
|
||||
const value = this.translate(ast.values[idx]);
|
||||
|
||||
if (key.kind === 'property') {
|
||||
const keyNode = new TcbExpr(`"${key.key}"`).addParseSpanInfo(key.sourceSpan);
|
||||
const keyNode = new TcbExpr(quoteAndEscape(key.key));
|
||||
keyNode.addParseSpanInfo(key.sourceSpan);
|
||||
return `${keyNode.print()}: ${value.print()}`;
|
||||
} else {
|
||||
return `...${value.print()}`;
|
||||
@@ -185,7 +186,7 @@ class TcbExprTranslator implements AstVisitor {
|
||||
} else if (ast.value === null) {
|
||||
node = new TcbExpr('null');
|
||||
} else if (typeof ast.value === 'string') {
|
||||
node = new TcbExpr(JSON.stringify(ast.value));
|
||||
node = new TcbExpr(quoteAndEscape(ast.value));
|
||||
} else if (typeof ast.value === 'number') {
|
||||
if (Number.isNaN(ast.value)) {
|
||||
node = new TcbExpr('NaN');
|
||||
|
||||
@@ -129,6 +129,13 @@ export function getStatementsBlock(expressions: TcbExpr[], singleLine = false):
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Wraps a string value in quotes and escapes relevant characters. */
|
||||
export function quoteAndEscape(value: string): string {
|
||||
// Passing the value through `JSON.stringify` automatically
|
||||
// escapes quotes and allows us to handle line breaks.
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
let tempPrinter: ts.Printer | null = null;
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import {DirectiveOwner, ParseSourceSpan, TmplAstHostElement} from '@angular/compiler';
|
||||
import {TcbOp} from './base';
|
||||
import {TcbExpr} from './codegen';
|
||||
import {quoteAndEscape, TcbExpr} from './codegen';
|
||||
import {Context} from './context';
|
||||
import type {Scope} from './scope';
|
||||
import {TypeCheckableDirectiveMeta} from '../../api';
|
||||
@@ -171,7 +171,7 @@ function tcbCallTypeCtor(
|
||||
// Construct an object literal containing each directive input.
|
||||
for (let i = 0; i < inputs.length; i++) {
|
||||
const input = inputs[i];
|
||||
const propertyName = input.field;
|
||||
const propertyName = quoteAndEscape(input.field);
|
||||
const isLast = i === inputs.length - 1;
|
||||
|
||||
if (input.type === 'binding') {
|
||||
@@ -182,13 +182,13 @@ function tcbCallTypeCtor(
|
||||
expr = unwrapWritableSignal(expr, tcb);
|
||||
}
|
||||
|
||||
const assignment = new TcbExpr(`"${propertyName}": ${expr.wrapForTypeChecker().print()}`);
|
||||
const assignment = new TcbExpr(`${propertyName}: ${expr.wrapForTypeChecker().print()}`);
|
||||
assignment.addParseSpanInfo(input.sourceSpan);
|
||||
literal += assignment.print();
|
||||
} else {
|
||||
// A type constructor is required to be called with all input properties, so any unset
|
||||
// inputs are simply assigned a value of type `any` to ignore them.
|
||||
literal += `"${propertyName}": 0 as any`;
|
||||
literal += `${propertyName}: 0 as any`;
|
||||
}
|
||||
|
||||
literal += `${isLast ? '' : ','} `;
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
TmplAstElement,
|
||||
} from '@angular/compiler';
|
||||
import {TcbOp} from './base';
|
||||
import {getStatementsBlock, TcbExpr} from './codegen';
|
||||
import {quoteAndEscape, getStatementsBlock, TcbExpr} from './codegen';
|
||||
import type {Context} from './context';
|
||||
import type {Scope} from './scope';
|
||||
import {TypeCheckableDirectiveMeta} from '../../api';
|
||||
@@ -94,9 +94,8 @@ export class TcbDirectiveOutputsOp extends TcbOp {
|
||||
if (dirId === null) {
|
||||
dirId = this.scope.resolve(this.node, this.dir);
|
||||
}
|
||||
const outputField = new TcbExpr(`${dirId.print()}["${field}"]`).addParseSpanInfo(
|
||||
output.keySpan,
|
||||
);
|
||||
const outputField = new TcbExpr(`${dirId.print()}[${quoteAndEscape(field)}]`);
|
||||
outputField.addParseSpanInfo(output.keySpan);
|
||||
|
||||
if (this.tcb.env.config.checkTypeOfOutputEvents) {
|
||||
// For strict checking of directive events, generate a call to the `subscribe` method
|
||||
@@ -239,7 +238,9 @@ export class TcbUnclaimedOutputsOp extends TcbOp {
|
||||
EventParamType.Infer,
|
||||
domEventAssertion,
|
||||
);
|
||||
const call = new TcbExpr(`${propertyAccess.print()}("${output.name}", ${handler.print()})`);
|
||||
const call = new TcbExpr(
|
||||
`${propertyAccess.print()}(${quoteAndEscape(output.name)}, ${handler.print()})`,
|
||||
);
|
||||
call.addParseSpanInfo(output.sourceSpan);
|
||||
this.scope.addStatement(call);
|
||||
} else {
|
||||
|
||||
@@ -22,7 +22,7 @@ import type {Context} from './context';
|
||||
import type {Scope} from './scope';
|
||||
import {TypeCheckableDirectiveMeta} from '../../api';
|
||||
import {TcbOp} from './base';
|
||||
import {declareVariable, TcbExpr, tempPrint} from './codegen';
|
||||
import {declareVariable, quoteAndEscape, TcbExpr, tempPrint} from './codegen';
|
||||
import {BindingPropertyName, ClassPropertyName} from '../../../metadata';
|
||||
import {REGISTRY} from '../dom';
|
||||
import {tcbExpression, unwrapWritableSignal} from './expression';
|
||||
@@ -41,7 +41,7 @@ import {LocalSymbol} from './references';
|
||||
export function translateInput(value: AST | string, tcb: Context, scope: Scope): TcbExpr {
|
||||
if (typeof value === 'string') {
|
||||
// For regular attributes with a static string value, use the represented string literal.
|
||||
return new TcbExpr(`"${value}"`);
|
||||
return new TcbExpr(quoteAndEscape(value));
|
||||
} else {
|
||||
// Produce an expression representing the value of the binding.
|
||||
return tcbExpression(value, tcb, scope);
|
||||
@@ -171,7 +171,7 @@ export class TcbDirectiveInputsOp extends TcbOp {
|
||||
`Expected TypeReferenceNode from reference to ${this.dir.ref.debugName}`,
|
||||
);
|
||||
}
|
||||
const type = new TcbExpr(`(typeof ${dirId.print()})["${fieldName}"]`);
|
||||
const type = new TcbExpr(`(typeof ${dirId.print()})[${quoteAndEscape(fieldName)}]`);
|
||||
const temp = declareVariable(id, type);
|
||||
this.scope.addStatement(temp);
|
||||
target = id;
|
||||
@@ -184,7 +184,7 @@ export class TcbDirectiveInputsOp extends TcbOp {
|
||||
// when possible. String literal fields may not be valid JS identifiers so we use
|
||||
// literal element access instead for those cases.
|
||||
target = this.dir.stringLiteralInputFields.has(fieldName)
|
||||
? new TcbExpr(`${dirId.print()}["${fieldName}"]`)
|
||||
? new TcbExpr(`${dirId.print()}[${quoteAndEscape(fieldName)}]`)
|
||||
: new TcbExpr(`${dirId.print()}.${fieldName}`);
|
||||
}
|
||||
|
||||
@@ -305,7 +305,7 @@ export class TcbUnclaimedInputsOp extends TcbOp {
|
||||
// A direct binding to a property.
|
||||
const propertyName = REGISTRY.getMappedPropName(binding.name);
|
||||
const stmt = new TcbExpr(
|
||||
`${elId.print()}["${propertyName}"] = ${expr.wrapForTypeChecker().print()}`,
|
||||
`${elId.print()}[${quoteAndEscape(propertyName)}] = ${expr.wrapForTypeChecker().print()}`,
|
||||
).addParseSpanInfo(binding.sourceSpan);
|
||||
this.scope.addStatement(stmt);
|
||||
} else {
|
||||
|
||||
@@ -14,7 +14,7 @@ import {TypeCtorMetadata} from '../api';
|
||||
|
||||
import {ReferenceEmitEnvironment} from './reference_emit_environment';
|
||||
import {checkIfGenericTypeBoundsCanBeEmitted} from './tcb_util';
|
||||
import {TcbExpr, tempPrint} from './ops/codegen';
|
||||
import {quoteAndEscape, TcbExpr, tempPrint} from './ops/codegen';
|
||||
|
||||
export function generateTypeCtorDeclarationFn(
|
||||
env: ReferenceEmitEnvironment,
|
||||
@@ -134,9 +134,9 @@ function constructTypeCtorParameter(
|
||||
|
||||
for (const {classPropertyName, transform, isSignal} of meta.fields.inputs) {
|
||||
if (isSignal) {
|
||||
signalInputKeys.push(`"${classPropertyName}"`);
|
||||
signalInputKeys.push(quoteAndEscape(classPropertyName));
|
||||
} else if (!meta.coercedInputFields.has(classPropertyName)) {
|
||||
plainKeys.push(`"${classPropertyName}"`);
|
||||
plainKeys.push(quoteAndEscape(classPropertyName));
|
||||
} else {
|
||||
const coercionType =
|
||||
transform != null
|
||||
|
||||
Reference in New Issue
Block a user