Skip to content

Commit

Permalink
fix(type-utils): check IndexSignature internals when checking isTypeR…
Browse files Browse the repository at this point in the history
…eadonly

fix #3714
  • Loading branch information
RebeccaStevens committed Jan 9, 2022
1 parent 3e4f170 commit bb98b9e
Showing 1 changed file with 61 additions and 47 deletions.
108 changes: 61 additions & 47 deletions packages/type-utils/src/isTypeReadonly.ts
Expand Up @@ -107,62 +107,76 @@ function isTypeReadonlyObject(
function checkIndexSignature(kind: ts.IndexKind): Readonlyness {
const indexInfo = checker.getIndexInfoOfType(type, kind);
if (indexInfo) {
return indexInfo.isReadonly
? Readonlyness.Readonly
: Readonlyness.Mutable;
if (!indexInfo.isReadonly) {
return Readonlyness.Mutable;
}

return checkProperties(indexInfo.type);
}

return Readonlyness.UnknownType;
}

const properties = type.getProperties();
if (properties.length) {
// ensure the properties are marked as readonly
for (const property of properties) {
if (
!(
isPropertyReadonlyInType(type, property.getEscapedName(), checker) ||
(options.treatMethodsAsReadonly &&
property.valueDeclaration !== undefined &&
hasSymbol(property.valueDeclaration) &&
isSymbolFlagSet(
property.valueDeclaration.symbol,
ts.SymbolFlags.Method,
))
)
) {
return Readonlyness.Mutable;
function checkProperties(type: ts.Type): Readonlyness {
const properties = type.getProperties();
if (properties.length) {
// ensure the properties are marked as readonly
for (const property of properties) {
if (
!(
isPropertyReadonlyInType(
type,
property.getEscapedName(),
checker,
) ||
(options.treatMethodsAsReadonly &&
property.valueDeclaration !== undefined &&
hasSymbol(property.valueDeclaration) &&
isSymbolFlagSet(
property.valueDeclaration.symbol,
ts.SymbolFlags.Method,
))
)
) {
return Readonlyness.Mutable;
}
}
}

// all properties were readonly
// now ensure that all of the values are readonly also.

// do this after checking property readonly-ness as a perf optimization,
// as we might be able to bail out early due to a mutable property before
// doing this deep, potentially expensive check.
for (const property of properties) {
const propertyType = ESLintUtils.nullThrows(
getTypeOfPropertyOfType(checker, type, property),
ESLintUtils.NullThrowsReasons.MissingToken(
`property "${property.name}"`,
'type',
),
);

// handle recursive types.
// we only need this simple check, because a mutable recursive type will break via the above prop readonly check
if (seenTypes.has(propertyType)) {
continue;
}

if (
isTypeReadonlyRecurser(checker, propertyType, options, seenTypes) ===
Readonlyness.Mutable
) {
return Readonlyness.Mutable;
// all properties were readonly
// now ensure that all of the values are readonly also.

// do this after checking property readonly-ness as a perf optimization,
// as we might be able to bail out early due to a mutable property before
// doing this deep, potentially expensive check.
for (const property of properties) {
const propertyType = ESLintUtils.nullThrows(
getTypeOfPropertyOfType(checker, type, property),
ESLintUtils.NullThrowsReasons.MissingToken(
`property "${property.name}"`,
'type',
),
);

// handle recursive types.
// we only need this simple check, because a mutable recursive type will break via the above prop readonly check
if (seenTypes.has(propertyType)) {
continue;
}

if (
isTypeReadonlyRecurser(checker, propertyType, options, seenTypes) ===
Readonlyness.Mutable
) {
return Readonlyness.Mutable;
}
}
}
return Readonlyness.UnknownType;
}

const isProperties = checkProperties(type);
if (isProperties === Readonlyness.Mutable) {
return isProperties;
}

const isStringIndexSigReadonly = checkIndexSignature(ts.IndexKind.String);
Expand Down

0 comments on commit bb98b9e

Please sign in to comment.