From f0c8e8423772af3d0ff8e20d8d9bd99471cf5c74 Mon Sep 17 00:00:00 2001 From: CyrilFerlicot Date: Mon, 14 Sep 2026 15:52:48 +0200 Subject: [PATCH 1/4] inspectionFASTSourceCode: should scroll to selection For now this is bad and morphic dependant but I don't see how we can do better for now --- src/FAST-Core-Model-Extension/FASTTEntity.extension.st | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/FAST-Core-Model-Extension/FASTTEntity.extension.st b/src/FAST-Core-Model-Extension/FASTTEntity.extension.st index 69c6fbe..d68ccf9 100644 --- a/src/FAST-Core-Model-Extension/FASTTEntity.extension.st +++ b/src/FAST-Core-Model-Extension/FASTTEntity.extension.st @@ -92,7 +92,9 @@ FASTTEntity >> inspectionFASTSourceCode: aBuilder [ addTextSegmentDecoration: (SpTextPresenterDecorator forHighlight interval: (self startPos to: self endPos); yourself); - selectionInterval: (self startPos to: self endPos) ] ]; + selectionInterval: (self startPos to: self endPos). + "I don't know why I need the double defer:... But it does not work without it. Also, this is bad because this is Morphic dependant. But I don't see how to do that with Spec for now." + this withAdapterPerformOrDefer: [ :anAdapter | this defer: [ this defer: [ anAdapter widget scrollPane scrollSelectionIntoView ] ] ] ] ]; yourself ] From 523f89a4827652498ec534954c8e4f63c87d2cf4 Mon Sep 17 00:00:00 2001 From: CyrilFerlicot Date: Tue, 15 Sep 2026 15:35:59 +0200 Subject: [PATCH 2/4] Add API on nodes in lines + Add tests based on a new test generator --- src/BaselineOfFAST/BaselineOfFAST.class.st | 3 +- .../FASTTEntity.extension.st | 41 +++ .../FASTTestMetamodelGenerator.class.st | 79 ++++++ src/FAST-Core-Tests-Generator/package.st | 1 + .../FASTTEntityTest.class.st | 242 ++++++++++++++++-- 5 files changed, 350 insertions(+), 16 deletions(-) create mode 100644 src/FAST-Core-Tests-Generator/FASTTestMetamodelGenerator.class.st create mode 100644 src/FAST-Core-Tests-Generator/package.st diff --git a/src/BaselineOfFAST/BaselineOfFAST.class.st b/src/BaselineOfFAST/BaselineOfFAST.class.st index a55155b..b348971 100644 --- a/src/BaselineOfFAST/BaselineOfFAST.class.st +++ b/src/BaselineOfFAST/BaselineOfFAST.class.st @@ -56,8 +56,9 @@ BaselineOfFAST >> definePackages: spec [ package: 'FAST-Core-Model-Extension' with: [ spec requires: #( 'FAST-Core-Model' ) ]; package: 'FAST-Core-Visitor' with: [ spec requires: #( 'FAST-Core-Model' ) ]; package: 'FAST-Model-Generator'; + package: 'FAST-Core-Tests-Generator' with: [ spec requires: #( 'FAST-Core-Model' ) ]; package: 'FAST-Core-Tools' with: [ spec requires: #( 'FAST-Core-Visitor' ) ]; - package: 'FAST-Core-Tools-Tests' with: [ spec requires: #( 'FAST-Core-Tools' ) ]; + package: 'FAST-Core-Tools-Tests' with: [ spec requires: #( 'FAST-Core-Tools' 'FAST-Core-Tests-Generator' ) ]; package: 'FAST-Core-Deprecated'. spec for: #NeedsMoose do: [ diff --git a/src/FAST-Core-Model-Extension/FASTTEntity.extension.st b/src/FAST-Core-Model-Extension/FASTTEntity.extension.st index d68ccf9..10d8a24 100644 --- a/src/FAST-Core-Model-Extension/FASTTEntity.extension.st +++ b/src/FAST-Core-Model-Extension/FASTTEntity.extension.st @@ -1,5 +1,14 @@ Extension { #name : 'FASTTEntity' } +{ #category : '*FAST-Core-Model-Extension' } +FASTTEntity >> addNodesIncludedBetween: startLine and: endLine into: result [ + "If an entity is in the lines, we add it to the result. If it is partialy in the lines, we check if they have a child inside." + + self containedEntities do: [ :entity | + (entity isBetweenLine: startLine and: endLine) ifTrue: [ result add: entity ]. + (entity isPartiallyBetweenLine: startLine and: endLine) ifTrue: [ entity addNodesIncludedBetween: startLine and: endLine into: result ] ] +] + { #category : '*FAST-Core-Model-Extension' } FASTTEntity >> asSourceCode [ "Export AST as code using an exporter if available." @@ -98,6 +107,38 @@ FASTTEntity >> inspectionFASTSourceCode: aBuilder [ yourself ] +{ #category : '*FAST-Core-Model-Extension' } +FASTTEntity >> isBetweenLine: startLine and: endLine [ + "I return true if I am fully included in the lines as parameter." + + ^ self startLine >= startLine and: [ self endLine <= endLine ] +] + +{ #category : '*FAST-Core-Model-Extension' } +FASTTEntity >> isPartiallyBetweenLine: startLine and: endLine [ + "I return true if I am partially included in the lines as parameter. If I am fully inside the bound, I return false." + + (self isBetweenLine: startLine and: endLine) ifTrue: [ ^ false ]. + + self endLine < startLine ifTrue: [ ^ false ]. + + self startLine > endLine ifTrue: [ ^ false ]. + + ^ true +] + +{ #category : '*FAST-Core-Model-Extension' } +FASTTEntity >> nodesIncludedBetween: startLine and: endLine [ + "I return the nodes that are inside the given bounds, bounds included. + Nodes that are not entirely in the bounds are not considered. + We take the most high level node matching this. It means that if an assignment node for `x = 1` matches, we add only this one and not the identifier `x` and literal `1` since they are subnodes of the assignment." + + | result | + result := OrderedCollection new. + self addNodesIncludedBetween: startLine and: endLine into: result. + ^ result +] + { #category : '*FAST-Core-Model-Extension' } FASTTEntity >> orderedChildren [ ^ self children asOrderedCollection sort: #startPos ascending diff --git a/src/FAST-Core-Tests-Generator/FASTTestMetamodelGenerator.class.st b/src/FAST-Core-Tests-Generator/FASTTestMetamodelGenerator.class.st new file mode 100644 index 0000000..a580118 --- /dev/null +++ b/src/FAST-Core-Tests-Generator/FASTTestMetamodelGenerator.class.st @@ -0,0 +1,79 @@ +Class { + #name : 'FASTTestMetamodelGenerator', + #superclass : 'FamixMetamodelGenerator', + #instVars : [ + 'entity', + 'class', + 'method', + 'tEntity', + 'tNamedBehaviouralEntity', + 'tNamedEntity', + 'tStatement', + 'tStatementBlock' + ], + #category : 'FAST-Core-Tests-Generator', + #package : 'FAST-Core-Tests-Generator' +} + +{ #category : 'class initialization' } +FASTTestMetamodelGenerator class >> initialize [ + "Generate the test MM when we load the generator to not save the generated code and ensure we start from a clean state." + + self new generateWithCleaning +] + +{ #category : 'accessing' } +FASTTestMetamodelGenerator class >> packageName [ + + ^ #'FAST-Core-Test-Model' +] + +{ #category : 'accessing' } +FASTTestMetamodelGenerator class >> prefix [ + + ^ #FASTTest +] + +{ #category : 'accessing' } +FASTTestMetamodelGenerator class >> submetamodels [ + + ^ { FASTMetamodelGenerator } +] + +{ #category : 'definition' } +FASTTestMetamodelGenerator >> defineClasses [ + + super defineClasses. + + entity := builder newClassNamed: #Entity. + class := builder newClassNamed: #Class. + method := builder newClassNamed: #Method +] + +{ #category : 'definition' } +FASTTestMetamodelGenerator >> defineHierarchy [ + + super defineHierarchy. + entity --|> tEntity. + + class --|> entity. + class --|> tStatementBlock. + class --|> tNamedEntity. + class --|> #THasImmediateSource. + class withPrecedenceOf: #THasImmediateSource. + + method --|> tStatement. + method --|> tNamedBehaviouralEntity +] + +{ #category : 'definition' } +FASTTestMetamodelGenerator >> defineTraits [ + + super defineTraits. + + tEntity := self remoteTrait: #TEntity withPrefix: #FAST. + tNamedBehaviouralEntity := self remoteTrait: #TNamedBehaviouralEntity withPrefix: #FAST. + tNamedEntity := self remoteTrait: #TNamedEntity withPrefix: #FAST. + tStatement := self remoteTrait: #TStatement withPrefix: #FAST. + tStatementBlock := self remoteTrait: #TStatementBlock withPrefix: #FAST +] diff --git a/src/FAST-Core-Tests-Generator/package.st b/src/FAST-Core-Tests-Generator/package.st new file mode 100644 index 0000000..3380278 --- /dev/null +++ b/src/FAST-Core-Tests-Generator/package.st @@ -0,0 +1 @@ +Package { #name : 'FAST-Core-Tests-Generator' } diff --git a/src/FAST-Core-Tools-Tests/FASTTEntityTest.class.st b/src/FAST-Core-Tools-Tests/FASTTEntityTest.class.st index 4a41c27..73bcd9f 100644 --- a/src/FAST-Core-Tools-Tests/FASTTEntityTest.class.st +++ b/src/FAST-Core-Tools-Tests/FASTTEntityTest.class.st @@ -8,20 +8,68 @@ Class { #package : 'FAST-Core-Tools-Tests' } -{ #category : 'tests' } +{ #category : 'helpers' } +FASTTEntityTest >> containerEntityWithChildren [ + + | container | + container := self entityWithStartPosition: 1 endPosition: 17. + container addStatement: (self newMethodWithStartPosition: 1 endPosition: 5). + container addStatement: (self newMethodWithStartPosition: 7 endPosition: 11). + container addStatement: (self newMethodWithStartPosition: 13 endPosition: 17). + ^ container +] + +{ #category : 'helpers' } FASTTEntityTest >> entityWithStartPosition: start endPosition: end [ - | entity | - entity := FASTEntity new. - entity startPos: start. - entity endPos: end. - entity addTalent: FamixTHasImmediateSource. - entity source: 'x = 1 + + ^ FASTTestClass new + startPos: start; + endPos: end; + source: self sourceCode; + yourself +] + +{ #category : 'helpers' } +FASTTEntityTest >> nestedContainerEntity [ + + | container child | + container := self entityWithStartPosition: 1 endPosition: 17. + child := self entityWithStartPosition: 7 endPosition: 17. + container addStatement: child. + child addStatement: (self newMethodWithStartPosition: 7 endPosition: 11). + child addStatement: (self newMethodWithStartPosition: 13 endPosition: 17). + ^ container +] + +{ #category : 'helpers' } +FASTTEntityTest >> newMethodWithStartPosition: start endPosition: end [ + + ^ FASTTestMethod new + startPos: start; + endPos: end; + yourself +] + +{ #category : 'helpers' } +FASTTEntityTest >> sourceCode [ + + ^ 'x = 1 y = 2 -z = 3'. - ^ entity +z = 3' +] + +{ #category : 'tests - nodesBetweenLineAnd' } +FASTTEntityTest >> testBoundaryLinesAreInclusive [ + + | container result | + container := self containerEntityWithChildren. + result := container nodesIncludedBetween: 1 and: 1. + + self assert: result size equals: 1. + self assert: result first startLine equals: 1 ] -{ #category : 'tests' } +{ #category : 'tests - start/endLine' } FASTTEntityTest >> testEndLineOnFirstLine [ | entity | entity := self entityWithStartPosition: 1 endPosition: 5. @@ -29,7 +77,7 @@ FASTTEntityTest >> testEndLineOnFirstLine [ self assert: entity endLine equals: 1 ] -{ #category : 'tests' } +{ #category : 'tests - start/endLine' } FASTTEntityTest >> testEndLineOnThirdLine [ | entity | entity := self entityWithStartPosition: 13 endPosition: 17. @@ -37,7 +85,171 @@ FASTTEntityTest >> testEndLineOnThirdLine [ self assert: entity endLine equals: 3 ] -{ #category : 'tests' } +{ #category : 'tests - nodesBetweenLineAnd' } +FASTTEntityTest >> testIncludedNodesWithinLineRange [ + + | container result | + container := self containerEntityWithChildren. + result := container nodesIncludedBetween: 2 and: 2. + + self assert: result size equals: 1. + self assert: result first startLine equals: 2 +] + +{ #category : 'tests - isBetweenLineAnd' } +FASTTEntityTest >> testIsBetweenLineAndNodeAtExactBounds [ + + | entity | + entity := self entityWithStartPosition: 1 endPosition: 11. + + self assert: (entity isBetweenLine: 1 and: 2) +] + +{ #category : 'tests - isBetweenLineAnd' } +FASTTEntityTest >> testIsBetweenLineAndNodeExtendsBeforeStart [ + + | entity | + entity := self entityWithStartPosition: 7 endPosition: 17. + + self deny: (entity isBetweenLine: 3 and: 3) +] + +{ #category : 'tests - isBetweenLineAnd' } +FASTTEntityTest >> testIsBetweenLineAndNodeExtendsBeyondEnd [ + + | entity | + entity := self entityWithStartPosition: 7 endPosition: 17. + + self deny: (entity isBetweenLine: 2 and: 2) +] + +{ #category : 'tests - isBetweenLineAnd' } +FASTTEntityTest >> testIsBetweenLineAndNodeFullyWithin [ + + | entity | + entity := self entityWithStartPosition: 1 endPosition: 11. + + self assert: (entity isBetweenLine: 1 and: 3) +] + +{ #category : 'tests - isBetweenLineAnd' } +FASTTEntityTest >> testIsBetweenLineAndNodeOutside [ + + | entity | + entity := self entityWithStartPosition: 1 endPosition: 5. + + self deny: (entity isBetweenLine: 2 and: 3) +] + +{ #category : 'tests - isPartiallyInBetweenLineAnd' } +FASTTEntityTest >> testIsPartiallyBetweenLineAndNodeAtExactBounds [ + + | entity | + entity := self entityWithStartPosition: 1 endPosition: 11. + + self deny: (entity isPartiallyBetweenLine: 1 and: 2) +] + +{ #category : 'tests - isPartiallyInBetweenLineAnd' } +FASTTEntityTest >> testIsPartiallyBetweenLineAndNodeEncompassesRange [ + + | entity | + entity := self entityWithStartPosition: 7 endPosition: 17. + + self assert: (entity isPartiallyBetweenLine: 2 and: 2) +] + +{ #category : 'tests - isPartiallyInBetweenLineAnd' } +FASTTEntityTest >> testIsPartiallyBetweenLineAndNodeExtendsBeforeStart [ + + | entity | + entity := self entityWithStartPosition: 1 endPosition: 11. + + self assert: (entity isPartiallyBetweenLine: 2 and: 3) +] + +{ #category : 'tests - isPartiallyInBetweenLineAnd' } +FASTTEntityTest >> testIsPartiallyBetweenLineAndNodeExtendsBeyondEnd [ + + | entity | + entity := self entityWithStartPosition: 1 endPosition: 11. + + self assert: (entity isPartiallyBetweenLine: 1 and: 1) +] + +{ #category : 'tests - isPartiallyInBetweenLineAnd' } +FASTTEntityTest >> testIsPartiallyBetweenLineAndNodeFullyWithin [ + + | entity | + entity := self entityWithStartPosition: 1 endPosition: 11. + + self deny: (entity isPartiallyBetweenLine: 1 and: 3) +] + +{ #category : 'tests - isPartiallyInBetweenLineAnd' } +FASTTEntityTest >> testIsPartiallyBetweenLineAndNodeOutside [ + + | entity | + entity := self entityWithStartPosition: 1 endPosition: 11. + + self deny: (entity isPartiallyBetweenLine: 3 and: 3) +] + +{ #category : 'tests - nodesBetweenLineAnd' } +FASTTEntityTest >> testLineRangeCoveringWholeSourceIncludesAllStatements [ + + | container result | + container := self containerEntityWithChildren. + result := container nodesIncludedBetween: 1 and: 3. + + self assert: result size equals: 3 +] + +{ #category : 'tests - nodesBetweenLineAnd' } +FASTTEntityTest >> testNestedNodesIncludedOnlyWhenFullyInRange [ + + | container result | + container := self nestedContainerEntity. + result := container nodesIncludedBetween: 2 and: 2. + + self assert: result size equals: 1. + self assert: result first startLine equals: 2 +] + +{ #category : 'tests - nodesBetweenLineAnd' } +FASTTEntityTest >> testNoNodesWhenLineRangeIsOutsideSource [ + + | container result | + container := self containerEntityWithChildren. + result := container nodesIncludedBetween: 4 and: 4. + + self assertEmpty: result +] + +{ #category : 'tests - nodesBetweenLineAnd' } +FASTTEntityTest >> testNodeStartingInsideButEndingOutsideIsExcluded [ + + | container result | + container := self entityWithStartPosition: 1 endPosition: 17. + container addStatement: (self newMethodWithStartPosition: 7 endPosition: 17). + + result := container nodesIncludedBetween: 2 and: 2. + + self assertEmpty: result +] + +{ #category : 'tests - nodesBetweenLineAnd' } +FASTTEntityTest >> testPartiallyOverlappingNodesAreExcluded [ + + | container child | + container := self entityWithStartPosition: 1 endPosition: 17. + child := self newMethodWithStartPosition: 7 endPosition: 17. + container addStatement: child. + + self deny: ((container nodesIncludedBetween: 1 and: 1) includes: child) +] + +{ #category : 'tests - start/endLine' } FASTTEntityTest >> testStartAndEndLineOnSameLine [ | entity | entity := self entityWithStartPosition: 7 endPosition: 10. @@ -45,7 +257,7 @@ FASTTEntityTest >> testStartAndEndLineOnSameLine [ self assert: entity startLine equals: entity endLine ] -{ #category : 'tests' } +{ #category : 'tests - start/endLine' } FASTTEntityTest >> testStartLineOnFirstLine [ | entity | entity := self entityWithStartPosition: 1 endPosition: 5. @@ -53,7 +265,7 @@ FASTTEntityTest >> testStartLineOnFirstLine [ self assert: entity startLine equals: 1 ] -{ #category : 'tests' } +{ #category : 'tests - start/endLine' } FASTTEntityTest >> testStartLineOnSecondLine [ | entity | entity := self entityWithStartPosition: 7 endPosition: 10. @@ -61,7 +273,7 @@ FASTTEntityTest >> testStartLineOnSecondLine [ self assert: entity startLine equals: 2 ] -{ #category : 'tests' } +{ #category : 'tests - start/endLine' } FASTTEntityTest >> testStartLineOnThirdLine [ | entity | entity := self entityWithStartPosition: 13 endPosition: 17. From 8d75795965a4ad44dfc8fa1bfb62b75333f3207c Mon Sep 17 00:00:00 2001 From: CyrilFerlicot Date: Tue, 15 Sep 2026 15:50:02 +0200 Subject: [PATCH 3/4] Enable coveralls --- .github/workflows/tests.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c12dbed..2c0247d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -20,6 +20,12 @@ jobs: - run: smalltalkci -s ${{ matrix.smalltalk }} shell: bash timeout-minutes: 15 + - name: Coveralls GitHub Action + uses: coverallsapp/github-action@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + flag-name: ${{matrix.smalltalk}} + parallel: true finish-coveralls: needs: build From 60ee4891b6c3f0f11807fb019bee1016fcf685e3 Mon Sep 17 00:00:00 2001 From: CyrilFerlicot Date: Tue, 15 Sep 2026 15:59:15 +0200 Subject: [PATCH 4/4] Do not run coverage on Deprecated package and tests --- .smalltalk.ston | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.smalltalk.ston b/.smalltalk.ston index 88d84ef..00316ac 100644 --- a/.smalltalk.ston +++ b/.smalltalk.ston @@ -1,7 +1,7 @@ SmalltalkCISpec { #testing : { #coverage : { - #packages : [ 'FAST-Core.*' ], + #packages : [ 'FAST-Core-Model', 'FAST-Core-Model-Extension', 'FAST-Core-Tools', 'FAST-Core-Visitor' ], #format : #lcov } },