Skip to content

Commit

Permalink
v3.21.0
Browse files Browse the repository at this point in the history
  • Loading branch information
ajafff committed Mar 7, 2021
1 parent 1413977 commit 03b4df1
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 6 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,10 @@
# 3.21.0

**Features:**

* added `getWellKnownSymbolPropertyOfType` to reliably get symbol named properties due to changes in typescript@4.3
* `getPropertyNameOfWellKnownSymbol` is now deprecated

# 3.20.0

**Features:**
Expand Down
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "tsutils",
"version": "3.20.0",
"version": "3.21.0",
"description": "utilities for working with typescript's AST",
"scripts": {
"precompile": "rimraf \"{,util,typeguard,test{,/rules}/*.{js,d.ts,js.map}\"",
Expand Down
42 changes: 41 additions & 1 deletion util/type.ts
Expand Up @@ -30,6 +30,8 @@ import {
isShorthandPropertyAssignment,
isEnumMember,
isClassLikeDeclaration,
isInterfaceDeclaration,
isSourceFile,
} from '../typeguard/node';

export function isEmptyObjectType(type: ts.Type): type is ts.ObjectType {
Expand Down Expand Up @@ -205,6 +207,29 @@ export function getPropertyOfType(type: ts.Type, name: ts.__String) {
return type.getProperties().find((s) => s.escapedName === name);
}

export function getWellKnownSymbolPropertyOfType(type: ts.Type, wellKnownSymbolName: string, checker: ts.TypeChecker) {
const prefix = '__@' + wellKnownSymbolName;
for (const prop of type.getProperties()) {
if (!prop.name.startsWith(prefix))
continue;
const globalSymbol = checker.getApparentType(
checker.getTypeAtLocation((<ts.ComputedPropertyName>(<ts.NamedDeclaration>prop.valueDeclaration).name).expression),
).symbol;
if (prop.escapedName === getPropertyNameOfWellKnownSymbol(checker, globalSymbol, wellKnownSymbolName))
return prop;
}
return;
}

function getPropertyNameOfWellKnownSymbol(checker: ts.TypeChecker, symbolConstructor: ts.Symbol | undefined, symbolName: string) {
const knownSymbol = symbolConstructor &&
checker.getTypeOfSymbolAtLocation(symbolConstructor, symbolConstructor.valueDeclaration).getProperty(symbolName);
const knownSymbolType = knownSymbol && checker.getTypeOfSymbolAtLocation(knownSymbol, knownSymbol.valueDeclaration);
if (knownSymbolType && isUniqueESSymbolType(knownSymbolType))
return knownSymbolType.escapedName;
return <ts.__String>('__@' + symbolName);
}

/** Determines if writing to a certain property of a given type is allowed. */
export function isPropertyReadonlyInType(type: ts.Type, name: ts.__String, checker: ts.TypeChecker): boolean {
let seenProperty = false;
Expand Down Expand Up @@ -285,11 +310,26 @@ export function getPropertyNameFromType(type: ts.Type): PropertyName | undefined
}
if (isUniqueESSymbolType(type))
return {
displayName: `[${type.symbol ? type.symbol.name : (<string>type.escapedName).replace(/^__@|@\d+$/g, '')}]`,
displayName: `[${type.symbol
? `${isKnownSymbol(type.symbol) ? 'Symbol.' : ''}${type.symbol.name}`
: (<string>type.escapedName).replace(/^__@|@\d+$/g, '')
}]`,
symbolName: type.escapedName,
};
}

function isKnownSymbol(symbol: ts.Symbol): boolean {
return isSymbolFlagSet(symbol, ts.SymbolFlags.Property) &&
symbol.valueDeclaration !== undefined &&
isInterfaceDeclaration(symbol.valueDeclaration.parent) &&
symbol.valueDeclaration.parent.name.text === 'SymbolConstructor' &&
isGlobalDeclaration(symbol.valueDeclaration.parent);
}

function isGlobalDeclaration(node: ts.DeclarationStatement): boolean {
return isNodeFlagSet(node.parent!, ts.NodeFlags.GlobalAugmentation) || isSourceFile(node.parent) && !ts.isExternalModule(node.parent);
}

export function getSymbolOfClassLikeDeclaration(node: ts.ClassLikeDeclaration, checker: ts.TypeChecker) {
return checker.getSymbolAtLocation(node.name ?? getChildOfKind(node, ts.SyntaxKind.ClassKeyword)!)!;
}
Expand Down
11 changes: 7 additions & 4 deletions util/util.ts
Expand Up @@ -1647,13 +1647,16 @@ export interface PropertyName {
symbolName: ts.__String;
}

/** @deprecated typescript 4.3 removed the concept of literal well known symbols. Use `getPropertyNameFromType` instead. */
export function getPropertyNameOfWellKnownSymbol(node: WellKnownSymbolLiteral): PropertyName {
return {
displayName: `[Symbol.${node.name.text}]`,
symbolName: <ts.__String>('__@' + node.name.text),
};
}

const isTsBefore43 = (([major, minor]) => major < '4' || major === '4' && minor < '3')(ts.versionMajorMinor.split('.'));

export interface LateBoundPropertyNames {
/** Whether all constituents are literal names. */
known: boolean;
Expand All @@ -1667,8 +1670,8 @@ export function getLateBoundPropertyNames(node: ts.Expression, checker: ts.TypeC
};

node = unwrapParentheses(node);
if (isWellKnownSymbolLiterally(node)) {
result.names.push(getPropertyNameOfWellKnownSymbol(node));
if (isTsBefore43 && isWellKnownSymbolLiterally(node)) {
result.names.push(getPropertyNameOfWellKnownSymbol(node)); // wotan-disable-line no-unstable-api-use
} else {
const type = checker.getTypeAtLocation(node);
for (const key of unionTypeParts(checker.getBaseConstraintOfType(type) || type)) {
Expand Down Expand Up @@ -1700,8 +1703,8 @@ export function getSingleLateBoundPropertyNameOfPropertyName(node: ts.PropertyNa
if (node.kind === ts.SyntaxKind.PrivateIdentifier)
return {displayName: node.text, symbolName: checker.getSymbolAtLocation(node)!.escapedName};
const {expression} = <ts.ComputedPropertyName>node;
return isWellKnownSymbolLiterally(expression)
? getPropertyNameOfWellKnownSymbol(expression)
return isTsBefore43 && isWellKnownSymbolLiterally(expression)
? getPropertyNameOfWellKnownSymbol(expression) // wotan-disable-line no-unstable-api-use
: getPropertyNameFromType(checker.getTypeAtLocation(expression));
}

Expand Down

0 comments on commit 03b4df1

Please sign in to comment.