Skip to content

Commit

Permalink
fix: conditionally load typescript and tsutils
Browse files Browse the repository at this point in the history
  • Loading branch information
RebeccaStevens committed Aug 1, 2021
1 parent 0424cad commit 441d55b
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 20 deletions.
16 changes: 13 additions & 3 deletions packages/experimental-utils/package.json
Expand Up @@ -44,13 +44,23 @@
"@typescript-eslint/types": "4.28.5",
"@typescript-eslint/typescript-estree": "4.28.5",
"eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0",
"tsutils": "^3.21.0"
"eslint-utils": "^3.0.0"
},
"peerDependencies": {
"eslint": "*"
"eslint": "*",
"tsutils": "^3.21.0",
"typescript": "^3.4.1 || ^4.0.0"
},
"peerDependenciesMeta": {
"tsutils": {
"optional": true
},
"typescript": {
"optional": true
}
},
"devDependencies": {
"tsutils": "*",
"typescript": "*"
},
"funding": {
Expand Down
12 changes: 12 additions & 0 deletions packages/experimental-utils/src/conditional-imports/tsutils.ts
@@ -0,0 +1,12 @@
import type * as tsutils from 'tsutils';

// Conditionally loaded tsutils but only if it is available.
const conditionalTsutils = ((): typeof tsutils | Error => {
try {
return require('tsutils') as typeof tsutils;
} catch {
return new Error('Cannot find local tsutils peer depenancy.');
}
})();

export { conditionalTsutils };
12 changes: 12 additions & 0 deletions packages/experimental-utils/src/conditional-imports/typescript.ts
@@ -0,0 +1,12 @@
import type * as ts from 'typescript';

// Conditionally loaded TypeScript but only if it is available.
const conditionalTypeScript = ((): typeof ts | Error => {
try {
return require('typescript') as typeof ts;
} catch {
return new Error('Cannot find local typescript peer depenancy.');
}
})();

export { conditionalTypeScript };
56 changes: 40 additions & 16 deletions packages/experimental-utils/src/eslint-utils/isTypeReadonly.ts
@@ -1,11 +1,9 @@
import {
isObjectType,
isUnionType,
isUnionOrIntersectionType,
unionTypeParts,
isPropertyReadonlyInType,
} from 'tsutils';
import * as ts from 'typescript';
import assert from 'assert';
import type * as ts from 'typescript';

import { conditionalTypeScript } from '../conditional-imports/typescript';
import { conditionalTsutils } from '../conditional-imports/tsutils';

import { getTypeOfPropertyOfType, nullThrows, NullThrowsReasons } from '.';

const enum Readonlyness {
Expand Down Expand Up @@ -73,6 +71,9 @@ function isTypeReadonlyObject(
type: ts.Type,
seenTypes: Set<ts.Type>,
): Readonlyness {
assert(!(conditionalTypeScript instanceof Error));
assert(!(conditionalTsutils instanceof Error));

function checkIndexSignature(kind: ts.IndexKind): Readonlyness {
const indexInfo = checker.getIndexInfoOfType(type, kind);
if (indexInfo) {
Expand All @@ -88,7 +89,13 @@ function isTypeReadonlyObject(
if (properties.length) {
// ensure the properties are marked as readonly
for (const property of properties) {
if (!isPropertyReadonlyInType(type, property.getEscapedName(), checker)) {
if (
!conditionalTsutils.isPropertyReadonlyInType(
type,
property.getEscapedName(),
checker,
)
) {
return Readonlyness.Mutable;
}
}
Expand Down Expand Up @@ -120,12 +127,16 @@ function isTypeReadonlyObject(
}
}

const isStringIndexSigReadonly = checkIndexSignature(ts.IndexKind.String);
const isStringIndexSigReadonly = checkIndexSignature(
conditionalTypeScript.IndexKind.String,
);
if (isStringIndexSigReadonly === Readonlyness.Mutable) {
return isStringIndexSigReadonly;
}

const isNumberIndexSigReadonly = checkIndexSignature(ts.IndexKind.Number);
const isNumberIndexSigReadonly = checkIndexSignature(
conditionalTypeScript.IndexKind.Number,
);
if (isNumberIndexSigReadonly === Readonlyness.Mutable) {
return isNumberIndexSigReadonly;
}
Expand All @@ -139,20 +150,26 @@ function isTypeReadonlyRecurser(
type: ts.Type,
seenTypes: Set<ts.Type>,
): Readonlyness.Readonly | Readonlyness.Mutable {
assert(!(conditionalTypeScript instanceof Error));
assert(!(conditionalTsutils instanceof Error));

seenTypes.add(type);

if (isUnionType(type)) {
if (conditionalTsutils.isUnionType(type)) {
// all types in the union must be readonly
const result = unionTypeParts(type).every(t =>
isTypeReadonlyRecurser(checker, t, seenTypes),
);
const result = conditionalTsutils
.unionTypeParts(type)
.every(t => isTypeReadonlyRecurser(checker, t, seenTypes));
const readonlyness = result ? Readonlyness.Readonly : Readonlyness.Mutable;
return readonlyness;
}

// all non-object, non-intersection types are readonly.
// this should only be primitive types
if (!isObjectType(type) && !isUnionOrIntersectionType(type)) {
if (
!conditionalTsutils.isObjectType(type) &&
!conditionalTsutils.isUnionOrIntersectionType(type)
) {
return Readonlyness.Readonly;
}

Expand Down Expand Up @@ -183,6 +200,13 @@ function isTypeReadonlyRecurser(
* Checks if the given type is readonly
*/
function isTypeReadonly(checker: ts.TypeChecker, type: ts.Type): boolean {
if (conditionalTypeScript instanceof Error) {
throw conditionalTypeScript;
}
if (conditionalTsutils instanceof Error) {
throw conditionalTsutils;
}

return (
isTypeReadonlyRecurser(checker, type, new Set()) === Readonlyness.Readonly
);
Expand Down
@@ -1,4 +1,4 @@
import * as ts from 'typescript';
import type * as ts from 'typescript';

export function getTypeOfPropertyOfName(
checker: ts.TypeChecker,
Expand Down

0 comments on commit 441d55b

Please sign in to comment.