UNPKG

ts-transformer-properties-rename

Version:

A TypeScript custom transformer which renames internal properties of the project

416 lines (415 loc) 21.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = propertiesRenameTransformer; const ts = require("typescript"); const exports_symbol_tree_1 = require("./exports-symbol-tree"); const typescript_helpers_1 = require("./typescript-helpers"); const defaultOptions = { entrySourceFiles: [], privatePrefix: '_private_', internalPrefix: '_internal_', publicJSDocTag: 'public', ignoreDecorated: false, }; // eslint-disable-next-line import/no-default-export function propertiesRenameTransformer(program, config) { return createTransformerFactory(program, config); } function createTransformerFactory(program, options) { const fullOptions = { ...defaultOptions, ...options }; const typeChecker = program.getTypeChecker(); const exportsSymbolTree = new exports_symbol_tree_1.ExportsSymbolTree(program, fullOptions.entrySourceFiles); const cache = new Map(); function putToCache(nodeSymbol, val) { cache.set(nodeSymbol, val); return val; } return (context) => { function transformNodeAndChildren(node, ctx) { return ts.visitEachChild(transformNode(node), (childNode) => transformNodeAndChildren(childNode, ctx), ctx); } // eslint-disable-next-line complexity function transformNode(node) { // const a = { node } if (ts.isShorthandPropertyAssignment(node)) { return handleShorthandPropertyAssignment(node); } // const { node } = obj; if (ts.isBindingElement(node) && node.propertyName === undefined && ts.isObjectBindingPattern(node.parent)) { return handleShorthandObjectBindingElement(node); } // 'node' in obj if (ts.isStringLiteral(node) && ts.isBinaryExpression(node.parent) && node.parent.operatorToken.kind === ts.SyntaxKind.InKeyword) { return handleInKeyword(node); } if (ts.isIdentifier(node) && node.parent) { // obj.node if (ts.isPropertyAccessExpression(node.parent) && node.parent.name === node) { return handlePropertyAccessIdentifier(node); } // private node // public node() if ((0, typescript_helpers_1.isClassMember)(node.parent) && node.parent.name === node) { return handleClassMember(node); } // enum Enum { node } if (ts.isEnumMember(node.parent) && node.parent.name === node) { return handleEnumMember(node); } // const a = { node: 123 } if (ts.isPropertyAssignment(node.parent) && node.parent.name === node) { return handlePropertyAssignment(node); } // const { node: localName } = obj; if (ts.isBindingElement(node.parent) && node.parent.propertyName === node) { return handleBindingElement(node); } // <Comp node={...} /> if (ts.isJsxAttribute(node.parent) && node.parent.name === node) { return handleJsxAttributeName(node); } // constructor(public node: string) { // <--- this // console.log(node); // <--- and this // } if ((0, typescript_helpers_1.isConstructorParameter)(node.parent) && node.parent.name === node || isConstructorParameterReference(node)) { return handleCtorParameter(node); } } // obj['fooBar'] if (ts.isStringLiteral(node) && ts.isElementAccessExpression(node.parent) && node.parent.argumentExpression === node) { return handleElementAccessExpression(node); } return node; } function handleInKeyword(node) { const parent = node.parent; const nodeType = typeChecker.getTypeAtLocation(node); if (!nodeType.isStringLiteral()) { throw new Error(`Can't get type for left expression in ${node.parent.getText()}`); } const propertyName = nodeType.value; if (isTypePropertyExternal(typeChecker.getTypeAtLocation(parent.right), propertyName)) { return node; } return createNewNode(propertyName, 0 /* VisibilityType.Internal */, context.factory.createStringLiteral); } // obj.node function handlePropertyAccessIdentifier(node) { return createNewIdentifier(node); } // obj['fooBar'] function handleElementAccessExpression(node) { const visibilityType = getNodeVisibilityType(node); if (visibilityType === 2 /* VisibilityType.External */) { return node; } return createNewNodeFromProperty(node, visibilityType, context.factory.createStringLiteral); } // private node // public node() function handleClassMember(node) { return createNewIdentifier(node); } // enum Enum { node } function handleEnumMember(node) { return createNewIdentifier(node); } // const { node: localName } = obj; function handleBindingElement(node) { return createNewIdentifier(node); } // <Comp node={...} /> function handleJsxAttributeName(node) { return createNewIdentifier(node); } // const a = { node: 123 } function handlePropertyAssignment(node) { return createNewIdentifier(node); } // const a = { node } function handleShorthandPropertyAssignment(node) { const visibilityType = getNodeVisibilityType(node.name); if (visibilityType === 2 /* VisibilityType.External */) { return node; } return createNewNodeFromProperty(node.name, visibilityType, (newName) => { return context.factory.createPropertyAssignment(newName, node.name); }); } // const { node } = obj; function handleShorthandObjectBindingElement(node) { if (!ts.isIdentifier(node.name)) { return node; } const visibilityType = getNodeVisibilityType(node); if (visibilityType === 2 /* VisibilityType.External */) { return node; } return createNewNodeFromProperty(node.name, visibilityType, (newName) => { return context.factory.createBindingElement(node.dotDotDotToken, newName, node.name, node.initializer); }); } // constructor(public node: string) { // <--- this // console.log(node); // <--- and this // } function handleCtorParameter(node) { return createNewIdentifier(node); } function createNewIdentifier(oldIdentifier) { const visibilityType = getNodeVisibilityType(oldIdentifier); if (visibilityType === 2 /* VisibilityType.External */) { return oldIdentifier; } return createNewNodeFromProperty(oldIdentifier, visibilityType, context.factory.createIdentifier); } function createNewNodeFromProperty(oldProperty, type, createNode) { const symbol = typeChecker.getSymbolAtLocation(oldProperty); if (symbol === undefined) { throw new Error(`Cannot get symbol for node "${oldProperty.getText()}"`); } const oldPropertyName = ts.unescapeLeadingUnderscores(symbol.escapedName); return createNewNode(oldPropertyName, type, createNode); } function createNewNode(oldPropertyName, type, createNode) { const newPropertyName = getNewName(oldPropertyName, type); return createNode(newPropertyName); } function getNewName(originalName, type) { return `${type === 1 /* VisibilityType.Private */ ? fullOptions.privatePrefix : fullOptions.internalPrefix}${originalName}`; } function getActualSymbol(symbol) { if (symbol.flags & ts.SymbolFlags.Alias) { symbol = typeChecker.getAliasedSymbol(symbol); } return symbol; } function getNodeSymbol(node) { const symbol = typeChecker.getSymbolAtLocation(node); if (symbol === undefined) { return null; } return getActualSymbol(symbol); } // eslint-disable-next-line complexity function isTypePropertyExternal(type, typePropertyName) { // if a type is unknown or any - they should be interpret as a public ones if (type.flags & ts.TypeFlags.Unknown || type.flags & ts.TypeFlags.Any) { return true; } if (type.flags & ts.TypeFlags.IndexedAccess) { return isTypePropertyExternal(typeChecker.getApparentType(type), typePropertyName); } const symbol = type.getSymbol(); const propertySymbol = typeChecker.getPropertyOfType(type, typePropertyName); if (type.flags & ts.TypeFlags.Object) { const objectType = type; // treat any tuple property as "external" if (objectType.objectFlags & ts.ObjectFlags.Tuple) { return true; } if (objectType.objectFlags & ts.ObjectFlags.Reference) { const target = objectType.target; if (target !== objectType && isTypePropertyExternal(target, typePropertyName)) { return true; } } // in case when we can't get where a property come from in mapped types // let's check the whole type explicitly // thus in case of when property doesn't have a declaration let's treat any property of a mapped type as "external" if its parent type is external // e.g. Readonly<Foo>.field will look for `field` in _Foo_ type (not in Readonly<Foo>), but { [K in 'foo' | 'bar']: any } won't // perhaps it would be awesome to handle exactly property we have, but ¯\_(ツ)_/¯ const propertyHasDeclarations = propertySymbol !== undefined ? (0, typescript_helpers_1.getDeclarationsForSymbol)(propertySymbol).length !== 0 : false; if ((objectType.objectFlags & ts.ObjectFlags.Mapped) && symbol !== undefined && !propertyHasDeclarations && getSymbolVisibilityType(symbol) === 2 /* VisibilityType.External */) { return true; } } if (type.isUnionOrIntersection()) { const hasExternalSubType = type.types.some((t) => isTypePropertyExternal(t, typePropertyName)); if (hasExternalSubType) { return hasExternalSubType; } } if (symbol !== undefined) { const declarations = (0, typescript_helpers_1.getDeclarationsForSymbol)(symbol); for (const declaration of declarations) { if ((ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration)) && declaration.heritageClauses !== undefined) { const hasHeritageClausesExternals = declaration.heritageClauses.some((clause) => { return clause.types.some((expr) => { return isTypePropertyExternal(typeChecker.getTypeAtLocation(expr), typePropertyName); }); }); if (hasHeritageClausesExternals) { return true; } } } } if (propertySymbol === undefined) { return false; } return [propertySymbol, ...(0, typescript_helpers_1.splitTransientSymbol)(propertySymbol, typeChecker)] .some((sym) => getSymbolVisibilityType(sym) === 2 /* VisibilityType.External */); } // eslint-disable-next-line complexity function getNodeVisibilityType(node) { if (ts.isPropertyAssignment(node.parent) || ts.isShorthandPropertyAssignment(node.parent)) { let expressionToGetTypeFrom = node.parent.parent; let lastKnownExpression = node.parent.parent; let currentNode = node.parent.parent.parent; while (ts.isParenthesizedExpression(currentNode) || ts.isAsExpression(currentNode)) { if (ts.isAsExpression(currentNode)) { expressionToGetTypeFrom = lastKnownExpression; lastKnownExpression = currentNode; } currentNode = currentNode.parent; } // to get correct contextual type we need to provide the previous last expression rather than the last one const type = typeChecker.getContextualType(expressionToGetTypeFrom); if (type !== undefined && isTypePropertyExternal(type, node.getText())) { return 2 /* VisibilityType.External */; } } if (ts.isPropertyAccessExpression(node.parent) || ts.isElementAccessExpression(node.parent)) { if (ts.isIdentifier(node.parent.expression)) { const expressionSymbol = typeChecker.getSymbolAtLocation(node.parent.expression); if (expressionSymbol !== undefined) { // import * as foo from '...'; // foo.node; // foo['node']; // or // namespace Foo { ... } // Foo.node // Foo['node']; const isModuleOrStarImport = (0, typescript_helpers_1.getDeclarationsForSymbol)(expressionSymbol) .some((decl) => ts.isNamespaceImport(decl) || ts.isModuleDeclaration(decl)); if (isModuleOrStarImport) { // treat accessing to import-star or namespace as external one // because we can't rename them yet return 2 /* VisibilityType.External */; } } } if (isTypePropertyExternal(typeChecker.getTypeAtLocation(node.parent.expression), node.getText())) { return 2 /* VisibilityType.External */; } } // shorthand binding element // const { node } = obj; if (ts.isBindingElement(node) && isTypePropertyExternal(typeChecker.getTypeAtLocation(node.parent), node.getText())) { return 2 /* VisibilityType.External */; } // full binding element // const { node: propName } = obj; if (ts.isBindingElement(node.parent) && isTypePropertyExternal(typeChecker.getTypeAtLocation(node.parent.parent), node.getText())) { return 2 /* VisibilityType.External */; } // <Comp node={...} /> if (ts.isJsxAttribute(node.parent)) { const jsxTagSymbol = typeChecker.getSymbolAtLocation(node.parent.parent.parent.tagName); if (jsxTagSymbol !== undefined && jsxTagSymbol.valueDeclaration !== undefined) { const jsxPropsType = typeChecker.getTypeOfSymbolAtLocation(jsxTagSymbol, jsxTagSymbol.valueDeclaration); if (isTypePropertyExternal(jsxPropsType, node.getText())) { return 2 /* VisibilityType.External */; } } } const nodeSymbol = getNodeSymbol(node); const classOfMember = nodeSymbol !== null ? (0, typescript_helpers_1.getClassOfMemberSymbol)(nodeSymbol) : null; if (classOfMember !== null && isTypePropertyExternal(typeChecker.getTypeAtLocation(classOfMember), node.getText())) { return 2 /* VisibilityType.External */; } const symbol = ts.isBindingElement(node) ? getShorthandObjectBindingElementSymbol(node) : nodeSymbol; if (symbol === null) { return 2 /* VisibilityType.External */; } return getSymbolVisibilityType(symbol); } // eslint-disable-next-line complexity function getSymbolVisibilityType(nodeSymbol) { nodeSymbol = getActualSymbol(nodeSymbol); const cachedValue = cache.get(nodeSymbol); if (cachedValue !== undefined) { return cachedValue; } const symbolDeclarations = (0, typescript_helpers_1.getDeclarationsForSymbol)(nodeSymbol); if (symbolDeclarations.some(isDeclarationFromExternals)) { return putToCache(nodeSymbol, 2 /* VisibilityType.External */); } if (symbolDeclarations.some((decl) => (0, typescript_helpers_1.hasModifier)(decl, ts.SyntaxKind.DeclareKeyword))) { return putToCache(nodeSymbol, 2 /* VisibilityType.External */); } if (nodeSymbol.escapedName === 'prototype') { // accessing to prototype return putToCache(nodeSymbol, 2 /* VisibilityType.External */); } if (fullOptions.publicJSDocTag.length !== 0 || fullOptions.ignoreDecorated) { for (const declaration of symbolDeclarations) { let currentNode = declaration; while (!ts.isSourceFile(currentNode)) { if (fullOptions.publicJSDocTag.length !== 0 && (0, typescript_helpers_1.getNodeJSDocComment)(currentNode).includes(`@${fullOptions.publicJSDocTag}`)) { return putToCache(nodeSymbol, 2 /* VisibilityType.External */); } if (fullOptions.ignoreDecorated && (0, typescript_helpers_1.hasDecorators)(currentNode)) { return putToCache(nodeSymbol, 2 /* VisibilityType.External */); } currentNode = currentNode.parent; } } } if ((0, typescript_helpers_1.isPrivateClassMember)(nodeSymbol)) { return putToCache(nodeSymbol, 1 /* VisibilityType.Private */); } if (exportsSymbolTree.isSymbolAccessibleFromExports(nodeSymbol)) { return putToCache(nodeSymbol, 2 /* VisibilityType.External */); } for (const declaration of symbolDeclarations) { if (!(0, typescript_helpers_1.isNodeNamedDeclaration)(declaration.parent) || declaration.parent.name === undefined) { continue; } const parentSymbol = getNodeSymbol(declaration.parent.name); if (parentSymbol === null) { continue; } if (getSymbolVisibilityType(parentSymbol) === 2 /* VisibilityType.External */) { return putToCache(nodeSymbol, 2 /* VisibilityType.External */); } } return putToCache(nodeSymbol, 0 /* VisibilityType.Internal */); } function getShorthandObjectBindingElementSymbol(element) { if (element.propertyName !== undefined) { throw new Error(`Cannot handle binding element with property name: ${element.getText()} in ${element.getSourceFile().fileName}`); } if (!ts.isIdentifier(element.name)) { return null; } // if no property name is set (const { a } = foo) // then node.propertyName is undefined and we need to find this property ourselves // so let's use go-to-definition algorithm from TSServer // see https://github.com/microsoft/TypeScript/blob/672b0e3e16ad18b422dbe0cec5a98fce49881b76/src/services/goToDefinition.ts#L58-L77 const type = typeChecker.getTypeAtLocation(element.parent); if (type.isUnion()) { return null; } return type.getProperty(ts.idText(element.name)) || null; } function isDeclarationFromExternals(declaration) { const sourceFile = declaration.getSourceFile(); // all declarations from declaration source files are external by default return sourceFile.isDeclarationFile || program.isSourceFileDefaultLibrary(sourceFile) || /[\\/]node_modules[\\/]/.test(sourceFile.fileName); } function isConstructorParameterReference(node) { if (!ts.isIdentifier(node)) { return false; } return (0, typescript_helpers_1.isSymbolClassMember)(typeChecker.getSymbolAtLocation(node)); } return (sourceFile) => transformNodeAndChildren(sourceFile, context); }; }