Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

12-Add-simplification-for-ifEmptyifNotEmpty #66

Merged
merged 1 commit into from
May 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 50 additions & 1 deletion resources/doc/documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ Channel do multiple cleanings in protocols.
*Warnings:*
This cleaning should not have any counter indication.

### Conditional simplifications
### Nil conditional simplifications

Chanel simplifies conditionals. For example it will rewrite:

Expand Down Expand Up @@ -181,6 +181,55 @@ ifNotNil: aBlock
*Warnings:*
The only danger of this cleaning happens for projects working on multiple Smalltalks

### Empty conditional simplifications

Chanel simplifies empty conditionals. For example it will rewrite:

| Original | Transformation |
| ------------- | ------------- |
| `x isEmpty ifTrue: y` | `x ifEmpty: y` |
| `x isEmpty ifFalse: y` | `x ifNotEmpty: y` |
| `x isEmpty ifTrue: y ifFalse: z` | `x ifEmpty: y ifNotEmpty: z` |
| `x isEmpty ifFalse: y ifTrue: z` | `x ifEmpty: z ifNotEmpty: y` |
| `x isNotEmpty ifTrue: y` | `x ifNotEmpty: y` |
| `x isNotEmpty ifFalse: y` | `x ifEmpty: y` |
| `x isNotEmpty ifTrue: y ifFalse: z` | `x ifEmpty: z ifNotEmpty: y` |
| `x isNotEmpty ifFalse: y ifTrue: z` | `x ifEmpty: y ifNotEmpty: z` |
| `x ifEmpty: [ true ] ifNotEmpty: [ false ]` | `x isEmpty` |
| `x ifEmpty: [ false ] ifNotEmpty: [ true ]` | `x isNotEmpty` |
| `x ifNotEmpty: [ false ] ifEmpty: [ true ]` | `x isEmpty` |
| `x ifNotEmpty: [ true ] ifEmpty: [ false ]` | `x isNotEmpty` |
| `x isEmpty ifTrue: [ true ] ifFalse: [ false ]` | `x isEmpty` |
| `x isEmpty ifTrue: [ false ] ifFalse: [ true ]` | `x isNotEmpty` |
| `x isEmpty ifFalse: [ false ] ifTrue: [ true ]` | `x isEmpty` |
| `x isEmpty ifFalse: [ true ] ifTrue: [ false ]` | `x isNotEmpty` |
| `x isNotEmpty ifTrue: [ true ] ifFalse: [ false ]` | `x isNotEmpty` |
| `x isNotEmpty ifTrue: [ false ] ifFalse: [ true ]` | `x isEmpty` |
| `x isNotEmpty ifFalse: [ false ] ifTrue: [ true ]` | `x isNotEmpty` |
| `x isNotEmpty ifFalse: [ true ] ifTrue: [ false ]` | `x isEmpty` |

*Conditions for the cleanings to by applied:*
- Can be applied on any classes and traits.
- A pattern from the list above match.
- Does not apply if the application of the pattern would cause an infinit loop. For example it will **not** rewrite:

```Smalltalk
ifEmpty:: aBlock
^ self isEmpty ifTrue: aBlock
```

into:

```Smalltalk
ifEmpty: aBlock
^ self ifEmpty: aBlock
```

*Warnings:*
This cleaner might introduce problems in two cases:
- You project works on multiple smalltalks with different APIs
- You have your own objects implementing #isEmpty or #isNotEmpty but not the conditionals.

### Methods with alias unification

Chanel unify the use of some methods with alias. Here is the list of rewrites:
Expand Down
235 changes: 235 additions & 0 deletions src/Chanel-Tests/ChanelEmptyConditionalSimplifierCleanerTest.class.st
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
"
A ChanelEmptyConditionalSimplifierCleanerTest is a test class for testing the behavior of ChanelEmptyConditionalSimplifierCleaner
"
Class {
#name : #ChanelEmptyConditionalSimplifierCleanerTest,
#superclass : #ChanelAbstractCleanerTest,
#category : #'Chanel-Tests'
}

