diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index e70339ae1271f..7a8f905a9d245 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -4470,5 +4470,9 @@ "Add all missing enum members": { "category": "Message", "code": 95064 + }, + "Private names are not allowed outside of class bodies": { + "category": "Error", + "code": 95065 } } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index bfc502ce2da1e..7a27b626fd152 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1320,15 +1320,22 @@ namespace ts { // An identifier that starts with two underscores has an extra underscore character prepended to it to avoid issues // with magic property names like '__proto__'. The 'identifiers' object is used to share a single string instance for // each identifier in order to reduce memory consumption. - function createIdentifier(isIdentifier: boolean, diagnosticMessage?: DiagnosticMessage): Identifier { + function createIdentifier(isIdentifier: boolean, suggestedDiagnosticMessage?: DiagnosticMessage): Identifier { identifierCount++; - if (isIdentifier) { + let diagnosticMessage = suggestedDiagnosticMessage; + const isPrivateName = !!(scanner.getTokenFlags() & TokenFlags.PrivateName); + const privateNameIsAllowed = !!(parsingContext & (1 << ParsingContext.ClassMembers)); + if (isPrivateName && !privateNameIsAllowed) { + diagnosticMessage = Diagnostics.Private_names_are_not_allowed_outside_of_class_bodies; + } + else if (isIdentifier && (!isPrivateName || privateNameIsAllowed)) { const node = createNode(SyntaxKind.Identifier); // Store original token kind if it is not just an Identifier so we can report appropriate error later in type checker if (token() !== SyntaxKind.Identifier) { node.originalKeywordKind = token(); } + node.isPrivateName = isPrivateName; node.escapedText = escapeLeadingUnderscores(internIdentifier(scanner.getTokenValue())); nextToken(); return finishNode(node); diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index af74d69ceefbb..5df5a2a66902e 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -1723,6 +1723,21 @@ namespace ts { error(Diagnostics.Invalid_character); pos++; return token = SyntaxKind.Unknown; + case CharacterCodes.hash: + pos++; + if (isIdentifierStart(ch = text.charCodeAt(pos), languageVersion)) { + tokenFlags |= TokenFlags.PrivateName; + pos++; + while (pos < end && isIdentifierPart(ch = text.charCodeAt(pos), languageVersion)) pos++; + tokenValue = text.substring(tokenPos, pos); + if (ch === CharacterCodes.backslash) { + tokenValue += scanIdentifierParts(); + } + return token = SyntaxKind.Identifier; + } + error(Diagnostics.Invalid_character); + // no `pos++` because already advanced at beginning of this `case` statement + return token = SyntaxKind.Unknown; default: if (isIdentifierStart(ch, languageVersion)) { pos++; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 85efeb96ea5d7..ff8257ddd3962 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -683,6 +683,7 @@ namespace ts { */ escapedText: __String; originalKeywordKind?: SyntaxKind; // Original syntaxKind which get set so that we can report an error later + isPrivateName: boolean; // is private name like `this.#foo` /*@internal*/ autoGenerateFlags?: GeneratedIdentifierFlags; // Specifies whether to auto-generate the text for an identifier. /*@internal*/ autoGenerateId?: number; // Ensures unique generated identifiers get unique names, but clones get the same name. isInJSDocNamespace?: boolean; // if the node is a member in a JSDoc namespace @@ -1574,6 +1575,7 @@ namespace ts { BinarySpecifier = 1 << 7, // e.g. `0b0110010000000000` OctalSpecifier = 1 << 8, // e.g. `0o777` ContainsSeparator = 1 << 9, // e.g. `0b1100_0101` + PrivateName = 1 << 10, // e.g. `#foo` BinaryOrOctalSpecifier = BinarySpecifier | OctalSpecifier, NumericLiteralFlags = Scientific | Octal | HexSpecifier | BinarySpecifier | OctalSpecifier | ContainsSeparator } diff --git a/src/services/services.ts b/src/services/services.ts index b2a2fdaaae21e..b9771e977ffac 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -347,6 +347,7 @@ namespace ts { public kind: SyntaxKind.Identifier; public escapedText: __String; public symbol: Symbol; + public isPrivateName: boolean; public autoGenerateFlags: GeneratedIdentifierFlags; _primaryExpressionBrand: any; _memberExpressionBrand: any;