UNPKG

@custom-elements-manifest/analyzer

Version:

<!-- [=> See Source <=](../../docs/analyzer/index.md) -->

187 lines (160 loc) 5.73 kB
import ts from 'typescript'; import { safe } from './index.js'; /** * AST HELPERS */ export const isProperty = node => ts.isPropertyDeclaration(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node); /** * @example this.dispatchEvent(new Event('foo')); */ export const isDispatchEvent = node => node.expression?.name?.getText() === 'dispatchEvent' && node?.expression?.expression?.kind === ts.SyntaxKind.ThisKeyword export const isReturnStatement = statement => statement?.kind === ts.SyntaxKind.ReturnStatement; /** * @example customElements.define('my-el', MyEl); * @example window.customElements.define('my-el', MyEl); */ export const isCustomElementsDefineCall = node => (node?.expression?.getText() === 'customElements' || node?.expression?.getText() === 'window.customElements' || node?.expression?.getText() === 'globalThis.customElements') && node?.name?.getText() === 'define' && node?.parent?.kind === ts.SyntaxKind.CallExpression; /** * @example @attr * @example @attribute */ export function hasAttrAnnotation(member) { return member?.jsDoc?.some(jsDoc => jsDoc?.tags?.some(tag => safe(() => ["attribute", "attr"].includes(tag?.tagName?.getText())))); } /** * Whether or not node is: * - Number * - String * - Boolean * - Null */ export function isPrimitive(node) { return node && (ts.isNumericLiteral(node) || ts.isStringLiteral(node) || node?.kind === ts.SyntaxKind.NullKeyword || node?.kind === ts.SyntaxKind.TrueKeyword || node?.kind === ts.SyntaxKind.FalseKeyword) || // Handle only empty arrays for now (node?.kind === ts.SyntaxKind.ArrayLiteralExpression && node?.elements?.length === 0) } /** * Checks if a VariableStatement has an initializer * @example `let foo;` will return false * @example `let foo = '';` will return true */ export function hasInitializer(node) { return node?.declarationList?.declarations?.some(declaration => declaration?.initializer); } export function getElementNameFromDecorator(decorator) { const argument = decorator.expression.arguments[0]; /** * @example @customElement('my-el') */ if(argument.kind === ts.SyntaxKind.StringLiteral) { return argument.text; } /** * @example @customElement({ * name: 'my-el', * template * }) */ if(argument.kind === ts.SyntaxKind.ObjectLiteralExpression) { let result; argument?.properties?.forEach(property => { if(property?.name?.getText() === 'name') { result = property?.initializer?.text; } }); return result; } } /** * Gets the name of an attr from a decorators callExpression * @example @attr({attribute: 'my-el'}) */ export const getOptionsObject = decorator => decorator?.expression?.arguments?.find(arg => arg.kind === ts.SyntaxKind.ObjectLiteralExpression); /** * Get the return value expression of a return statement, omitting the type assertion */ export const getReturnValue = returnStatement => { let value = returnStatement.expression?.kind === ts.SyntaxKind.AsExpression ? returnStatement.expression.expression.getText() : returnStatement.expression?.getText() return value?.split?.(' ')?.[0]; } /** * Is this class member a static member? */ export const isStaticMember = member => member?.modifiers?.some?.(x => x.kind === ts.SyntaxKind.StaticKeyword); /** * @param {import('typescript').Expression} initializer * @return {initializer is import('typescript').AsExpression & { type: import("typescript").TypeReference }} */ function isAsConst(initializer) { return ( initializer && initializer.kind && ts.isAsExpression(initializer) && ts.isTypeReferenceNode(initializer.type) && initializer.type.typeName.getText() === 'const' ); } /** * Does the name have an initializer with `as const`? * @param {import('typescript').Node} node * @return {Boolean} */ export function isWellKnownType(node) { return ( node?.initializer && ( isAsConst(node?.initializer) ) ); } /** * Whether or not a node has an `@ignore` jsdoc annotation */ export const hasIgnoreJSDoc = node => node?.jsDoc?.some(doc => doc?.tags?.some(tag => safe(() => tag?.tagName?.getText()) === 'ignore' || safe(() => tag?.tagName?.getText()) === 'internal')); /** * @example this.__onClick = this.__onClick.bind(this); */ export function isBindCall(statement) { const { expression } = statement; if(expression) { const leftName = expression?.left?.name?.getText(); const rightName = expression?.right?.expression?.expression?.name?.getText(); const isBind = expression?.right?.expression?.name?.getText() === 'bind'; if(leftName === undefined || rightName === undefined) return false; if(leftName === rightName && isBind) { return true; } } return false; } /** * Does the variable have an `@ignore` or `@internal` JSDoc tag? * @param {import('typescript').Node|string} nodeOrName * @param {import('typescript').SourceFile} sourceFile * @return {import('typescript').Node} */ export function getDeclarationInFile(nodeOrName, sourceFile) { let name = nodeOrName; if (typeof nodeOrName === 'string') { if (!sourceFile) throw new Error('must provide sourceFile when first argument is a string'); } else { sourceFile = nodeOrName?.getSourceFile(); name = nodeOrName?.name?.getText(); } if (!name) return undefined; const sourceFileStatements = sourceFile.statements ?? []; return sourceFileStatements.find(statement => { if (ts.isVariableStatement(statement)) return statement.declarationList.declarations.find(declaration => declaration.name.getText() === name) else if (statement.name?.getText) return statement.name.getText() === name; }); }