Skip to content

parse private fields - separate syntax kind for PrivateName #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
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
10 changes: 5 additions & 5 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1387,7 +1387,7 @@ namespace ts {
}
if (node.expression.kind === SyntaxKind.PropertyAccessExpression) {
const propertyAccess = <PropertyAccessExpression>node.expression;
if (isNarrowableOperand(propertyAccess.expression) && isPushOrUnshiftIdentifier(propertyAccess.name)) {
if (isIdentifier(propertyAccess.name) && isNarrowableOperand(propertyAccess.expression) && isPushOrUnshiftIdentifier(propertyAccess.name)) {
currentFlow = createFlowArrayMutation(currentFlow, node);
}
}
Expand Down Expand Up @@ -2326,7 +2326,7 @@ namespace ts {
return;
}
const lhs = node.left as PropertyAccessEntityNameExpression;
const symbol = forEachIdentifierInEntityName(lhs.expression, /*parent*/ undefined, (id, symbol) => {
const symbol = forEachIdentifierOrPrivateNameInEntityName(lhs.expression, /*parent*/ undefined, (id, symbol) => {
if (symbol) {
addDeclarationToSymbol(symbol, id, SymbolFlags.Module | SymbolFlags.JSContainer);
}
Expand Down Expand Up @@ -2480,7 +2480,7 @@ namespace ts {
// make symbols or add declarations for intermediate containers
const flags = SymbolFlags.Module | SymbolFlags.JSContainer;
const excludeFlags = SymbolFlags.ValueModuleExcludes & ~SymbolFlags.JSContainer;
namespaceSymbol = forEachIdentifierInEntityName(propertyAccess.expression, namespaceSymbol, (id, symbol, parent) => {
namespaceSymbol = forEachIdentifierOrPrivateNameInEntityName(propertyAccess.expression, namespaceSymbol, (id, symbol, parent) => {
if (symbol) {
addDeclarationToSymbol(symbol, id, flags);
return symbol;
Expand Down Expand Up @@ -2552,15 +2552,15 @@ namespace ts {
}
}

function forEachIdentifierInEntityName(e: EntityNameExpression, parent: Symbol | undefined, action: (e: Identifier, symbol: Symbol | undefined, parent: Symbol | undefined) => Symbol | undefined): Symbol | undefined {
function forEachIdentifierOrPrivateNameInEntityName(e: EntityNameExpression, parent: Symbol | undefined, action: (e: Identifier | PrivateName, symbol: Symbol | undefined, parent: Symbol | undefined) => Symbol | undefined): Symbol | undefined {
if (isExportsOrModuleExportsOrAlias(file, e)) {
return file.symbol;
}
else if (isIdentifier(e)) {
return action(e, lookupSymbolForPropertyAccess(e), parent);
}
else {
const s = forEachIdentifierInEntityName(e.expression, parent, action);
const s = forEachIdentifierOrPrivateNameInEntityName(e.expression, parent, action);
if (!s || !s.exports) return Debug.fail();
return action(e.name, s.exports.get(e.name.escapedText), s);
}
Expand Down
26 changes: 17 additions & 9 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13755,8 +13755,10 @@ namespace ts {
const root = getReferenceRoot(node);
const parent = root.parent;
const isLengthPushOrUnshift = parent.kind === SyntaxKind.PropertyAccessExpression && (
(<PropertyAccessExpression>parent).name.escapedText === "length" ||
parent.parent.kind === SyntaxKind.CallExpression && isPushOrUnshiftIdentifier((<PropertyAccessExpression>parent).name));
(<PropertyAccessExpression>parent).name.escapedText === "length" || (
parent.parent.kind === SyntaxKind.CallExpression
&& isIdentifier((parent as PropertyAccessExpression).name)
&& isPushOrUnshiftIdentifier((parent as PropertyAccessExpression).name)));
const isElementAssignment = parent.kind === SyntaxKind.ElementAccessExpression &&
(<ElementAccessExpression>parent).expression === root &&
parent.parent.kind === SyntaxKind.BinaryExpression &&
Expand Down Expand Up @@ -17381,7 +17383,7 @@ namespace ts {
return checkPropertyAccessExpressionOrQualifiedName(node, node.left, node.right);
}

function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, right: Identifier) {
function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, right: Identifier | PrivateName) {
let propType: Type;
const leftType = checkNonNullExpression(left);
const parentSymbol = getNodeLinks(left).resolvedSymbol;
Expand Down Expand Up @@ -17454,7 +17456,7 @@ namespace ts {
return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType;
}

function checkPropertyNotUsedBeforeDeclaration(prop: Symbol, node: PropertyAccessExpression | QualifiedName, right: Identifier): void {
function checkPropertyNotUsedBeforeDeclaration(prop: Symbol, node: PropertyAccessExpression | QualifiedName, right: Identifier | PrivateName): void {
const { valueDeclaration } = prop;
if (!valueDeclaration) {
return;
Expand Down Expand Up @@ -17516,7 +17518,7 @@ namespace ts {
return getIntersectionType(x);
}

function reportNonexistentProperty(propNode: Identifier, containingType: Type) {
function reportNonexistentProperty(propNode: Identifier | PrivateName, containingType: Type) {
let errorInfo: DiagnosticMessageChain | undefined;
if (containingType.flags & TypeFlags.Union && !(containingType.flags & TypeFlags.Primitive)) {
for (const subtype of (containingType as UnionType).types) {
Expand Down Expand Up @@ -17549,7 +17551,7 @@ namespace ts {
diagnostics.add(createDiagnosticForNodeFromMessageChain(propNode, errorInfo));
}

function getSuggestionForNonexistentProperty(name: Identifier | string, containingType: Type): string | undefined {
function getSuggestionForNonexistentProperty(name: Identifier | PrivateName | string, containingType: Type): string | undefined {
const suggestion = getSpellingSuggestionForName(isString(name) ? name : idText(name), getPropertiesOfType(containingType), SymbolFlags.Value);
return suggestion && symbolName(suggestion);
}
Expand Down Expand Up @@ -21341,6 +21343,9 @@ namespace ts {
checkGrammarDecoratorsAndModifiers(node);

checkVariableLikeDeclaration(node);
if (node.name && isIdentifier(node.name) && node.name.originalKeywordKind === SyntaxKind.PrivateName) {
error(node, Diagnostics.Private_names_cannot_be_used_as_parameters);
}
const func = getContainingFunction(node)!;
if (hasModifier(node, ModifierFlags.ParameterPropertyModifier)) {
if (!(func.kind === SyntaxKind.Constructor && nodeIsPresent(func.body))) {
Expand Down Expand Up @@ -22985,9 +22990,9 @@ namespace ts {
}
}

function getIdentifierFromEntityNameExpression(node: Identifier | PropertyAccessExpression): Identifier;
function getIdentifierFromEntityNameExpression(node: Expression): Identifier | undefined;
function getIdentifierFromEntityNameExpression(node: Expression): Identifier | undefined {
function getIdentifierFromEntityNameExpression(node: Identifier | PropertyAccessExpression): Identifier | PrivateName;
function getIdentifierFromEntityNameExpression(node: Expression): Identifier | PrivateName | undefined;
function getIdentifierFromEntityNameExpression(node: Expression): Identifier | PrivateName | undefined {
switch (node.kind) {
case SyntaxKind.Identifier:
return node as Identifier;
Expand Down Expand Up @@ -28723,6 +28728,9 @@ namespace ts {
if (name.originalKeywordKind === SyntaxKind.LetKeyword) {
return grammarErrorOnNode(name, Diagnostics.let_is_not_allowed_to_be_used_as_a_name_in_let_or_const_declarations);
}
if (name.originalKeywordKind === SyntaxKind.PrivateName) {
return grammarErrorOnNode(name, Diagnostics.Private_names_are_not_allowed_outside_class_bodies);
}
}
else {
const elements = name.elements;
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -4093,6 +4093,14 @@
"category": "Error",
"code": 18003
},
"Private names are not allowed outside class bodies.": {
"category": "Error",
"code": 18004
},
"Private names cannot be used as parameters": {
"category": "Error",
"code": 18005
},

"File is a CommonJS module; it may be converted to an ES6 module.": {
"category": "Suggestion",
Expand Down
16 changes: 15 additions & 1 deletion src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,10 @@ namespace ts {
case SyntaxKind.Identifier:
return emitIdentifier(<Identifier>node);

// PrivateNames
case SyntaxKind.PrivateName:
return emitPrivateName(node as PrivateName);

// Parse tree nodes

// Names
Expand Down Expand Up @@ -872,6 +876,10 @@ namespace ts {
case SyntaxKind.Identifier:
return emitIdentifier(<Identifier>node);

// Private Names
case SyntaxKind.PrivateName:
return emitPrivateName(node as PrivateName);

// Reserved words
case SyntaxKind.FalseKeyword:
case SyntaxKind.NullKeyword:
Expand Down Expand Up @@ -1061,6 +1069,12 @@ namespace ts {
emitList(node, node.typeArguments, ListFormat.TypeParameters); // Call emitList directly since it could be an array of TypeParameterDeclarations _or_ type arguments
}

function emitPrivateName(node: PrivateName) {
const writeText = node.symbol ? writeSymbol : write;
writeText(getTextOfNode(node, /*includeTrivia*/ false), node.symbol);
emitList(node, /*typeArguments*/ undefined, ListFormat.TypeParameters); // Call emitList directly since it could be an array of TypeParameterDeclarations _or_ type arguments
}

//
// Names
//
Expand Down Expand Up @@ -3296,7 +3310,7 @@ namespace ts {
function getLiteralTextOfNode(node: LiteralLikeNode): string {
if (node.kind === SyntaxKind.StringLiteral && (<StringLiteral>node).textSourceNode) {
const textSourceNode = (<StringLiteral>node).textSourceNode!;
if (isIdentifier(textSourceNode)) {
if (isIdentifierOrPrivateName(textSourceNode)) {
return getEmitFlags(node) & EmitFlags.NoAsciiEscaping ?
`"${escapeString(getTextOfNode(textSourceNode))}"` :
`"${escapeNonAsciiString(getTextOfNode(textSourceNode))}"`;
Expand Down
20 changes: 12 additions & 8 deletions src/compiler/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,13 @@ namespace ts {

// Literals

/* @internal */ export function createLiteral(value: string | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | Identifier, isSingleQuote: boolean): StringLiteral; // tslint:disable-line unified-signatures
/* @internal */ export function createLiteral(value: string | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | Identifier | PrivateName, isSingleQuote: boolean): StringLiteral; // tslint:disable-line unified-signatures
/** If a node is passed, creates a string literal whose source text is read from a source node during emit. */
export function createLiteral(value: string | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | Identifier): StringLiteral;
export function createLiteral(value: string | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | Identifier | PrivateName): StringLiteral;
export function createLiteral(value: number): NumericLiteral;
export function createLiteral(value: boolean): BooleanLiteral;
export function createLiteral(value: string | number | boolean): PrimaryExpression;
export function createLiteral(value: string | number | boolean | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | Identifier, isSingleQuote?: boolean): PrimaryExpression {
export function createLiteral(value: string | number | boolean | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | Identifier | PrivateName, isSingleQuote?: boolean): PrimaryExpression {
if (typeof value === "number") {
return createNumericLiteral(value + "");
}
Expand Down Expand Up @@ -138,6 +138,10 @@ namespace ts {
: node;
}

export function updatePrivateName(node: PrivateName): PrivateName {
return node;
}

let nextAutoGenerateId = 0;

/** Create a unique temporary variable. */
Expand Down Expand Up @@ -996,15 +1000,15 @@ namespace ts {
: node;
}

export function createPropertyAccess(expression: Expression, name: string | Identifier | undefined) {
export function createPropertyAccess(expression: Expression, name: string | Identifier | PrivateName | undefined) {
const node = <PropertyAccessExpression>createSynthesizedNode(SyntaxKind.PropertyAccessExpression);
node.expression = parenthesizeForAccess(expression);
node.name = asName(name)!; // TODO: GH#18217
setEmitFlags(node, EmitFlags.NoIndentation);
return node;
}

export function updatePropertyAccess(node: PropertyAccessExpression, expression: Expression, name: Identifier) {
export function updatePropertyAccess(node: PropertyAccessExpression, expression: Expression, name: Identifier | PrivateName) {
// Because we are updating existed propertyAccess we want to inherit its emitFlags
// instead of using the default from createPropertyAccess
return node.expression !== expression
Expand Down Expand Up @@ -2755,7 +2759,7 @@ namespace ts {

// Utilities

function asName<T extends Identifier | BindingName | PropertyName | EntityName | ThisTypeNode | undefined>(name: string | T): T | Identifier {
function asName<T extends Identifier | PrivateName | BindingName | PropertyName | EntityName | ThisTypeNode | undefined>(name: string | T): T | Identifier {
return isString(name) ? createIdentifier(name) : name;
}

Expand Down Expand Up @@ -3140,7 +3144,7 @@ namespace ts {
}
else {
const expression = setTextRange(
isIdentifier(memberName)
(isIdentifier(memberName) || isPrivateName(memberName))
? createPropertyAccess(target, memberName)
: createElementAccess(target, memberName),
memberName
Expand Down Expand Up @@ -3571,7 +3575,7 @@ namespace ts {
}

export function createExpressionForPropertyName(memberName: PropertyName): Expression {
if (isIdentifier(memberName)) {
if (isIdentifier(memberName) || isPrivateName(memberName)) {
return createLiteral(memberName);
}
else if (isComputedPropertyName(memberName)) {
Expand Down
29 changes: 24 additions & 5 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1363,6 +1363,9 @@ namespace ts {
if (allowComputedPropertyNames && token() === SyntaxKind.OpenBracketToken) {
return parseComputedPropertyName();
}
if (token() === SyntaxKind.PrivateName) {
return parsePrivateName();
}
return parseIdentifierName();
}

Expand All @@ -1386,6 +1389,17 @@ namespace ts {
return finishNode(node);
}

function createPrivateName(): PrivateName {
const node = createNode(SyntaxKind.PrivateName) as PrivateName;
node.escapedText = escapeLeadingUnderscores(scanner.getTokenText());
nextToken();
return finishNode(node);
}

function parsePrivateName(): PrivateName {
return createPrivateName();
}

function parseContextualModifier(t: SyntaxKind): boolean {
return token() === t && tryParse(nextTokenCanFollowModifier);
}
Expand Down Expand Up @@ -2114,7 +2128,7 @@ namespace ts {
break;
}
dotPos = scanner.getStartPos();
entity = createQualifiedName(entity, parseRightSideOfDot(allowReservedWords));
entity = createQualifiedName(entity, parseRightSideOfDot(allowReservedWords, /* allowPrivateNames */ false) as Identifier);
}
return entity;
}
Expand All @@ -2126,7 +2140,7 @@ namespace ts {
return finishNode(node);
}

function parseRightSideOfDot(allowIdentifierNames: boolean): Identifier {
function parseRightSideOfDot(allowIdentifierNames: boolean, allowPrivateNames: boolean): Identifier | PrivateName {
// Technically a keyword is valid here as all identifiers and keywords are identifier names.
// However, often we'll encounter this in error situations when the identifier or keyword
// is actually starting another valid construct.
Expand Down Expand Up @@ -2157,6 +2171,10 @@ namespace ts {
}
}

if (allowPrivateNames && token() === SyntaxKind.PrivateName) {
return parsePrivateName();
}

return allowIdentifierNames ? parseIdentifierName() : parseIdentifier();
}

Expand Down Expand Up @@ -4148,7 +4166,8 @@ namespace ts {
const node = <PropertyAccessExpression>createNode(SyntaxKind.PropertyAccessExpression, expression.pos);
node.expression = expression;
parseExpectedToken(SyntaxKind.DotToken, Diagnostics.super_must_be_followed_by_an_argument_list_or_member_access);
node.name = parseRightSideOfDot(/*allowIdentifierNames*/ true);
// private names will never work with `super` (`super.#foo`), but that's a semantic error, not syntactic
node.name = parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateNames*/ true);
return finishNode(node);
}

Expand Down Expand Up @@ -4318,7 +4337,7 @@ namespace ts {
while (parseOptional(SyntaxKind.DotToken)) {
const propertyAccess: JsxTagNamePropertyAccess = <JsxTagNamePropertyAccess>createNode(SyntaxKind.PropertyAccessExpression, expression.pos);
propertyAccess.expression = expression;
propertyAccess.name = parseRightSideOfDot(/*allowIdentifierNames*/ true);
propertyAccess.name = parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateNames*/ true);
expression = finishNode(propertyAccess);
}
return expression;
Expand Down Expand Up @@ -4421,7 +4440,7 @@ namespace ts {
if (dotToken) {
const propertyAccess = <PropertyAccessExpression>createNode(SyntaxKind.PropertyAccessExpression, expression.pos);
propertyAccess.expression = expression;
propertyAccess.name = parseRightSideOfDot(/*allowIdentifierNames*/ true);
propertyAccess.name = parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateNames*/ true);
expression = finishNode(propertyAccess);
continue;
}
Expand Down
Loading