import fs from "fs" ; import path from "path" ; import Parser from "tree-sitter" ; import TypeScript from "tree-sitter-typescript/typescript" ; import { CompareResult , FileDetails , prepareResult , compareItems , compareValue , MessageType , OrderedItem , compareStrings , compareText , } from "../comparers" ; const parser = new Parser ( ) ; parser . setLanguage ( TypeScript ) ; export type ParameterDetails = { name : string ; type : string ; ordinal : number ; isOptional : boolean ; } ; export interface VariableDetails { name : string ; type : string ; value ? : string ; } export interface ModuleVariableDetails extends VariableDetails { isConst ? : boolean ; isExported : boolean ; } export interface FieldDetails extends VariableDetails { visibility ? : Visibility ; isReadOnly ? : boolean ; } export interface GenericTypeParameterDetails extends OrderedItem { } export interface FunctionDetails { name : string ; body : string ; returnType : string ; genericTypes : GenericTypeParameterDetails [ ] ; parameters : ParameterDetails [ ] ; } export type Visibility = "public" | "private" | "protected" ; export interface MethodDetails extends FunctionDetails { visibility : Visibility ; } export type InterfaceDetails = { name : string ; interfaces ? : string [ ] ; methods : MethodDetails [ ] ; fields : FieldDetails [ ] ; isExported : boolean ; } ; export type ClassDetails = { name : string ; baseClass ? : string ; interfaces ? : string [ ] ; methods : MethodDetails [ ] ; fields : FieldDetails [ ] ; isExported : boolean ; } ; export type TypeDetails = { name : string ; type : string ; isExported : boolean ; } ; export type SourceDetails = { classes : ClassDetails [ ] ; interfaces : InterfaceDetails [ ] ; types : TypeDetails [ ] ; variables : ModuleVariableDetails [ ] ; functions : FunctionDetails [ ] ; } ; export function parseFile ( filePath ) { const contents = fs . readFileSync ( filePath ) . toString ( ) ; return parser . parse ( contents ) ; } function extractField ( fieldNode ) { const { typeNode , valueNode } = fieldNode as any ; const accessibilityNode = fieldNode . namedChildren . find ( ( n ) => n . type === "accessibility_modifier" ) ; const readOnlyNode = fieldNode . namedChildren . find ( ( n ) => n . type === "readonly" ) ; return { name : ( fieldNode as any ) . nameNode . text , type : typeNode ? typeNode . children [ 1 ] . text : "any" , value : valueNode ? valueNode . text : undefined , visibility : accessibilityNode ? ( accessibilityNode . text as Visibility ) : "private" , isReadOnly : readOnlyNode !== undefined , } ; } function extractParameter ( parameterNode , ordinal ) { const { nameNode , typeNode } = parseParameterNode ( parameterNode ) ; return { name : nameNode . text , type : typeNode ? typeNode . children [ 1 ] . text : "any" , ordinal , isOptional : parameterNode . type === "optional_parameter" , } ; } function parseParameterNode ( parameterNode ) { if ( parameterNode . namedChildren . length === 3 ) { const [ accessibility , nameNode , typeNode ] = parameterNode . namedChildren ; return { nameNode , typeNode } ; } if ( parameterNode . namedChildren . length === 4 ) { const [ accessibility , nameNode , typeNode , value ] = parameterNode . namedChildren ; return { nameNode , typeNode } ; } const [ nameNode , typeNode ] = parameterNode . namedChildren ; return { nameNode , typeNode } ; } function extractGenericParameter ( genericParamNode , ordinal ) { return { name : genericParamNode . namedChildren [ 0 ] . text , ordinal , } ; } function extractFunction ( functionNode ) { const returnTypeNode = ( functionNode as any ) . returnTypeNode ; const parameterNodes = ( functionNode as any ) . parametersNode . namedChildren ; const genericNodes = ( functionNode as any ) . typeParametersNode ; return { name : ( functionNode as any ) . nameNode . text , body : ( functionNode as any ) . bodyNode . text , genericTypes : genericNodes ? genericNodes . namedChildren . map ( ( p , i ) => extractGenericParameter ( p , i ) ) : [ ] , returnType : returnTypeNode ? returnTypeNode . children [ 1 ] . text : "any" , parameters : parameterNodes . map ( ( p , i ) => extractParameter ( p , i ) ) , } ; } function extractMethod ( methodNode ) { const accessibilityNode = methodNode . namedChildren . find ( ( n ) => n . type === "accessibility_modifier" ) ; return { ... extractFunction ( methodNode ) , visibility : accessibilityNode ? ( accessibilityNode . text as Visibility ) : "public" , } ; } function isExported ( node ) { return node . parent ! . type === "export_statement" ; } function extractImplements ( node ) { const implementsNode = ( node as any ) . namedChildren . find ( ( n ) => n . type === "implements_clause" ) ; return implementsNode ? implementsNode . namedChildren . map ( ( i ) => i . text ) : undefined ; } function extractExtends ( node ) { const extendsNode = ( node as any ) . namedChildren . find ( ( n ) => n . type === "extends_clause" ) ; return extendsNode ? extendsNode . namedChildren . map ( ( i ) => i . text ) : undefined ; } function extractClass ( classNode ) { const classBody = ( classNode as any ) . bodyNode ; const heritageNode = ( classNode as any ) . namedChildren . find ( ( n ) => n . type === "class_heritage" ) ; const baseClass = heritageNode ? ( extractExtends ( heritageNode ) || [ ] ) [ 0 ] : undefined ; const interfaces = heritageNode ? extractImplements ( heritageNode ) : undefined ; return { name : ( classNode as any ) . nameNode . text , methods : classBody . namedChildren . filter ( ( n ) => n . type === "method_definition" ) . map ( extractMethod ) , fields : classBody . namedChildren . filter ( ( n ) => n . type === "public_field_definition" ) . map ( extractField ) , isExported : isExported ( classNode ) , ... ( baseClass ? { baseClass } : undefined ) , ... ( interfaces ? { interfaces } : undefined ) , } ; } function extractInterface ( interfaceNode ) { const interfaceBody = ( interfaceNode as any ) . bodyNode ; const interfaces = extractExtends ( interfaceNode ) ; return { name : ( interfaceNode as any ) . nameNode . text , methods : interfaceBody . namedChildren . filter ( ( n ) => n . type === "method_definition" ) . map ( extractMethod ) , fields : interfaceBody . namedChildren . filter ( ( n ) => n . type === "public_field_definition" ) . map ( extractField ) , isExported : isExported ( interfaceNode ) , ... ( interfaces ? { interfaces } : undefined ) , } ; } function extractTypeAlias ( typeAliasNode ) { const typeNode = ( typeAliasNode as any ) . valueNode ; return { name : ( typeAliasNode as any ) . nameNode . text , type : typeNode . text , isExported : isExported ( typeAliasNode ) , } ; } function extractVariable ( variableNode ) { const typeNode = ( variableNode as any ) . typeNode ; const valueNode = ( variableNode as any ) . valueNode ; return { name : ( variableNode as any ) . nameNode . text , type : typeNode ? typeNode . children [ 1 ] . text : "any" , value : valueNode ? valueNode . text : undefined , isConst : variableNode . parent ! . children [ 0 ] . type === "const" , isExported : isExported ( variableNode . parent ! ) , } ; } export function isModuleScopeVariable ( variableNode ) { const grandparent = variableNode . parent && variableNode . parent . parent ; return Boolean ( grandparent && ( grandparent . type === "export_statement" || grandparent . type === "program" ) ) ; } export function extractSourceDetails ( parseTree ) { return { classes : parseTree . rootNode . descendantsOfType ( "class_declaration" ) . map ( extractClass ) , interfaces : parseTree . rootNode . descendantsOfType ( "interface_declaration" ) . map ( extractInterface ) , types : parseTree . rootNode . descendantsOfType ( "type_alias_declaration" ) . map ( extractTypeAlias ) , variables : parseTree . rootNode . descendantsOfType ( "variable_declarator" ) . filter ( isModuleScopeVariable ) . map ( extractVariable ) , functions : parseTree . rootNode . descendantsOfType ( "function_declaration" ) . map ( extractFunction ) , } ; } export function compareParameter ( oldParameter , newParameter ) { return prepareResult ( oldParameter . name , MessageType . Changed , [ compareValue ( "Type" , oldParameter . type , newParameter . type ) , compareValue ( "Optional" , oldParameter . isOptional , newParameter . isOptional ) , ] ) ; } export function compareFunction ( oldFunction , newFunction , extraResults ? ) { return prepareResult ( oldFunction . name , MessageType . Changed , [ compareItems ( "Generic Parameters" , MessageType . Outline , oldFunction . genericTypes , newFunction . genericTypes , ( t ) => undefined , true ) , compareItems ( "Parameters" , MessageType . Outline , oldFunction . parameters , newFunction . parameters , compareParameter , true ) , compareValue ( "Return Type" , oldFunction . returnType , newFunction . returnType ) , ... ( extraResults || [ ] ) , compareText ( "Body" , oldFunction . body , newFunction . body ) , ] ) ; } export function compareMethod ( oldMethod , newMethod ) { return compareFunction ( oldMethod , newMethod , [ compareValue ( "Visibility" , oldMethod . visibility , newMethod . visibility ) , ] ) ; } export function compareField ( oldField , newField ) { return prepareResult ( oldField . name , MessageType . Changed , [ compareValue ( "Type" , oldField . type , newField . type ) , compareText ( "Value" , oldField . value , newField . value ) , compareValue ( "Visibility" , oldField . visibility , newField . visibility ) , compareValue ( "Read Only" , oldField . isReadOnly , newField . isReadOnly ) , ] ) ; } export function compareVariable ( oldVariable , newVariable ) { return prepareResult ( oldVariable . name , MessageType . Changed , [ compareValue ( "Type" , oldVariable . type , newVariable . type ) , compareText ( "Value" , oldVariable . value , newVariable . value ) , compareValue ( "Exported" , oldVariable . isExported , newVariable . isExported ) , compareValue ( "Constant" , oldVariable . isConst , newVariable . isConst ) , ] ) ; } export function compareClass ( oldClass , newClass ) { return prepareResult ( oldClass . name , MessageType . Changed , [ compareValue ( "Exported" , oldClass . isExported , newClass . isExported ) , compareValue ( "Base Class" , oldClass . baseClass , newClass . baseClass ) , compareStrings ( "Interfaces" , oldClass . interfaces , newClass . interfaces ) , compareItems ( "Methods" , MessageType . Outline , oldClass . methods , newClass . methods , compareMethod ) , compareItems ( "Fields" , MessageType . Outline , oldClass . fields , newClass . fields , compareField ) , ] ) ; } export function compareInterface ( oldInterface , newInterface ) { return prepareResult ( oldInterface . name , MessageType . Changed , [ compareValue ( "Exported" , oldInterface . isExported , newInterface . isExported ) , compareStrings ( "Base Interfaces" , oldInterface . interfaces , newInterface . interfaces ) , compareItems ( "Methods" , MessageType . Outline , oldInterface . methods , newInterface . methods , compareMethod ) , compareItems ( "Fields" , MessageType . Outline , oldInterface . fields , newInterface . fields , compareField ) , ] ) ; } export function compareFile ( oldFile , newFile ) { const oldSource = extractSourceDetails ( parseFile ( path . resolve ( oldFile . basePath , oldFile . name ) ) ) ; const newSource = extractSourceDetails ( parseFile ( path . resolve ( newFile . basePath , newFile . name ) ) ) ; return prepareResult ( oldFile . name , MessageType . Changed , [ compareItems ( "Classes" , MessageType . Outline , oldSource . classes , newSource . classes , compareClass ) , compareItems ( "Interfaces" , MessageType . Outline , oldSource . interfaces , newSource . interfaces , compareInterface ) , compareItems ( "Functions" , MessageType . Outline , oldSource . functions , newSource . functions , compareFunction ) , compareItems ( "Variables" , MessageType . Outline , oldSource . variables , newSource . variables , compareVariable ) , ] ) ; }