UNPKG

dry-ts

Version:

Find candidate duplicate TypeScript code by comparing normalized AST structure.

106 lines (105 loc) 3.63 kB
import ts from "typescript"; import { NormalizedNode } from "./NormalizedNode.js"; export class TypeScriptNormalizer { normalize(node, memo) { if (memo) { const cached = memo.get(node); if (cached) { return cached; } } const children = []; for (const marker of this.markers(node)) { children.push(new NormalizedNode(marker, [])); } node.forEachChild((child) => { if (this.keepsStructuralChild(child)) { children.push(this.normalize(child, memo)); } }); const result = new NormalizedNode(this.tag(node), children); if (memo) { memo.set(node, result); } return result; } tag(node) { return ts.SyntaxKind[node.kind] ?? `SyntaxKind${node.kind}`; } keepsStructuralChild(child) { return !this.isName(child) && !this.isLiteral(child) && child.kind !== ts.SyntaxKind.JSDocComment; } isName(node) { return ts.isIdentifier(node) || node.kind === ts.SyntaxKind.PrivateIdentifier; } isLiteral(node) { return (ts.isStringLiteral(node) || ts.isNumericLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node) || ts.isRegularExpressionLiteral(node) || node.kind === ts.SyntaxKind.BigIntLiteral || node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword || node.kind === ts.SyntaxKind.NullKeyword); } markers(node) { const markers = []; this.addDecoratorMarkers(node, markers); this.addModifierMarkers(node, markers); this.addOperatorMarkers(node, markers); this.addTypeScriptShapeMarkers(node, markers); markers.sort(); return markers; } addDecoratorMarkers(node, markers) { if (ts.canHaveDecorators(node)) { for (const _decorator of ts.getDecorators(node) ?? []) { markers.push("decorator"); } } } addModifierMarkers(node, markers) { if (!ts.canHaveModifiers(node)) { return; } for (const modifier of ts.getModifiers(node) ?? []) { markers.push(`modifier:${this.tag(modifier)}`); } } addOperatorMarkers(node, markers) { if (ts.isBinaryExpression(node)) { markers.push(`operator:${this.tag(node.operatorToken)}`); } else if (ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) { markers.push(`operator:${ts.SyntaxKind[node.operator]}`); } } addTypeScriptShapeMarkers(node, markers) { if (ts.isVariableDeclarationList(node)) { if ((node.flags & ts.NodeFlags.Const) !== 0) { markers.push("variable:const"); } else if ((node.flags & ts.NodeFlags.Let) !== 0) { markers.push("variable:let"); } else { markers.push("variable:var"); } } if (ts.isHeritageClause(node)) { markers.push(`heritage:${ts.SyntaxKind[node.token]}`); } if (hasQuestionToken(node)) { markers.push("optional"); } if (hasExclamationToken(node)) { markers.push("definite"); } } } function hasQuestionToken(node) { return "questionToken" in node && Boolean(node.questionToken); } function hasExclamationToken(node) { return "exclamationToken" in node && Boolean(node.exclamationToken); }