mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-28 09:22:42 -05:00
Readonly support for jsdoc (#35790)
* Add @readonly The rule for @readonly on this-assignments in the constructor is wrong. See failing tests. * In-progress Add ctor function test Add some notes and rename variable * Done except for cleanup and fix 1 bug * Fix last test and clean up
This commit is contained in:
committed by
GitHub
parent
d96be353cb
commit
3d2b92ce69
@@ -11964,7 +11964,7 @@ namespace ts {
|
||||
if (prop) {
|
||||
if (accessExpression) {
|
||||
markPropertyAsReferenced(prop, accessExpression, /*isThisAccess*/ accessExpression.expression.kind === SyntaxKind.ThisKeyword);
|
||||
if (isAssignmentTarget(accessExpression) && (isReferenceToReadonlyEntity(accessExpression, prop) || isReferenceThroughNamespaceImport(accessExpression))) {
|
||||
if (isAssignmentToReadonlyEntity(accessExpression, prop, getAssignmentTargetKind(accessExpression))) {
|
||||
error(accessExpression.argumentExpression, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, symbolToString(prop));
|
||||
return undefined;
|
||||
}
|
||||
@@ -23046,11 +23046,9 @@ namespace ts {
|
||||
markPropertyAsReferenced(prop, node, left.kind === SyntaxKind.ThisKeyword);
|
||||
getNodeLinks(node).resolvedSymbol = prop;
|
||||
checkPropertyAccessibility(node, left.kind === SyntaxKind.SuperKeyword, apparentType, prop);
|
||||
if (assignmentKind) {
|
||||
if (isReferenceToReadonlyEntity(<Expression>node, prop) || isReferenceThroughNamespaceImport(<Expression>node)) {
|
||||
error(right, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, idText(right));
|
||||
return errorType;
|
||||
}
|
||||
if (isAssignmentToReadonlyEntity(node as Expression, prop, assignmentKind)) {
|
||||
error(right, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, idText(right));
|
||||
return errorType;
|
||||
}
|
||||
propType = getConstraintForLocation(getTypeOfSymbol(prop), node);
|
||||
}
|
||||
@@ -26332,29 +26330,39 @@ namespace ts {
|
||||
);
|
||||
}
|
||||
|
||||
function isReferenceToReadonlyEntity(expr: Expression, symbol: Symbol): boolean {
|
||||
function isAssignmentToReadonlyEntity(expr: Expression, symbol: Symbol, assignmentKind: AssignmentKind) {
|
||||
if (assignmentKind === AssignmentKind.None) {
|
||||
// no assigment means it doesn't matter whether the entity is readonly
|
||||
return false;
|
||||
}
|
||||
if (isReadonlySymbol(symbol)) {
|
||||
// Allow assignments to readonly properties within constructors of the same class declaration.
|
||||
if (symbol.flags & SymbolFlags.Property &&
|
||||
(expr.kind === SyntaxKind.PropertyAccessExpression || expr.kind === SyntaxKind.ElementAccessExpression) &&
|
||||
(expr as AccessExpression).expression.kind === SyntaxKind.ThisKeyword) {
|
||||
// Look for if this is the constructor for the class that `symbol` is a property of.
|
||||
const func = getContainingFunction(expr);
|
||||
if (!(func && func.kind === SyntaxKind.Constructor)) {
|
||||
const ctor = getContainingFunction(expr);
|
||||
if (!(ctor && ctor.kind === SyntaxKind.Constructor)) {
|
||||
return true;
|
||||
}
|
||||
// If func.parent is a class and symbol is a (readonly) property of that class, or
|
||||
// if func is a constructor and symbol is a (readonly) parameter property declared in it,
|
||||
// then symbol is writeable here.
|
||||
return !symbol.valueDeclaration || !(func.parent === symbol.valueDeclaration.parent || func === symbol.valueDeclaration.parent);
|
||||
if (symbol.valueDeclaration) {
|
||||
const isAssignmentDeclaration = isBinaryExpression(symbol.valueDeclaration);
|
||||
const isLocalPropertyDeclaration = ctor.parent === symbol.valueDeclaration.parent;
|
||||
const isLocalParameterProperty = ctor === symbol.valueDeclaration.parent;
|
||||
const isLocalThisPropertyAssignment = isAssignmentDeclaration && symbol.parent?.valueDeclaration === ctor.parent;
|
||||
const isLocalThisPropertyAssignmentConstructorFunction = isAssignmentDeclaration && symbol.parent?.valueDeclaration === ctor;
|
||||
const isWriteableSymbol =
|
||||
isLocalPropertyDeclaration
|
||||
|| isLocalParameterProperty
|
||||
|| isLocalThisPropertyAssignment
|
||||
|| isLocalThisPropertyAssignmentConstructorFunction;
|
||||
return !isWriteableSymbol;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isReferenceThroughNamespaceImport(expr: Expression): boolean {
|
||||
if (expr.kind === SyntaxKind.PropertyAccessExpression || expr.kind === SyntaxKind.ElementAccessExpression) {
|
||||
// references through namespace import should be readonly
|
||||
const node = skipParentheses((expr as AccessExpression).expression);
|
||||
if (node.kind === SyntaxKind.Identifier) {
|
||||
const symbol = getNodeLinks(node).resolvedSymbol!;
|
||||
|
||||
@@ -509,6 +509,7 @@ namespace ts {
|
||||
case SyntaxKind.JSDocPublicTag:
|
||||
case SyntaxKind.JSDocPrivateTag:
|
||||
case SyntaxKind.JSDocProtectedTag:
|
||||
case SyntaxKind.JSDocReadonlyTag:
|
||||
return visitNode(cbNode, (node as JSDocTag).tagName);
|
||||
case SyntaxKind.PartiallyEmittedExpression:
|
||||
return visitNode(cbNode, (<PartiallyEmittedExpression>node).expression);
|
||||
@@ -6845,6 +6846,9 @@ namespace ts {
|
||||
case "protected":
|
||||
tag = parseSimpleTag(start, SyntaxKind.JSDocProtectedTag, tagName);
|
||||
break;
|
||||
case "readonly":
|
||||
tag = parseSimpleTag(start, SyntaxKind.JSDocReadonlyTag, tagName);
|
||||
break;
|
||||
case "this":
|
||||
tag = parseThisTag(start, tagName);
|
||||
break;
|
||||
|
||||
@@ -471,6 +471,7 @@ namespace ts {
|
||||
JSDocPublicTag,
|
||||
JSDocPrivateTag,
|
||||
JSDocProtectedTag,
|
||||
JSDocReadonlyTag,
|
||||
JSDocCallbackTag,
|
||||
JSDocEnumTag,
|
||||
JSDocParameterTag,
|
||||
@@ -2632,6 +2633,10 @@ namespace ts {
|
||||
kind: SyntaxKind.JSDocProtectedTag;
|
||||
}
|
||||
|
||||
export interface JSDocReadonlyTag extends JSDocTag {
|
||||
kind: SyntaxKind.JSDocReadonlyTag;
|
||||
}
|
||||
|
||||
export interface JSDocEnumTag extends JSDocTag, Declaration {
|
||||
parent: JSDoc;
|
||||
kind: SyntaxKind.JSDocEnumTag;
|
||||
|
||||
@@ -4130,7 +4130,8 @@ namespace ts {
|
||||
// or when !(node.flags & NodeFlags.Synthesized) && node.kind !== SyntaxKind.SourceFile)
|
||||
const tags = (getJSDocPublicTag(node) ? ModifierFlags.Public : ModifierFlags.None)
|
||||
| (getJSDocPrivateTag(node) ? ModifierFlags.Private : ModifierFlags.None)
|
||||
| (getJSDocProtectedTag(node) ? ModifierFlags.Protected : ModifierFlags.None);
|
||||
| (getJSDocProtectedTag(node) ? ModifierFlags.Protected : ModifierFlags.None)
|
||||
| (getJSDocReadonlyTag(node) ? ModifierFlags.Readonly : ModifierFlags.None);
|
||||
flags |= tags;
|
||||
}
|
||||
|
||||
|
||||
@@ -688,6 +688,11 @@ namespace ts {
|
||||
return getFirstJSDocTag(node, isJSDocProtectedTag);
|
||||
}
|
||||
|
||||
/** Gets the JSDoc protected tag for the node if present */
|
||||
export function getJSDocReadonlyTag(node: Node): JSDocReadonlyTag | undefined {
|
||||
return getFirstJSDocTag(node, isJSDocReadonlyTag);
|
||||
}
|
||||
|
||||
/** Gets the JSDoc enum tag for the node if present */
|
||||
export function getJSDocEnumTag(node: Node): JSDocEnumTag | undefined {
|
||||
return getFirstJSDocTag(node, isJSDocEnumTag);
|
||||
@@ -1574,6 +1579,10 @@ namespace ts {
|
||||
return node.kind === SyntaxKind.JSDocProtectedTag;
|
||||
}
|
||||
|
||||
export function isJSDocReadonlyTag(node: Node): node is JSDocReadonlyTag {
|
||||
return node.kind === SyntaxKind.JSDocReadonlyTag;
|
||||
}
|
||||
|
||||
export function isJSDocEnumTag(node: Node): node is JSDocEnumTag {
|
||||
return node.kind === SyntaxKind.JSDocEnumTag;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user