diff --git a/plugins/groovy/groovy-psi/resources/inspectionDescriptions/SingletonConstructor.html b/plugins/groovy/groovy-psi/resources/inspectionDescriptions/SingletonConstructor.html
new file mode 100644
index 000000000000..69ed586942b4
--- /dev/null
+++ b/plugins/groovy/groovy-psi/resources/inspectionDescriptions/SingletonConstructor.html
@@ -0,0 +1,5 @@
+
+
+Checks that classes annotated by @Singleton do not have constructors unless it is declared non strict.
+
+
\ No newline at end of file
diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/fixes/RemoveElementQuickFix.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/fixes/RemoveElementQuickFix.java
new file mode 100644
index 000000000000..b6de7c11b1e2
--- /dev/null
+++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/fixes/RemoveElementQuickFix.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.groovy.codeInspection.fixes;
+
+import com.intellij.codeInspection.LocalQuickFix;
+import com.intellij.codeInspection.ProblemDescriptor;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiElement;
+import com.intellij.util.Function;
+import org.jetbrains.annotations.Nls;
+import org.jetbrains.annotations.NotNull;
+
+public class RemoveElementQuickFix implements LocalQuickFix {
+
+ private final String myName;
+ private final Function myElementFunction;
+
+ public RemoveElementQuickFix(@NotNull String name, @NotNull Function function) {
+ myName = name;
+ myElementFunction = function;
+ }
+
+ @Nls
+ @NotNull
+ @Override
+ public String getFamilyName() {
+ return myName;
+ }
+
+ @Override
+ public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
+ PsiElement descriptorElement = descriptor.getPsiElement();
+ if (descriptorElement == null) return;
+
+ PsiElement elementToRemove = myElementFunction.fun(descriptorElement);
+ if (elementToRemove == null) return;
+
+ elementToRemove.delete();
+ }
+}
diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/GroovyTransformationsBundle.properties b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/GroovyTransformationsBundle.properties
index e69de29bb2d1..ce01aae13af0 100644
--- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/GroovyTransformationsBundle.properties
+++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/GroovyTransformationsBundle.properties
@@ -0,0 +1,5 @@
+# @Singleton
+singleton.constructor.inspection=@Singleton constructors
+singleton.constructor.found=@Singleton class should not have constructors
+singleton.constructor.remove=Remove constructor
+singleton.constructor.makeNonStrict=Make @Singleton non-strict
diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/singleton/MakeNonStrictQuickFix.kt b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/singleton/MakeNonStrictQuickFix.kt
new file mode 100644
index 000000000000..c32e1a39259c
--- /dev/null
+++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/singleton/MakeNonStrictQuickFix.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.groovy.transformations.singleton
+
+import com.intellij.codeInsight.AnnotationUtil
+import com.intellij.codeInspection.LocalQuickFix
+import com.intellij.codeInspection.ProblemDescriptor
+import com.intellij.openapi.project.Project
+import org.jetbrains.annotations.Nls
+import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory
+import org.jetbrains.plugins.groovy.transformations.message
+
+internal class MakeNonStrictQuickFix : LocalQuickFix {
+
+ @Nls
+ override fun getFamilyName() = message("singleton.constructor.makeNonStrict")
+
+ override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
+ val annotation = getAnnotation(descriptor.psiElement) ?: return
+ val existingValue = AnnotationUtil.findDeclaredAttribute(annotation, "strict")
+ val newValue = GroovyPsiElementFactory.getInstance(project)
+ .createAnnotationFromText("@A(strict=false)")
+ .parameterList
+ .attributes[0]
+ if (existingValue == null) {
+ annotation.parameterList.add(newValue)
+ }
+ else {
+ existingValue.replace(newValue)
+ }
+ }
+}
diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/singleton/SingletonConstructorInspection.kt b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/singleton/SingletonConstructorInspection.kt
new file mode 100644
index 000000000000..6b08f7956c11
--- /dev/null
+++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/singleton/SingletonConstructorInspection.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jetbrains.plugins.groovy.transformations.singleton
+
+import com.intellij.codeInspection.LocalInspectionTool
+import com.intellij.codeInspection.ProblemHighlightType
+import com.intellij.codeInspection.ProblemsHolder
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiElementVisitor
+import org.jetbrains.plugins.groovy.codeInspection.fixes.RemoveElementQuickFix
+import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes
+import org.jetbrains.plugins.groovy.lang.psi.impl.booleanValue
+import org.jetbrains.plugins.groovy.lang.psi.impl.findDeclaredDetachedValue
+import org.jetbrains.plugins.groovy.transformations.message
+
+class SingletonConstructorInspection : LocalInspectionTool() {
+
+ override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = object : PsiElementVisitor() {
+
+ override fun visitElement(element: PsiElement) {
+ if (element.node.elementType !== GroovyTokenTypes.mIDENT) return
+ val annotation = getAnnotation(element) ?: return
+ val strict = annotation.findDeclaredDetachedValue("strict").booleanValue() ?: true
+ if (!strict) return
+
+ holder.registerProblem(
+ element,
+ message("singleton.constructor.found"),
+ ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
+ RemoveElementQuickFix(message("singleton.constructor.remove")) { e -> e.parent },
+ MakeNonStrictQuickFix()
+ )
+ }
+ }
+}
diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/singleton/impl.kt b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/singleton/impl.kt
index 2d68c7d443e8..c38b6cf3279f 100644
--- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/singleton/impl.kt
+++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/singleton/impl.kt
@@ -15,7 +15,19 @@
*/
package org.jetbrains.plugins.groovy.transformations.singleton
+import com.intellij.codeInsight.AnnotationUtil
+import com.intellij.psi.PsiElement
+import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotation
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames
internal val singletonFqn = GroovyCommonClassNames.GROOVY_LANG_SINGLETON
-internal val singletonOriginInfo = "by @Singleton"
\ No newline at end of file
+internal val singletonOriginInfo = "by @Singleton"
+
+internal fun getAnnotation(identifier: PsiElement?): GrAnnotation? {
+ val parent = identifier?.parent as? GrMethod ?: return null
+ if (!parent.isConstructor) return null
+ val clazz = parent.containingClass as? GrTypeDefinition ?: return null
+ return AnnotationUtil.findAnnotation(clazz, singletonFqn) as? GrAnnotation
+}
diff --git a/plugins/groovy/src/META-INF/plugin.xml b/plugins/groovy/src/META-INF/plugin.xml
index 54cce5af8b63..9ac4d73c13f1 100644
--- a/plugins/groovy/src/META-INF/plugin.xml
+++ b/plugins/groovy/src/META-INF/plugin.xml
@@ -1148,6 +1148,10 @@
enabledByDefault="true" level="WEAK WARNING"
implementationClass="org.jetbrains.plugins.groovy.codeInspection.untypedUnresolvedAccess.GrUnresolvedAccessInspection"/>
+
diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/transformations/singleton/SingletonConstructorInspectionTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/transformations/singleton/SingletonConstructorInspectionTest.groovy
new file mode 100644
index 000000000000..1ac32add9080
--- /dev/null
+++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/transformations/singleton/SingletonConstructorInspectionTest.groovy
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.groovy.transformations.singleton
+
+import com.intellij.testFramework.LightProjectDescriptor
+import groovy.transform.CompileStatic
+import org.jetbrains.plugins.groovy.GroovyLightProjectDescriptor
+import org.jetbrains.plugins.groovy.LightGroovyTestCase
+import org.jetbrains.plugins.groovy.transformations.GroovyTransformationsBundle
+
+@CompileStatic
+class SingletonConstructorInspectionTest extends LightGroovyTestCase {
+
+ final LightProjectDescriptor projectDescriptor = GroovyLightProjectDescriptor.GROOVY_LATEST
+
+ void setUp() {
+ super.setUp()
+ fixture.enableInspections(SingletonConstructorInspection)
+ }
+
+ void 'test highlighting'() {
+ fixture.with {
+ configureByText '_.groovy', '''\
+@Singleton
+class A {
+ A() {}
+ A(a) {}
+}
+
+@Singleton(strict=true)
+class ExplicitStrict {
+ ExplicitStrict() {}
+ ExplicitStrict(a) {}
+}
+
+@Singleton(strict = false)
+class NonStrict {
+ NonStrict() {}
+ NonStrict(a) {}
+}
+'''
+ checkHighlighting()
+ }
+ }
+
+ void 'test make non strict fix'() {
+ fixture.with {
+ configureByText '_.groovy', '''\
+@Singleton
+class A {
+ A() {}
+}
+'''
+ def intention = findSingleIntention(GroovyTransformationsBundle.message("singleton.constructor.makeNonStrict"))
+ assert intention
+ launchAction(intention)
+ checkResult '''\
+@Singleton(strict = false)
+class A {
+ A() {}
+}
+'''
+ }
+ }
+
+ void 'test make non strict fix existing'() {
+ fixture.with {
+ configureByText '_.groovy', '''\
+@Singleton(strict = true, property = "lol")
+class A {
+ A() {}
+}
+'''
+ def intention = findSingleIntention(GroovyTransformationsBundle.message("singleton.constructor.makeNonStrict"))
+ assert intention
+ launchAction(intention)
+ checkResult '''\
+@Singleton(strict = false, property = "lol")
+class A {
+ A() {}
+}
+'''
+ }
+ }
+
+ void 'test remove constructor fix'() {
+ fixture.with {
+ configureByText '_.groovy', '''\
+@Singleton
+class A {
+ A() {}
+}
+'''
+ def intention = findSingleIntention(GroovyTransformationsBundle.message("singleton.constructor.remove"))
+ assert intention
+ launchAction(intention)
+ checkResult '''\
+@Singleton
+class A {
+}
+'''
+ }
+ }
+}