import fs from "fs" ; import path from "path" ; import Parser from "tree-sitter" ; import Python from "tree-sitter-python" ; import { CompareResult , FileDetails , prepareResult , compareItems , MessageType , compareText , NamedItem , } from "../comparers" ; interface ParameterDetails { name : string ; type ? : string ; defaultValue ? : string ; } interface MethodDetails { name : string ; body : string ; returnType ? : string ; parameters : ParameterDetails [ ] ; } interface AssignmentDetails { name : string ; value : string ; } interface ClassDetails { name : string ; superclasses : SuperclassDetails [ ] ; methods : MethodDetails [ ] ; assignments : AssignmentDetails [ ] ; } export interface SuperclassDetails extends NamedItem { } export interface SourceDetails { classes : ClassDetails [ ] ; } export function extractParameter ( paramNode ) { const nameNode = ( paramNode as any ) . nameNode ; const typeNode = ( paramNode as any ) . typeNode ; const valueNode = ( paramNode as any ) . valueNode ; return { name : nameNode ? nameNode . text : paramNode . text , type : typeNode ?. text , defaultValue : valueNode ?. text , } ; } export function extractMethod ( methodNode ) { const bodyNode = methodNode . namedChildren . find ( ( node ) => node . type === "block" ) ; const returnTypeNode = ( methodNode as any ) . returnTypeNode ; const parameterNodes = ( methodNode as any ) . parametersNode . namedChildren . filter ( ( p ) => ! p . text . startsWith ( "#" ) ) ; return { name : ( methodNode as any ) . nameNode . text , returnType : returnTypeNode ? returnTypeNode . text : undefined , body : bodyNode ! . text , parameters : parameterNodes . map ( extractParameter ) , } ; } export function compareParameter ( oldParameter , newParameter ) { return prepareResult ( oldParameter . name , MessageType . Changed , [ compareText ( "Type" , oldParameter . type , newParameter . type ) , compareText ( "Default Value" , oldParameter . defaultValue , newParameter . defaultValue ) , ] ) ; } export function compareMethod ( oldMethod , newMethod ) { return prepareResult ( oldMethod . name , MessageType . Changed , [ compareItems ( "Parameters" , MessageType . Outline , oldMethod . parameters , newMethod . parameters , compareParameter , true ) , compareText ( "Body" , oldMethod . body , newMethod . body ) , compareText ( "Return Type" , oldMethod . returnType , newMethod . returnType ) , ] ) ; } export function extractAssignment ( expressionNode ) { const assignmentNode = expressionNode . namedChildren [ 0 ] ; const [ leftSideNode , rightSideNode ] = assignmentNode . namedChildren ; return { name : leftSideNode . text , value : rightSideNode . text , } ; } export function compareAssignment ( oldAssignment , newAssignment ) { return prepareResult ( oldAssignment . name , MessageType . Changed , [ compareText ( "Value" , oldAssignment . value , newAssignment . value ) , ] ) ; } export function extractClass ( classNode ) { const bodyNode = classNode . namedChildren . find ( ( node ) => node . type === "block" ) ; const superclasses = ( classNode as any ) . superclassesNode ; return { name : ( classNode as any ) . nameNode . text , superclasses : superclasses ? superclasses . namedChildren . map ( ( s ) => { return { name : s . text , } ; } ) : [ ] , methods : bodyNode ! . namedChildren . filter ( ( node ) => node . type === "function_definition" ) . map ( extractMethod ) , assignments : bodyNode ! . namedChildren . filter ( ( node ) => node . type === "expression_statement" && node . namedChildren [ 0 ] . type === "assignment" ) . map ( extractAssignment ) , } ; } export function compareClass ( oldClass , newClass ) { return prepareResult ( oldClass . name , MessageType . Changed , [ compareItems ( "Superclasses" , MessageType . Outline , oldClass . superclasses , newClass . superclasses , ( t ) => undefined ) , compareItems ( "Methods" , MessageType . Outline , oldClass . methods , newClass . methods , compareMethod ) , compareItems ( "Fields" , MessageType . Outline , oldClass . assignments , newClass . assignments , compareAssignment ) , ] ) ; } export function extractSourceDetails ( parseTree ) { return { classes : parseTree . rootNode . descendantsOfType ( "class_definition" ) . map ( extractClass ) , } ; } const parser = new Parser ( ) ; parser . setLanguage ( Python ) ; export function parseFile ( filePath ) { const contents = fs . readFileSync ( filePath ) . toString ( ) . replace ( /\r\n/g , "\n" ) ; return parser . parse ( contents ) ; } 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 ) , ] ) ; }