Skip to content
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
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
language: groovy

jdk:
- oraclejdk8
- openjdk8

before_script: unset CI
after_script: set CI=true
Expand Down
19 changes: 17 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ Say Good Bye to Temporary Hacks

# Introduction

## @Remember

`@Remember` is an annotation which helps you not to forget any temporary solution (aka hacks or quick wins)
you have introduced into your code base. You specify the date in the future when you want to revisit the code, e.g. `@Remember('2018-12-24)`.
After this date the code no longer compiles forcing you to re-evaluate if the code is still required or to find
more permanent solution.

## Full Usage

```groovy
import com.agorapulse.remember.Remember

Expand All @@ -35,6 +35,21 @@ You can add an `owner` who is responsible for action which needs to be taken whe
You can force failing on continuous integration server by setting `ci` to `true`. By default, the annotation will
only fail during local builds.

## @DoNotMerge

If you have a code you want to discuss with your colleagues before merging to the main branch or simply
you have created a temporary solution which should never enter the main branch then you can use `@DoNotMerge`
annotation. If the library recognizes (on a best effort) that the build has been triggered by pull request then
it will fail to compile.

```groovy
import com.agorapulse.remember.DoNotMerge

@DoNotMerge('Just testing some stuff')
class Subject { }
```


## Maintained by

[![Agorapulse](https://cloud.githubusercontent.com/assets/139017/17053391/4a44735a-5034-11e6-8e72-9f4b7139d7e0.png)](https://www.agorapulse.com/)
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ repositories {
dependencies {
compile 'org.codehaus.groovy:groovy-all:2.4.14'

testCompile 'com.github.stefanbirkner:system-rules:1.19.0'
testCompile 'org.spockframework:spock-core:1.1-groovy-2.4'
}

version '0.2'
version '0.3'
group 'com.agorapulse'


Expand Down
Empty file modified gradlew.bat
100644 → 100755
Empty file.
34 changes: 34 additions & 0 deletions src/main/groovy/com/agorapulse/remember/DoNotMerge.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.agorapulse.remember;

import org.codehaus.groovy.transform.GroovyASTTransformationClass;

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.SOURCE)
@Target({
ElementType.TYPE,
ElementType.FIELD,
ElementType.METHOD,
ElementType.PARAMETER,
ElementType.CONSTRUCTOR,
ElementType.LOCAL_VARIABLE,
ElementType.ANNOTATION_TYPE,
ElementType.PACKAGE,
ElementType.TYPE_PARAMETER,
ElementType.TYPE_USE
})
@GroovyASTTransformationClass("com.agorapulse.remember.DoNotMergeTransformation")
/**
* <code>@DoNotMerge</code>is an annotation which helps you not to forget any temporary solution on a feature branch
* which you have introduced into your code base. The code won't compile if the code is running from pull request continuous build.
*
*/
public @interface DoNotMerge {

/**
* @return description why should the code expression should not be merged into the main branch
*/
String value() default "Do not merge";

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.agorapulse.remember;

import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.syntax.SyntaxException;
import org.codehaus.groovy.transform.ASTTransformation;
import org.codehaus.groovy.transform.GroovyASTTransformation;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Optional;

/**
* AST Transformation for {@link DoNotMerge} annotation.
*/
@GroovyASTTransformation(phase=CompilePhase.SEMANTIC_ANALYSIS)
public class DoNotMergeTransformation implements ASTTransformation {

@Override
public void visit(ASTNode[] nodes, SourceUnit source) {
AnnotationNode annotation = (AnnotationNode) nodes[0];

Expression valueExpression = annotation.getMember("value");
String value = Optional.ofNullable(valueExpression).map(Expression::getText).orElse("Do not merge");

if (isPullRequest()) {
source.addError(createSyntaxException(annotation, value));
}
}

private boolean isPullRequest() {
return System.getenv()
.keySet()
.stream()
.filter(key -> key.endsWith("PULL_REQUEST"))
.findAny()
.map(System::getenv)
.map(value -> value.length() > 0 && !"false".equals(value))
.orElse(false);

}

private SyntaxException createSyntaxException(AnnotationNode annotation, String message) {
return new SyntaxException(message, annotation.getLineNumber(), annotation.getColumnNumber(), annotation.getColumnNumber(), annotation.getLastColumnNumber());
}
}
98 changes: 98 additions & 0 deletions src/test/groovy/com/agorapulse/remember/DoNotMergeSpec.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package com.agorapulse.remember

import groovy.test.GroovyAssert
import org.codehaus.groovy.control.MultipleCompilationErrorsException
import org.codehaus.groovy.control.messages.SyntaxErrorMessage
import org.codehaus.groovy.syntax.SyntaxException
import org.junit.Rule
import org.junit.contrib.java.lang.system.EnvironmentVariables
import spock.lang.Specification
import spock.util.environment.RestoreSystemProperties

class DoNotMergeSpec extends Specification {

public static final String PR_ENV_VAR_NAME = 'TRAVIS_PULL_REQUEST'
@Rule EnvironmentVariables environmentVariables = new EnvironmentVariables()

void 'the annotation is ignored by default'() {
when:
// to pass PR build for this library
environmentVariables.clear(PR_ENV_VAR_NAME)

// language=Groovy
GroovyAssert.assertScript """
import com.agorapulse.remember.DoNotMerge

@DoNotMerge
class Subject { }

true
"""
then:
noExceptionThrown()
}

void 'the annotation is ignored of the pull request env var is false'() {
when:
// to pass PR build for this library
environmentVariables.set(PR_ENV_VAR_NAME, 'false')

// language=Groovy
GroovyAssert.assertScript """
import com.agorapulse.remember.DoNotMerge

@DoNotMerge
class Subject { }

true
"""
then:
noExceptionThrown()
}

void 'error is reported on PR build'() {
when:
environmentVariables.set(PR_ENV_VAR_NAME, '123456')
// language=Groovy
GroovyAssert.assertScript """
import com.agorapulse.remember.DoNotMerge

@DoNotMerge
class Subject { }

true
"""
then:
MultipleCompilationErrorsException e = thrown(MultipleCompilationErrorsException)
assertMessage(e, 'Do not merge @ line 4, column 17.')
}

void 'error is reported on PR build - with details'() {
when:
environmentVariables.set(PR_ENV_VAR_NAME, '123456')
// language=Groovy
GroovyAssert.assertScript """
import com.agorapulse.remember.DoNotMerge

@DoNotMerge('This will break everything!')
class Subject { }

true
"""
then:
MultipleCompilationErrorsException e = thrown(MultipleCompilationErrorsException)
assertMessage(e, 'This will break everything! @ line 4, column 17.')
}

boolean assertMessage(MultipleCompilationErrorsException multipleCompilationErrorsException, String message) {
assert multipleCompilationErrorsException.errorCollector
assert multipleCompilationErrorsException.errorCollector.errorCount == 1
assert multipleCompilationErrorsException.errorCollector.errors.first() instanceof SyntaxErrorMessage

SyntaxException exception = multipleCompilationErrorsException.errorCollector.errors.first().cause
assert exception.message == message

return true
}

}