BaseInspectionVisitor () { return new BaseInspectionVisitor() { @Override public void visitMethod(@NotNull GrMethod method) { super.visitMethod(method); final GrOpenBlock block = method.getBlock(); if (block != null) { processLocalVars(block); } if (method.isConstructor()) { processFieldsInConstructors(method); } } @Override public void visitFile(@NotNull GroovyFileBase file) { super.visitFile(file); if (file instanceof GroovyFile && file.isScript()) { processLocalVars(file); } } @Override public void visitField(@NotNull GrField field) { super.visitField(field); if (field instanceof LightElement) { return; } final GrExpression initializer = field.getInitializerGroovy(); if (initializer != null) { processLocalVars(initializer); } if (field.hasModifierProperty(PsiModifier.FINAL)) { if (!isFieldInitialized(field)) { registerError(field.getNameIdentifierGroovy(), GroovyBundle.message("variable.0.might.not.have.been.initialized", field.getName()), LocalQuickFix.EMPTY_ARRAY, ProblemHighlightType.GENERIC_ERROR_OR_WARNING); } } } @Override public void visitReferenceExpression(@NotNull GrReferenceExpression ref) { super.visitReferenceExpression(ref); final PsiElement resolved = ref.resolve(); if (resolved instanceof GrField field && ((GrField)resolved).hasModifierProperty(PsiModifier.FINAL)) { final PsiClass containingClass = field.getContainingClass(); if (PsiUtil.isLValue(ref)) { if (containingClass == null || !PsiTreeUtil.isAncestor(containingClass, ref, true)) { registerError(ref, GroovyBundle.message("cannot.assign.a.value.to.final.field.0", field.getName()), LocalQuickFix.EMPTY_ARRAY, ProblemHighlightType.GENERIC_ERROR_OR_WARNING); } } else if (PsiUtil.isUsedInIncOrDec(ref)) { if (containingClass == null || !isInsideConstructorOrInitializer(containingClass, ref, field.hasModifierProperty(PsiModifier.STATIC))) { registerError(ref, GroovyBundle.message("cannot.assign.a.value.to.final.field.0", field.getName()), LocalQuickFix.EMPTY_ARRAY, ProblemHighlightType.GENERIC_ERROR_OR_WARNING); } } } else if (resolved instanceof GrParameter && ((GrParameter)resolved).getDeclarationScope() instanceof GrMethod && ((GrParameter)resolved).hasModifierProperty(PsiModifier.FINAL) && PsiUtil.isUsedInIncOrDec(ref)) { registerError(ref, GroovyBundle.message("cannot.assign.a.value.to.final.parameter.0", ((GrParameter)resolved).getName()), LocalQuickFix.EMPTY_ARRAY, ProblemHighlightType.GENERIC_ERROR_OR_WARNING); } } @Override public void visitClassInitializer(@NotNull GrClassInitializer initializer) { super.visitClassInitializer(initializer); processLocalVars(initializer.getBlock()); processFieldsInClassInitializer(initializer); } private void processFieldsInConstructors(@NotNull GrMethod constructor) { final GrOpenBlock block = constructor.getBlock(); if (block == null) return; final GrTypeDefinition clazz = (GrTypeDefinition)constructor.getContainingClass(); if (clazz == null) return; final GrClassInitializer[] initializers = clazz.getInitializers(); final List<GrField> fields = getFinalFields(clazz); Set<GrVariable> initializedFields = new HashSet<>(); appendFieldInitializedInDeclaration(false, fields, initializedFields); appendFieldsInitializedInClassInitializer(initializers, null, false, fields, initializedFields); appendInitializationFromChainedConstructors(constructor, fields, initializedFields); final GroovyControlFlow flow = buildFlowForField(block); final Set<GrVariable> variables = buildVarSet(fields, false); highlightInvalidWriteAccess(flow, variables, initializedFields); } private void processFieldsInClassInitializer(@NotNull GrClassInitializer initializer) { final GrTypeDefinition clazz = (GrTypeDefinition)initializer.getContainingClass(); if (clazz == null) return; final boolean isStatic = initializer.isStatic(); final GrClassInitializer[] initializers = clazz.getInitializers(); final List<GrField> fields = getFinalFields(clazz); Set<GrVariable> initializedFields = new HashSet<>(); appendFieldInitializedInDeclaration(isStatic, fields, initializedFields); appendFieldsInitializedInClassInitializer(initializers, initializer, isStatic, fields, initializedFields); final GroovyControlFlow flow = buildFlowForField(initializer.getBlock()); final Set<GrVariable> variables = buildVarSet(fields, isStatic); highlightInvalidWriteAccess(flow, variables, initializedFields); } private void processLocalVars(@NotNull GroovyPsiElement scope) { final MultiMap<PsiElement, GrVariable> scopes = collectVariables(scope); for (final Map.Entry<PsiElement, Collection<GrVariable>> entry : scopes.entrySet()) { final PsiElement scopeToProcess = entry.getKey(); final Set<GrVariable> forInParameters = new HashSet<>(); final Set<GrVariable> variables = new HashSet<>(); for (final GrVariable var : entry.getValue()) { variables.add(var); if (var instanceof GrParameter && ((GrParameter)var).getDeclarationScope() instanceof GrForStatement) { forInParameters.add(var); } } final GroovyControlFlow flow = getFlow(scopeToProcess); highlightInvalidWriteAccess(flow, variables, forInParameters); } } private void highlightInvalidWriteAccess(@NotNull GroovyControlFlow flow, @NotNull Set<GrVariable> variables, @NotNull Set<GrVariable> initializedVariables) { final List<ReadWriteVariableInstruction> result = InvalidWriteAccessSearcher.findInvalidWriteAccess(flow, variables, initializedVariables); if (result == null) return; for (final ReadWriteVariableInstruction instruction : result) { int descriptor = instruction.getDescriptor(); if (!(flow.getVarIndices()[descriptor] instanceof ResolvedVariableDescriptor)) continue; PsiElement element = instruction.getElement(); if (variables.contains(((ResolvedVariableDescriptor)flow.getVarIndices()[descriptor]).getVariable()) && element != null) { registerError(element, GroovyBundle.message("cannot.assign.a.value.to.final.field.0", descriptor), LocalQuickFix.EMPTY_ARRAY, ProblemHighlightType.GENERIC_ERROR_OR_WARNING); } } } }; }