-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Readonly support for jsdoc #35790
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
Readonly support for jsdoc #35790
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would be true inside any method, but we’ve already ensured we’re in a constructor 👍 |
||
const isLocalThisPropertyAssignmentConstructorFunction = isAssignmentDeclaration && symbol.parent?.valueDeclaration === ctor; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This one is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep. Any suggestions for making the variable name clearer? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. - isLocalThisPropertyAssignmentConstructorFunction
+ isLocalThisPropertyAssignmentInConstructorFunction ? |
||
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!; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
tests/cases/conformance/jsdoc/jsdocReadonly.js(23,3): error TS2540: Cannot assign to 'y' because it is a read-only property. | ||
|
||
|
||
==== tests/cases/conformance/jsdoc/jsdocReadonly.js (1 errors) ==== | ||
class LOL { | ||
/** | ||
* @readonly | ||
* @private | ||
* @type {number} | ||
* Order rules do not apply to JSDoc | ||
*/ | ||
x = 1 | ||
/** @readonly */ | ||
y = 2 | ||
/** @readonly Definitely not here */ | ||
static z = 3 | ||
/** @readonly This is OK too */ | ||
constructor() { | ||
/** ok */ | ||
this.y = 2 | ||
/** @readonly ok */ | ||
this.ka = 2 | ||
} | ||
} | ||
|
||
var l = new LOL() | ||
l.y = 12 | ||
~ | ||
!!! error TS2540: Cannot assign to 'y' because it is a read-only property. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I collapsed these three checks into one function because they are always called together.