{ #category : #running }
ChanelEmptyConditionalSimplifierCleanerTest >> setUp [
super setUp.
class := self createDefaultClass
]

{ #category : #tests }
ChanelEmptyConditionalSimplifierCleanerTest >> testDoesNotReplaceIfItIntroduceAnInfinitLoop [
| oldMethod |
class
compile:
'ifEmpty: aBlock
^self isEmpty ifTrue: aBlock'.

oldMethod := class >> #ifEmpty:.

self runCleaner.

self
assert: (class >> #ifEmpty:) sourceCode
equals:
'ifEmpty: aBlock
^self isEmpty ifTrue: aBlock'.

self assert: class >> #ifEmpty: identicalTo: oldMethod
]

{ #category : #tests }
ChanelEmptyConditionalSimplifierCleanerTest >> testDoesNotReplaceIfItIntroduceAnInfinitLoop2 [
| oldMethod |
class
compile:
'ifEmpty: aBlock
self isEmpty ifTrue: aBlock'.

oldMethod := class >> #ifEmpty:.

self runCleaner.

self
assert: (class >> #ifEmpty:) sourceCode
equals:
'ifEmpty: aBlock
self isEmpty ifTrue: aBlock'.

self assert: class >> #ifEmpty: identicalTo: oldMethod
]

{ #category : #tests }
ChanelEmptyConditionalSimplifierCleanerTest >> testIfEmptyIfNotEmpty [
self assert: '#() ifEmpty: [ true ] ifNotEmpty: [ false ]' isRewrittenAs: '#() isEmpty'
]

{ #category : #tests }
ChanelEmptyConditionalSimplifierCleanerTest >> testIfEmptyIfNotEmpty2 [
self assert: '#() ifEmpty: [ false ] ifNotEmpty: [ true ]' isRewrittenAs: '#() isNotEmpty'
]

{ #category : #tests }
ChanelEmptyConditionalSimplifierCleanerTest >> testIfNotEmptyIfEmpty [
self assert: '#() ifNotEmpty: [ false ] ifEmpty: [ true ]' isRewrittenAs: '#() isEmpty'
]

{ #category : #tests }
ChanelEmptyConditionalSimplifierCleanerTest >> testIfNotEmptyIfEmpty2 [
self assert: '#() ifNotEmpty: [ true ] ifEmpty: [ false ]' isRewrittenAs: '#() isNotEmpty'
]

{ #category : #tests }
ChanelEmptyConditionalSimplifierCleanerTest >> testIsEmptyIfFalse [
self assert: '#() isEmpty ifFalse: [ 2 ]' isRewrittenAs: '#() ifNotEmpty: [ 2 ]'
]

{ #category : #tests }
ChanelEmptyConditionalSimplifierCleanerTest >> testIsEmptyIfFalseIfTrue [
self assert: '#() isEmpty ifFalse: [ 2 ] ifTrue: [ 1 ]' isRewrittenAs: '#() ifEmpty: [ 1 ] ifNotEmpty: [ 2 ]'
]

{ #category : #tests }
ChanelEmptyConditionalSimplifierCleanerTest >> testIsEmptyIfFalseIfTrue2 [
self assert: '#() isEmpty ifFalse: [ false ] ifTrue: [ true ]' isRewrittenAs: '#() isEmpty'
]

{ #category : #tests }
ChanelEmptyConditionalSimplifierCleanerTest >> testIsEmptyIfFalseIfTrue3 [
self assert: '#() isEmpty ifFalse: [ true ] ifTrue: [ false ]' isRewrittenAs: '#() isNotEmpty'
]

{ #category : #tests }
ChanelEmptyConditionalSimplifierCleanerTest >> testIsEmptyIfTrue [
self assert: '#() isEmpty ifTrue: [ 2 ]' isRewrittenAs: '#() ifEmpty: [ 2 ]'
]

{ #category : #tests }
ChanelEmptyConditionalSimplifierCleanerTest >> testIsEmptyIfTrueIfFalse [
self assert: '#() isEmpty ifTrue: [ 1 ] ifFalse: [ 2 ]' isRewrittenAs: '#() ifEmpty: [ 1 ] ifNotEmpty: [ 2 ]'
]

{ #category : #tests }
ChanelEmptyConditionalSimplifierCleanerTest >> testIsEmptyIfTrueIfFalse2 [
self assert: '#() isEmpty ifTrue: [ true ] ifFalse: [ false ]' isRewrittenAs: '#() isEmpty'
]

{ #category : #tests }
ChanelEmptyConditionalSimplifierCleanerTest >> testIsEmptyIfTrueIfFalse3 [
self assert: '#() isEmpty ifTrue: [ false ] ifFalse: [ true ]' isRewrittenAs: '#() isNotEmpty'
]

{ #category : #tests }
ChanelEmptyConditionalSimplifierCleanerTest >> testIsNotEmptyIfFalse [
self assert: '#() isNotEmpty ifFalse: [ 2 ]' isRewrittenAs: '#() ifEmpty: [ 2 ]'
]

{ #category : #tests }
ChanelEmptyConditionalSimplifierCleanerTest >> testIsNotEmptyIfFalseIfTrue [
self assert: '#() isNotEmpty ifFalse: [ 1 ] ifTrue: [ 2 ]' isRewrittenAs: '#() ifEmpty: [ 1 ] ifNotEmpty: [ 2 ]'
]

{ #category : #tests }
ChanelEmptyConditionalSimplifierCleanerTest >> testIsNotEmptyIfFalseIfTrue2 [
self assert: '#() isNotEmpty ifFalse: [ true ] ifTrue: [ false ]' isRewrittenAs: '#() isEmpty'
]

{ #category : #tests }
ChanelEmptyConditionalSimplifierCleanerTest >> testIsNotEmptyIfFalseIfTrue3 [
self assert: '#() isNotEmpty ifFalse: [ false ] ifTrue: [ true ]' isRewrittenAs: '#() isNotEmpty'
]

{ #category : #tests }
ChanelEmptyConditionalSimplifierCleanerTest >> testIsNotEmptyIfTrue [
self assert: '#() isNotEmpty ifTrue: [ 2 ]' isRewrittenAs: '#() ifNotEmpty: [ 2 ]'
]

{ #category : #tests }
ChanelEmptyConditionalSimplifierCleanerTest >> testIsNotEmptyIfTrueIfFalse [
self assert: '#() isNotEmpty ifTrue: [ 2 ] ifFalse: [ 1 ]' isRewrittenAs: '#() ifEmpty: [ 1 ] ifNotEmpty: [ 2 ]'
]

{ #category : #tests }
ChanelEmptyConditionalSimplifierCleanerTest >> testIsNotEmptyIfTrueIfFalse2 [
self assert: '#() isNotEmpty ifTrue: [ false ] ifFalse: [ true ]' isRewrittenAs: '#() isEmpty'
]

{ #category : #tests }
ChanelEmptyConditionalSimplifierCleanerTest >> testIsNotEmptyIfTrueIfFalse3 [
self assert: '#() isNotEmpty ifTrue: [ true ] ifFalse: [ false ]' isRewrittenAs: '#() isNotEmpty'
]

{ #category : #tests }
ChanelEmptyConditionalSimplifierCleanerTest >> testReplacementDoesNotRemoveExtensions [
class
compile:
('{1}
{2}' format: {self selector . '#() isEmpty ifTrue: [ false ]'})
classified: self extensionProtocol.

self runCleaner.

self
assert: (class >> self selector) sourceCode
equals:
('{1}
{2}' format: {self selector . '#() ifEmpty: [ false ]'}).

self assert: (class >> self selector) protocol equals: self extensionProtocol
]

{ #category : #tests }
ChanelEmptyConditionalSimplifierCleanerTest >> testReplacementInTraits [
| trait |
trait := self createDefaultTrait.

class setTraitComposition: trait.

trait
compile:
('{1}
{2}' format: {self selector . '#() isEmpty ifTrue: [ false ]'}).

self runCleaner.

self
assert: (trait >> self selector) sourceCode
equals:
('{1}
{2}' format: {self selector . '#() ifEmpty: [ false ]'}).

self assert: (trait localSelectors includes: self selector).
self deny: (class localSelectors includes: self selector)
]

{ #category : #tests }
ChanelEmptyConditionalSimplifierCleanerTest >> testReplacementOnClassSide [
class class
compile:
('{1}
{2}' format: {self selector . '#() isEmpty ifTrue: [ false ]'}).

self runCleaner.

self
assert: (class class >> self selector) sourceCode
equals:
('{1}
{2}' format: {self selector . '#() ifEmpty: [ false ]'})
]

{ #category : #tests }
ChanelEmptyConditionalSimplifierCleanerTest >> testWithNothingToReplace [
| oldMethod |
class
compile:
('{1}
{2}' format: {self selector . '#() ifEmpty: [ false ]'}).

oldMethod := class >> self selector.
self runCleaner.

self
assert: (class >> self selector) sourceCode
equals:
('{1}
{2}' format: {self selector . '#() ifEmpty: [ false ]'}).

self assert: class >> self selector identicalTo: oldMethod
]
46 changes: 46 additions & 0 deletions src/Chanel/ChanelEmptyConditionalSimplifierCleaner.class.st
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"
Description
--------------------

I am a cleaner simplifying conditionals related to emptyness of collections.
"
Class {
#name : #ChanelEmptyConditionalSimplifierCleaner,
#superclass : #ChanelMethodRewriterCleaner,
#category : #Chanel
}

{ #category : #accessing }
ChanelEmptyConditionalSimplifierCleaner class >> priority [
^ 3500
]

{ #category : #cleaning }
ChanelEmptyConditionalSimplifierCleaner >> rewriter [
^ RBParseTreeRewriter new
replace: '`@receiver ifEmpty: [ true ] ifNotEmpty: [ false ]' with: '`@receiver isEmpty';
replace: '`@receiver ifEmpty: [ false ] ifNotEmpty: [ true ]' with: '`@receiver isNotEmpty';
replace: '`@receiver ifNotEmpty: [ false ] ifEmpty: [ true ]' with: '`@receiver isEmpty';
replace: '`@receiver ifNotEmpty: [ true ] ifEmpty: [ false ]' with: '`@receiver isNotEmpty';

replace: '`@receiver isEmpty ifTrue: [ true ] ifFalse: [ false ]' with: '`@receiver isEmpty';
replace: '`@receiver isEmpty ifTrue: [ false ] ifFalse: [ true ]' with: '`@receiver isNotEmpty';
replace: '`@receiver isEmpty ifFalse: [ false ] ifTrue: [ true ]' with: '`@receiver isEmpty';
replace: '`@receiver isEmpty ifFalse: [ true ] ifTrue: [ false ]' with: '`@receiver isNotEmpty';

replace: '`@receiver isNotEmpty ifTrue: [ true ] ifFalse: [ false ]' with: '`@receiver isNotEmpty';
replace: '`@receiver isNotEmpty ifTrue: [ false ] ifFalse: [ true ]' with: '`@receiver isEmpty';
replace: '`@receiver isNotEmpty ifFalse: [ false ] ifTrue: [ true ]' with: '`@receiver isNotEmpty';
replace: '`@receiver isNotEmpty ifFalse: [ true ] ifTrue: [ false ]' with: '`@receiver isEmpty';

replace: '`@receiver isEmpty ifTrue: `@arg' with: '`@receiver ifEmpty: `@arg';
replace: '`@receiver isEmpty ifFalse: `@arg' with: '`@receiver ifNotEmpty: `@arg';
replace: '`@receiver isEmpty ifTrue: `@arg ifFalse: `@arg2' with: '`@receiver ifEmpty: `@arg ifNotEmpty: `@arg2';
replace: '`@receiver isEmpty ifFalse: `@arg ifTrue: `@arg2' with: '`@receiver ifEmpty: `@arg2 ifNotEmpty: `@arg';

replace: '`@receiver isNotEmpty ifTrue: `@arg' with: '`@receiver ifNotEmpty: `@arg';
replace: '`@receiver isNotEmpty ifFalse: `@arg' with: '`@receiver ifEmpty: `@arg';
replace: '`@receiver isNotEmpty ifTrue: `@arg ifFalse: `@arg2' with: '`@receiver ifEmpty: `@arg2 ifNotEmpty: `@arg';
replace: '`@receiver isNotEmpty ifFalse: `@arg ifTrue: `@arg2' with: '`@receiver ifEmpty: `@arg ifNotEmpty: `@arg2';
yourself
]