diff --git a/src/FAST-Core-Tools/FASTDifferentialValidator.class.st b/src/FAST-Core-Tools/FASTDifferentialValidator.class.st index 7491052..ed7fbf5 100644 --- a/src/FAST-Core-Tools/FASTDifferentialValidator.class.st +++ b/src/FAST-Core-Tools/FASTDifferentialValidator.class.st @@ -28,122 +28,54 @@ Class { #superclass : #Object, #instVars : [ 'skipPaths', - 'strict' + 'comparator', + 'encoding' ], - #category : #'FAST-Core-Tools' + #category : #'FAST-Core-Tools-Validator' } -{ #category : #comparison } -FASTDifferentialValidator >> ast: node1 acceptableDifferenceTo: node2 [ - "In non strict mode, some differences could be accepted" - - ^false -] - -{ #category : #comparison } -FASTDifferentialValidator >> ast: node1 acceptableDifferenceTo: node2 property: property [ - - ^#(startPos endPos) includes: property -] - -{ #category : #comparison } -FASTDifferentialValidator >> ast: node1 differ: node2 [ - - self strict ifTrue: [ Exception signal: 'ASTs differ' ]. - - (self ast: node1 acceptableDifferenceTo: node2) - ifTrue: [ - (' ** difference in ignored, position in source: ' , node1 startPos asString) - traceCr. - Notification signal - ] - ifFalse: [ Exception signal: 'ASTs differ' ] -] - -{ #category : #comparison } -FASTDifferentialValidator >> ast: node1 differ: node2 property: property [ - - (self ast: node1 acceptableDifferenceTo: node2 property: property) - ifFalse: [ Exception signal: 'ASTs differ on property: ' , property implementingSelector ] -] - -{ #category : #configuration } -FASTDifferentialValidator >> beStrict [ +{ #category : #accessing } +FASTDifferentialValidator >> comparator [ - self strict: true + ^comparator ifNil: [ comparator := self comparatorClass new ] ] -{ #category : #utilities } -FASTDifferentialValidator >> childrenNodes: astNode [ - - ^OrderedCollection withAll: - (astNode children sorted: [:a :b | a startPos <= b startPos]) +{ #category : #accessing } +FASTDifferentialValidator >> comparatorClass [ + ^FamixModelComparator ] -{ #category : #comparison } +{ #category : #running } FASTDifferentialValidator >> compare: node1 to: node2 [ - "check the two nodes have the same class - then check they have the same properties (attributes with primitive types) - then check recursively that they ahev the same sub-nodes" - - self compareClasses: node1 to: node2. - self compareProperties: node1 to: node2. - self compareChildren: node1 to: node2 + + self comparator compare: node1 to: node2 ] -{ #category : #comparison } -FASTDifferentialValidator >> compareChildren: node1 to: node2 [ - "comparing the two lists of children may seem a bit complicate, but it is trying - to give more info when the children starts to differ - For example comparing using #with:collect: gives very little information if the two lists - differ in size" - - | size1 children1 size2 children2 | - children1 := self childrenNodes: node1. - children2 := self childrenNodes: node2. - - size1 := children1 size. - size2 := children2 size. - - 1 to: size1 do: [ :i | - size2 < i - ifTrue: [ self ast: (children1 at: i) differ: nil ] - ifFalse: [ self compare: (children1 at: i) to: (children2 at: i) ] ]. - - children2 size > children1 size ifTrue: [ - self ast: nil differ: (children2 at: children1 size + 1) ] +{ #category : #accessing } +FASTDifferentialValidator >> defaultEncoding [ + "other possibilities are 'latin1', 'utf8', ... + see `ZnCharacterEncoder knownEncodingIdentifiers` for all possibilities" + ^encoding ifNil: [ 'iso-8859-1' ] ] -{ #category : #comparison } -FASTDifferentialValidator >> compareClasses: node1 to: node2 [ +{ #category : #accessing } +FASTDifferentialValidator >> encoding [ - node1 class = node2 class ifFalse: [ - self ast: node1 differ: node2 ] + ^encoding ] -{ #category : #comparison } -FASTDifferentialValidator >> compareProperties: node1 to: node2 [ - "compare the values of the 'properties' (attributes with primitive types) of the two nodes - since the two nodes should be the same class, they have the same properties" - - (node1 class mooseDescription allPrimitiveProperties) do: [ :property || value1 value2 | - (self propertyToCompare: property) ifTrue: [ - value1 := node1 perform: property implementingSelector. - value2 := node2 perform: property implementingSelector. - - (value1 = value2) ifFalse: [ - self ast: node1 differ: node2 property: property - ]] - ] +{ #category : #accessing } +FASTDifferentialValidator >> encoding: aString [ + encoding := aString ] { #category : #utilities } FASTDifferentialValidator >> getASTFromFileReference: aFileReference [ | model | - aFileReference readStreamDo: [ :stream | + aFileReference readStreamEncoded: self defaultEncoding do: [ :stream | model := self getASTFromString: stream contents ]. ^self getTopLevelNodes: model @@ -172,7 +104,6 @@ FASTDifferentialValidator >> initialize [ super initialize. - strict := false. skipPaths := #(). ] @@ -188,16 +119,6 @@ FASTDifferentialValidator >> on: aDirectoryName [ self runOnFileReference: aDirectoryName asFileReference ] -{ #category : #testing } -FASTDifferentialValidator >> propertyToCompare: aFMProperty [ - "do not compare on derived (ie. computed) properties, only those with a stored value - do not compare on startPos/endPos as they are not meaningfull" - - aFMProperty isDerived ifTrue: [^false]. - (#(startPos endPos) includes: aFMProperty implementingSelector) ifTrue: [^false]. - ^true -] - { #category : #utilities } FASTDifferentialValidator >> reExportAST: ast [ @@ -241,9 +162,7 @@ FASTDifferentialValidator >> runOnSourceFile: aFileReference [ astBis := self getRootNode: (self getASTFromString: (self reExportAST: astOrig)). - [self compare: astOrig to: astBis] - on: Notification - do: [ "continue" ] + self compare: astOrig to: astBis ] ] @@ -258,15 +177,3 @@ FASTDifferentialValidator >> skipPaths: anObject [ skipPaths := anObject ] - -{ #category : #accessing } -FASTDifferentialValidator >> strict [ - - ^ strict -] - -{ #category : #accessing } -FASTDifferentialValidator >> strict: anObject [ - - strict := anObject -] diff --git a/src/FAST-Core-Tools/FamixModelComparator.class.st b/src/FAST-Core-Tools/FamixModelComparator.class.st new file mode 100644 index 0000000..b575061 --- /dev/null +++ b/src/FAST-Core-Tools/FamixModelComparator.class.st @@ -0,0 +1,182 @@ +Class { + #name : #FamixModelComparator, + #superclass : #Object, + #instVars : [ + 'strict' + ], + #category : #'FAST-Core-Tools-Validator' +} + +{ #category : #comparison } +FamixModelComparator >> ast: node1 acceptableDifferenceTo: node2 [ + "In non strict mode, some differences could be accepted" + + ^false +] + +{ #category : #comparison } +FamixModelComparator >> ast: node1 acceptableDifferenceTo: node2 property: property [ + "returns nil if the difference is not acceptable + otherwise, must return a block testing where the comparison process might resume" + + ^nil +] + +{ #category : #comparison } +FamixModelComparator >> ast: node1 differ: node2 [ + + self strict ifTrue: [ self differenceNotResumable ]. + + (self ast: node1 acceptableDifferenceTo: node2) + ifTrue: [ self differenceResumableInParent ] + ifFalse: [ self differenceNotResumable ] +] + +{ #category : #comparison } +FamixModelComparator >> ast: node1 differ: node2 property: property [ + + self strict ifTrue: [ self differenceNotResumable ]. + + (self ast: node1 acceptableDifferenceTo: node2 property: property) + ifNil: [ self differenceNotResumable ] + ifNotNil: [ :recovery | self differenceResumableOncondition: recovery ] +] + +{ #category : #configuration } +FamixModelComparator >> beStrict [ + + self strict: true +] + +{ #category : #utilities } +FamixModelComparator >> childrenNodes: astNode [ + + ^OrderedCollection withAll: + (astNode children sorted: [:a :b | a startPos <= b startPos]) + +] + +{ #category : #comparison } +FamixModelComparator >> compare: node1 to: node2 [ + "check the two nodes have the same class + then check they have the same properties (attributes with primitive types) + then check recursively that they ahev the same sub-nodes + + Upon error (differing asts) check a condition, + if it is true, do nothing (ie. resume comparison) + if it is false, resend the exception to be treated up in the calling stack" + + [ + self compareClasses: node1 to: node2. + self compareProperties: node1 to: node2. + self compareChildren: node1 to: node2 + ] + on: FamixModelComparatorRecoveryException + do: [ :exception | + (exception condition value: node1 value: node2) + ifFalse: [ exception pass ] + ] +] + +{ #category : #comparison } +FamixModelComparator >> compareChildren: node1 to: node2 [ + "comparing the two lists of children may seem a bit complicate, but it is trying + to give more info when the children starts to differ + For example comparing using #with:collect: gives very little information if the two lists + differ in size" + + | size1 children1 size2 children2 | + children1 := self childrenNodes: node1. + children2 := self childrenNodes: node2. + + size1 := children1 size. + size2 := children2 size. + + 1 to: size1 do: [ :i | + size2 < i + ifTrue: [ self ast: (children1 at: i) differ: nil ] + ifFalse: [ self compare: (children1 at: i) to: (children2 at: i) ] ]. + + children2 size > children1 size ifTrue: [ + self ast: nil differ: (children2 at: children1 size + 1) ] +] + +{ #category : #comparison } +FamixModelComparator >> compareClasses: node1 to: node2 [ + + node1 class = node2 class ifFalse: [ + self ast: node1 differ: node2 ] +] + +{ #category : #comparison } +FamixModelComparator >> compareProperties: node1 to: node2 [ + "compare the values of the 'properties' (attributes with primitive types) of the two nodes + since the two nodes should be the same class, they have the same properties" + + (node1 class mooseDescription allPrimitiveProperties) + select: [ :property | self propertyToCompare: property ] + thenDo: [ :property || value1 value2 | + value1 := node1 perform: property implementingSelector. + value2 := node2 perform: property implementingSelector. + + (value1 = value2) ifFalse: [ + self ast: node1 differ: node2 property: property + ] + ] + +] + +{ #category : #exceptionbuilder } +FamixModelComparator >> differenceNotResumable [ + + FamixModelComparatorRecoveryException signal: 'ASTs differ' + +] + +{ #category : #exceptionbuilder } +FamixModelComparator >> differenceResumableInParent [ + "raises an exception with a 'true' condition. + Process will resume in immediate parent" + + FamixModelComparatorRecoveryException new + condition: [ :node1 : node2 | true ] ; + signal: 'Overlooking difference in AST' +] + +{ #category : #exceptionbuilder } +FamixModelComparator >> differenceResumableOncondition: aBlock [ + + FamixModelComparatorRecoveryException new + condition: aBlock ; + signal: 'Overlooking difference in AST' +] + +{ #category : #initialization } +FamixModelComparator >> initialize [ + + super initialize. + + strict := false. +] + +{ #category : #testing } +FamixModelComparator >> propertyToCompare: aFMProperty [ + "do not compare on derived (ie. computed) properties, only those with a stored value + do not compare on startPos/endPos as they are not meaningfull" + + aFMProperty isDerived ifTrue: [^false]. + (#(startPos endPos) includes: aFMProperty implementingSelector) ifTrue: [^false]. + ^true +] + +{ #category : #accessing } +FamixModelComparator >> strict [ + + ^ strict +] + +{ #category : #accessing } +FamixModelComparator >> strict: anObject [ + + strict := anObject +] diff --git a/src/FAST-Core-Tools/FamixModelComparatorForTesting.class.st b/src/FAST-Core-Tools/FamixModelComparatorForTesting.class.st new file mode 100644 index 0000000..6854ea0 --- /dev/null +++ b/src/FAST-Core-Tools/FamixModelComparatorForTesting.class.st @@ -0,0 +1,19 @@ +Class { + #name : #FamixModelComparatorForTesting, + #superclass : #FamixModelComparator, + #category : #'FAST-Core-Tools-Validator' +} + +{ #category : #comparison } +FamixModelComparatorForTesting >> ast: node1 acceptableDifferenceTo: node2 property: property [ + + | recoveryBlock | + recoveryBlock := [:a :b | a class = FamixStNamedEntity]. + + ((node1 class = FamixStNamedEntity) and: + [ (property implementingSelector = #name) and: + [ (node1 perform: property implementingSelector) = #acceptable ] ]) + ifTrue: [ ^recoveryBlock ]. + + ^nil +] diff --git a/src/FAST-Core-Tools/FamixModelComparatorRecoveryException.class.st b/src/FAST-Core-Tools/FamixModelComparatorRecoveryException.class.st new file mode 100644 index 0000000..1c6245f --- /dev/null +++ b/src/FAST-Core-Tools/FamixModelComparatorRecoveryException.class.st @@ -0,0 +1,43 @@ +" +An exception raised when two nodes differ. A condition allows to check where in the calling stack to resume checking the AST. + +Upon error +- This Exception must be raised with the proper condition for recovery +- The condition is a block taking 2 parameters (`node1`, `node2`) +- The condition is then tested up in the calling stack of comparisons (`#compare:to:`) +- If the condition is true, the checking resumes at this point just have if the last comparison had succeeded. +- If the condition is false, the last comparison has failed and the checking goes up in the calling stack + +Typically, the condition will check the type of `node1` and/or `node2` and resume from there. +From example, comparing if-condition could fail and the recovery would occur from the parent `FASTxxxIfStatement`. +One must be specific enough in the condition so that another ancestor does not accept to recover when it was not expected. A possible way to do this is to specify the exact file and position of the node intended +" +Class { + #name : #FamixModelComparatorRecoveryException, + #superclass : #Exception, + #instVars : [ + 'condition' + ], + #category : #'FAST-Core-Tools-Validator' +} + +{ #category : #accessing } +FamixModelComparatorRecoveryException >> condition [ + + ^ condition +] + +{ #category : #accessing } +FamixModelComparatorRecoveryException >> condition: anObject [ + + condition := anObject +] + +{ #category : #initialization } +FamixModelComparatorRecoveryException >> initialize [ + "if no condition is specified, no recovery is possible" + + super initialize. + + condition := [ :node1 :node2 | false] +] diff --git a/src/FAST-Core-Tools/FamixModelComparatorTest.class.st b/src/FAST-Core-Tools/FamixModelComparatorTest.class.st new file mode 100644 index 0000000..fdd39d3 --- /dev/null +++ b/src/FAST-Core-Tools/FamixModelComparatorTest.class.st @@ -0,0 +1,74 @@ +Class { + #name : #FamixModelComparatorTest, + #superclass : #TestCase, + #instVars : [ + 'validator' + ], + #category : #'FAST-Core-Tools-Validator' +} + +{ #category : #running } +FamixModelComparatorTest >> setUp [ + + validator := FamixModelComparatorForTesting new +] + +{ #category : #tests } +FamixModelComparatorTest >> testClassDifference [ + + | node1 node2 | + node1 := FASTEntity new. + node2 := MooseEntity new. + + self + should: [ validator compare: node1 to: node2 ] + raise: FamixModelComparatorRecoveryException +] + +{ #category : #tests } +FamixModelComparatorTest >> testClassNoDifference [ + + | node1 node2 | + node1 := FASTEntity new. + node2 := FASTEntity new. + + self + shouldnt: [ validator compare: node1 to: node2 ] + raise: FamixModelComparatorRecoveryException +] + +{ #category : #tests } +FamixModelComparatorTest >> testPropertyAcceptableDifference [ + + | node1 node2 | + node1 := FamixStNamedEntity new name: 'acceptable'. + node2 := FamixStNamedEntity new name: 'other'. + + self + shouldnt: [ validator compare: node1 to: node2 ] + raise: FamixModelComparatorRecoveryException +] + +{ #category : #tests } +FamixModelComparatorTest >> testPropertyDifference [ + + | node1 node2 | + node1 := FamixStNamedEntity new name: 'a'. + node2 := FamixStNamedEntity new name: 'b'. + + self + should: [ validator compare: node1 to: node2 ] + raise: FamixModelComparatorRecoveryException +] + +{ #category : #tests } +FamixModelComparatorTest >> testPropertyNoDifference [ + + | node1 node2 | + node1 := FamixStNamedEntity new name: 'a'. + node2 := FamixStNamedEntity new name: 'a'. + + self + shouldnt: [ validator compare: node1 to: node2 ] + raise: FamixModelComparatorRecoveryException +]