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 Sep 6, 2021
1 parent 0be9903 commit 8622ca7
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 42 deletions.
16 changes: 13 additions & 3 deletions packages/experimental-utils/package.json
Expand Up @@ -44,13 +44,23 @@
"@typescript-eslint/types": "4.30.0",
"@typescript-eslint/typescript-estree": "4.30.0",
"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
75 changes: 45 additions & 30 deletions packages/experimental-utils/src/eslint-utils/isTypeReadonly.ts
@@ -1,12 +1,8 @@
import {
isObjectType,
isUnionType,
isUnionOrIntersectionType,
unionTypeParts,
isPropertyReadonlyInType,
isSymbolFlagSet,
} from 'tsutils';
import * as ts from 'typescript';
import assert from 'assert';
import type * as tsTypes from 'typescript';

import { ts, tsutils } from '../optional-dependencies';

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

const enum Readonlyness {
Expand Down Expand Up @@ -36,17 +32,19 @@ export const readonlynessOptionsDefaults: ReadonlynessOptions = {
treatMethodsAsReadonly: false,
};

function hasSymbol(node: ts.Node): node is ts.Node & { symbol: ts.Symbol } {
function hasSymbol(
node: tsTypes.Node,
): node is tsTypes.Node & { symbol: tsTypes.Symbol } {
return Object.prototype.hasOwnProperty.call(node, 'symbol');
}

function isTypeReadonlyArrayOrTuple(
checker: ts.TypeChecker,
type: ts.Type,
checker: tsTypes.TypeChecker,
type: tsTypes.Type,
options: ReadonlynessOptions,
seenTypes: Set<ts.Type>,
seenTypes: Set<tsTypes.Type>,
): Readonlyness {
function checkTypeArguments(arrayType: ts.TypeReference): Readonlyness {
function checkTypeArguments(arrayType: tsTypes.TypeReference): Readonlyness {
const typeArguments =
// getTypeArguments was only added in TS3.7
checker.getTypeArguments
Expand Down Expand Up @@ -98,12 +96,15 @@ function isTypeReadonlyArrayOrTuple(
}

function isTypeReadonlyObject(
checker: ts.TypeChecker,
type: ts.Type,
checker: tsTypes.TypeChecker,
type: tsTypes.Type,
options: ReadonlynessOptions,
seenTypes: Set<ts.Type>,
seenTypes: Set<tsTypes.Type>,
): Readonlyness {
function checkIndexSignature(kind: ts.IndexKind): Readonlyness {
assert(!(ts instanceof Error));
assert(!(tsutils instanceof Error));

function checkIndexSignature(kind: tsTypes.IndexKind): Readonlyness {
const indexInfo = checker.getIndexInfoOfType(type, kind);
if (indexInfo) {
return indexInfo.isReadonly
Expand All @@ -120,11 +121,15 @@ function isTypeReadonlyObject(
for (const property of properties) {
if (
!(
isPropertyReadonlyInType(type, property.getEscapedName(), checker) ||
!tsutils.isPropertyReadonlyInType(
type,
property.getEscapedName(),
checker,
) ||
(options.treatMethodsAsReadonly &&
property.valueDeclaration !== undefined &&
hasSymbol(property.valueDeclaration) &&
isSymbolFlagSet(
tsutils.isSymbolFlagSet(
property.valueDeclaration.symbol,
ts.SymbolFlags.Method,
))
Expand Down Expand Up @@ -176,25 +181,28 @@ function isTypeReadonlyObject(

// a helper function to ensure the seenTypes map is always passed down, except by the external caller
function isTypeReadonlyRecurser(
checker: ts.TypeChecker,
type: ts.Type,
checker: tsTypes.TypeChecker,
type: tsTypes.Type,
options: ReadonlynessOptions,
seenTypes: Set<ts.Type>,
seenTypes: Set<tsTypes.Type>,
): Readonlyness.Readonly | Readonlyness.Mutable {
assert(!(ts instanceof Error));
assert(!(tsutils instanceof Error));

seenTypes.add(type);

if (isUnionType(type)) {
if (tsutils.isUnionType(type)) {
// all types in the union must be readonly
const result = unionTypeParts(type).every(t =>
isTypeReadonlyRecurser(checker, t, options, seenTypes),
);
const result = tsutils
.unionTypeParts(type)
.every(t => isTypeReadonlyRecurser(checker, t, options, 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 (!tsutils.isObjectType(type) && !tsutils.isUnionOrIntersectionType(type)) {
return Readonlyness.Readonly;
}

Expand Down Expand Up @@ -235,10 +243,17 @@ function isTypeReadonlyRecurser(
* Checks if the given type is readonly
*/
function isTypeReadonly(
checker: ts.TypeChecker,
type: ts.Type,
checker: tsTypes.TypeChecker,
type: tsTypes.Type,
options: ReadonlynessOptions,
): boolean {
if (ts instanceof Error) {
throw ts;
}
if (tsutils instanceof Error) {
throw tsutils;
}

return (
isTypeReadonlyRecurser(checker, type, options, new Set()) ===
Readonlyness.Readonly
Expand Down
18 changes: 9 additions & 9 deletions packages/experimental-utils/src/eslint-utils/propertyTypes.ts
@@ -1,11 +1,11 @@
import * as ts from 'typescript';
import type * as tsType from 'typescript';

export function getTypeOfPropertyOfName(
checker: ts.TypeChecker,
type: ts.Type,
checker: tsType.TypeChecker,
type: tsType.Type,
name: string,
escapedName?: ts.__String,
): ts.Type | undefined {
escapedName?: tsType.__String,
): tsType.Type | undefined {
// Most names are directly usable in the checker and aren't different from escaped names
if (!escapedName || !name.startsWith('__')) {
return checker.getTypeOfPropertyOfType(type, name);
Expand All @@ -23,10 +23,10 @@ export function getTypeOfPropertyOfName(
}

export function getTypeOfPropertyOfType(
checker: ts.TypeChecker,
type: ts.Type,
property: ts.Symbol,
): ts.Type | undefined {
checker: tsType.TypeChecker,
type: tsType.Type,
property: tsType.Symbol,
): tsType.Type | undefined {
return getTypeOfPropertyOfName(
checker,
type,
Expand Down
@@ -0,0 +1,2 @@
export * from './tsutils';
export * from './typescript';
12 changes: 12 additions & 0 deletions packages/experimental-utils/src/optional-dependencies/tsutils.ts
@@ -0,0 +1,12 @@
import type * as tsutilsType from 'tsutils';

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

export { tsutils };
@@ -0,0 +1,12 @@
import type * as tsType from 'typescript';

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

export { ts };

0 comments on commit 8622ca7

Please sign in to comment.