@nestjs/graphql
Version:
Nest - modern, fast, powerful node.js web framework (@graphql)
220 lines (219 loc) • 8.1 kB
JavaScript
import { head } from 'es-toolkit';
import { isAbsolute, posix } from 'path';
import * as ts from 'typescript';
import { getText, getTypeArguments, isArray, isBigInt, isBoolean, isEnum, isInterface, isNumber, isString, isStringLiteral, } from './ast-utils.js';
export function getTypeReferenceAsString(type, typeChecker, arrayDepth = 0) {
if (isArray(type)) {
const arrayType = getTypeArguments(type)[0];
const { typeName, arrayDepth: depth } = getTypeReferenceAsString(arrayType, typeChecker, arrayDepth + 1);
if (!typeName) {
return { typeName: undefined };
}
return {
typeName: `${typeName}`,
isArray: true,
arrayDepth: depth,
};
}
if (isBoolean(type)) {
return { typeName: Boolean.name, arrayDepth };
}
if (isNumber(type) || isBigInt(type)) {
return { typeName: Number.name, arrayDepth };
}
if (isString(type) || isStringLiteral(type)) {
return { typeName: String.name, arrayDepth };
}
if (isPromiseOrObservable(getText(type, typeChecker))) {
const typeArguments = getTypeArguments(type);
const elementType = getTypeReferenceAsString(head(typeArguments), typeChecker, arrayDepth);
return elementType;
}
if (type.isClass()) {
return { typeName: getText(type, typeChecker), arrayDepth };
}
try {
const text = getText(type, typeChecker);
if (text === Date.name) {
return { typeName: text, arrayDepth };
}
if (isOptionalBoolean(text)) {
return { typeName: Boolean.name, arrayDepth };
}
if (isEnum(type)) {
return { typeName: text, arrayDepth };
}
const isEnumMember = type.symbol && type.symbol.flags === ts.SymbolFlags.EnumMember;
if (isEnumMember) {
type = typeChecker.getDeclaredTypeOfSymbol(type.symbol.parent);
if (!type) {
return undefined;
}
return { typeName: text, arrayDepth };
}
if (isAutoGeneratedTypeUnion(type) ||
isAutoGeneratedEnumUnion(type, typeChecker)) {
const types = type.types;
return getTypeReferenceAsString(types[types.length - 1], typeChecker);
}
if (text === 'any' ||
text === 'unknown' ||
text === 'object' ||
isInterface(type) ||
(type.isUnionOrIntersection() && !isEnum(type))) {
return { typeName: 'Object', arrayDepth };
}
if (type.aliasSymbol) {
return { typeName: 'Object', arrayDepth };
}
return { typeName: 'Object', arrayDepth };
}
catch {
return { typeName: 'Object', arrayDepth };
}
}
export function isPromiseOrObservable(type) {
return type.includes('Promise') || type.includes('Observable');
}
export function replaceImportPath(typeReference, fileName, options) {
if (!typeReference.includes('import')) {
return { typeReference, importPath: null };
}
let importPath = /\("([^)]).+(")/.exec(typeReference)[0];
if (!importPath) {
return { typeReference: undefined, importPath: null };
}
importPath = convertPath(importPath);
importPath = importPath.slice(2, importPath.length - 1);
const from = options?.readonly
? convertPath(options.pathToSource)
: posix.dirname(convertPath(fileName));
let relativePath = posix.relative(from, importPath);
relativePath =
!isAbsolute(relativePath) && relativePath[0] !== '.'
? './' + relativePath
: relativePath;
const nodeModulesText = 'node_modules';
const nodeModulePos = relativePath.indexOf(nodeModulesText);
if (nodeModulePos >= 0) {
relativePath = relativePath.slice(nodeModulePos + nodeModulesText.length + 1);
const typesText = '@types';
const typesPos = relativePath.indexOf(typesText);
if (typesPos >= 0) {
relativePath = relativePath.slice(typesPos + typesText.length + 1);
}
const indexText = '/index';
const indexPos = relativePath.indexOf(indexText);
if (indexPos >= 0) {
relativePath = relativePath.slice(0, indexPos);
}
}
typeReference = typeReference.replace(importPath, relativePath);
if (options.readonly) {
const { typeName, typeImportStatement } = convertToAsyncImport(typeReference);
return {
typeReference: typeImportStatement,
typeName,
importPath: relativePath,
};
}
return {
typeReference: typeReference.replace('import', 'require'),
importPath: relativePath,
};
}
function convertToAsyncImport(typeReference) {
const regexp = /import\(.+\).([^\]]+)(\])?/;
const match = regexp.exec(typeReference);
if (match?.length >= 2) {
const importPos = typeReference.indexOf(match[0]);
typeReference = typeReference.replace(`.${match[1]}`, '');
return {
typeImportStatement: insertAt(typeReference, importPos, 'await '),
typeName: match[1],
};
}
return { typeImportStatement: typeReference };
}
export function insertAt(string, index, substring) {
return string.slice(0, index) + substring + string.slice(index);
}
export function isDynamicallyAdded(identifier) {
return identifier && !identifier.parent && identifier.pos === -1;
}
/**
* when "strict" mode enabled, TypeScript transform the enum type to a union composed of
* the enum values and the undefined type. Hence, we have to lookup all the union types to get the original type
* @param type
* @param typeChecker
*/
export function isAutoGeneratedEnumUnion(type, typeChecker) {
if (type.isUnionOrIntersection() && !isEnum(type)) {
if (!type.types) {
return undefined;
}
const undefinedTypeIndex = type.types.findIndex((type) => type.intrinsicName === 'undefined');
if (undefinedTypeIndex < 0) {
return undefined;
}
// "strict" mode for enums
let parentType = undefined;
const isParentSymbolEqual = type.types.every((item, index) => {
if (index === undefinedTypeIndex) {
return true;
}
if (!item.symbol) {
return false;
}
if (!item.symbol.parent ||
item.symbol.flags !== ts.SymbolFlags.EnumMember) {
return false;
}
const symbolType = typeChecker.getDeclaredTypeOfSymbol(item.symbol.parent);
if (symbolType === parentType || !parentType) {
parentType = symbolType;
return true;
}
return false;
});
if (isParentSymbolEqual) {
return parentType;
}
}
return undefined;
}
/**
* when "strict" mode enabled, TypeScript transform the type signature of optional properties to
* the {undefined | T} where T is the original type. Hence, we have to extract the last type of type union
* @param type
*/
export function isAutoGeneratedTypeUnion(type) {
if (type.isUnionOrIntersection() && !isEnum(type)) {
if (!type.types) {
return false;
}
const undefinedTypeIndex = type.types.findIndex((type) => type.intrinsicName === 'undefined');
// "strict" mode for non-enum properties
if (type.types.length === 2 && undefinedTypeIndex >= 0) {
return true;
}
}
return false;
}
/**
* when "strict" mode enabled, TypeScript transform optional boolean properties to "boolean | undefined"
* @param text
*/
function isOptionalBoolean(text) {
return typeof text === 'string' && text === 'boolean | undefined';
}
/**
* Converts Windows specific file paths to posix
* @param windowsPath
*/
export function convertPath(windowsPath) {
return windowsPath
.replace(/^\\\\\?\\/, '')
.replace(/\\/g, '/')
.replace(/\/\/+/g, '/');
}