/* * Copyright 2013-2017 consulo.io * * 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 consulo.csharp.ide.highlight.check.impl; import java.util.ArrayList; import java.util.List; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import consulo.annotations.RequiredDispatchThread; import consulo.annotations.RequiredReadAction; import consulo.annotations.RequiredWriteAction; import consulo.csharp.ide.highlight.CSharpHighlightContext; import consulo.csharp.ide.highlight.check.CompilerCheck; import consulo.csharp.lang.psi.*; import consulo.csharp.lang.psi.impl.source.CSharpAwaitExpressionImpl; import consulo.csharp.lang.psi.impl.source.CSharpCatchStatementImpl; import consulo.csharp.lang.psi.impl.source.CSharpConstantExpressionImpl; import consulo.csharp.lang.psi.impl.source.CSharpDictionaryInitializerImpl; import consulo.csharp.lang.psi.impl.source.CSharpFinallyStatementImpl; import consulo.csharp.lang.psi.impl.source.CSharpGenericParameterListImpl; import consulo.csharp.lang.psi.impl.source.CSharpLambdaExpressionImpl; import consulo.csharp.lang.psi.CSharpLocalVariable; import consulo.csharp.lang.psi.CSharpTypeDeclaration; import consulo.csharp.lang.psi.impl.source.CSharpTupleExpressionImpl; import consulo.csharp.module.extension.CSharpLanguageVersion; import consulo.csharp.module.extension.CSharpMutableModuleExtension; import consulo.csharp.module.extension.CSharpSimpleModuleExtension; import consulo.dotnet.psi.DotNetExpression; import consulo.dotnet.psi.DotNetModifierList; import consulo.dotnet.psi.DotNetParameter; import consulo.dotnet.psi.DotNetStatement; import consulo.dotnet.resolve.DotNetTypeRef; import com.intellij.codeInsight.daemon.impl.HighlightInfoType; import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction; import com.intellij.lang.ASTNode; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ModifiableRootModel; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.psi.PsiElement; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.Function; import com.intellij.util.IncorrectOperationException; /** * @author VISTALL * @since 15.05.14 */ public class CS1644 extends CompilerCheck<PsiElement> { public static class SetLanguageVersionFix extends PsiElementBaseIntentionAction { private CSharpLanguageVersion myLanguageVersion; public SetLanguageVersionFix(CSharpLanguageVersion languageVersion) { myLanguageVersion = languageVersion; setText("Set language version to '" + myLanguageVersion.getPresentableName() + "'"); } @Override @RequiredWriteAction public void invoke(@NotNull Project project, Editor editor, @NotNull PsiElement element) throws IncorrectOperationException { CSharpSimpleModuleExtension extension = ModuleUtilCore.getExtension(element, CSharpSimpleModuleExtension.class); if(extension == null || !extension.isSupportedLanguageVersion(myLanguageVersion)) { return; } ModuleRootManager rootManager = ModuleRootManager.getInstance(extension.getModule()); ModifiableRootModel modifiableModel = rootManager.getModifiableModel(); final CSharpMutableModuleExtension mutable = modifiableModel.getExtension(CSharpMutableModuleExtension.class); assert mutable != null; mutable.setLanguageVersion(myLanguageVersion); modifiableModel.commit(); } @Override @RequiredDispatchThread public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement element) { CSharpSimpleModuleExtension extension = ModuleUtilCore.getExtension(element, CSharpSimpleModuleExtension.class); return extension != null && extension.isSupportedLanguageVersion(myLanguageVersion) && extension.getLanguageVersion().ordinal() < myLanguageVersion.ordinal(); } @NotNull @Override public String getFamilyName() { return "C#"; } } public static class Feature { private String myName; private CSharpLanguageVersion myLanguageVersion; private Function<PsiElement, PsiElement> myFunc; Feature(String name, CSharpLanguageVersion languageVersion, Function<PsiElement, PsiElement> processor) { myName = name; myLanguageVersion = languageVersion; myFunc = processor; } } private List<Feature> myFeatures = new ArrayList<Feature>() { { add(new Feature("lambda expressions", CSharpLanguageVersion._3_0, new Function<PsiElement, PsiElement>() { @Override public PsiElement fun(PsiElement element) { if(element instanceof CSharpLambdaExpressionImpl) { return element; } return null; } })); add(new Feature("generics", CSharpLanguageVersion._2_0, new Function<PsiElement, PsiElement>() { @Override public PsiElement fun(PsiElement element) { if(element instanceof CSharpGenericParameterListImpl) { return element; } else if(element.getNode() != null && (element.getNode().getElementType() == CSharpElements.TYPE_ARGUMENTS || element.getNode().getElementType() == CSharpStubElements .TYPE_ARGUMENTS)) { return element; } return null; } })); add(new Feature("implicitly typed local variable", CSharpLanguageVersion._3_0, new Function<PsiElement, PsiElement>() { @Override @RequiredReadAction public PsiElement fun(PsiElement element) { if(element instanceof CSharpLocalVariable && ((CSharpLocalVariable) element).toTypeRef(false) == DotNetTypeRef.AUTO_TYPE) { return ((CSharpLocalVariable) element).getType(); } return null; } })); add(new Feature("extension methods", CSharpLanguageVersion._3_0, new Function<PsiElement, PsiElement>() { @Override @RequiredReadAction public PsiElement fun(PsiElement element) { if(element instanceof CSharpMethodDeclaration) { DotNetParameter[] parameters = ((CSharpMethodDeclaration) element).getParameters(); if(parameters.length > 0) { DotNetModifierList modifierList = parameters[0].getModifierList(); if(modifierList != null) { PsiElement modifier = modifierList.getModifierElement(CSharpModifier.THIS); if(modifier != null) { return modifier; } } } } return null; } })); add(new Feature("using static members", CSharpLanguageVersion._6_0, new Function<PsiElement, PsiElement>() { @Override public PsiElement fun(PsiElement element) { return element instanceof CSharpUsingTypeStatement ? element : null; } })); add(new Feature("parameterless struct ctors", CSharpLanguageVersion._6_0, new Function<PsiElement, PsiElement>() { @Override public PsiElement fun(PsiElement element) { if(element instanceof CSharpConstructorDeclaration) { PsiElement parent = element.getParent(); if(parent instanceof CSharpTypeDeclaration && ((CSharpTypeDeclaration) parent).isStruct() && ((CSharpConstructorDeclaration) element).getParameters().length == 0) { return ((CSharpConstructorDeclaration) element).getNameIdentifier(); } } return null; } })); add(new Feature("property initializer", CSharpLanguageVersion._6_0, new Function<PsiElement, PsiElement>() { @Override @RequiredReadAction public PsiElement fun(PsiElement element) { if(element instanceof CSharpPropertyDeclaration) { DotNetExpression initializer = ((CSharpPropertyDeclaration) element).getInitializer(); if(initializer != null) { return initializer; } } return null; } })); add(new Feature("expression-bodied members", CSharpLanguageVersion._6_0, new Function<PsiElement, PsiElement>() { @Override public PsiElement fun(PsiElement element) { if(element instanceof CSharpMethodDeclaration) { PsiElement codeBlock = ((CSharpMethodDeclaration) element).getCodeBlock(); if(codeBlock instanceof DotNetExpression) { return codeBlock; } } else if(element instanceof CSharpFieldDeclaration) { ASTNode darrowNode = element.getNode().findChildByType(CSharpTokens.DARROW); if(darrowNode != null) { return darrowNode.getPsi(); } } return null; } })); add(new Feature("exception filters", CSharpLanguageVersion._6_0, new Function<PsiElement, PsiElement>() { @Override public PsiElement fun(PsiElement element) { if(element instanceof CSharpCatchStatementImpl) { return ((CSharpCatchStatementImpl) element).getFilterExpression(); } return null; } })); add(new Feature("null propagation", CSharpLanguageVersion._6_0, new Function<PsiElement, PsiElement>() { @Override public PsiElement fun(PsiElement element) { if(element instanceof CSharpReferenceExpression) { PsiElement memberAccessElement = ((CSharpReferenceExpression) element).getMemberAccessElement(); if(memberAccessElement != null && memberAccessElement.getNode().getElementType() == CSharpTokens.NULLABE_CALL) { return memberAccessElement; } } return null; } })); add(new Feature("string interpolation", CSharpLanguageVersion._6_0, new Function<PsiElement, PsiElement>() { @Override @RequiredReadAction public PsiElement fun(PsiElement element) { if(element instanceof CSharpConstantExpressionImpl) { return ((CSharpConstantExpressionImpl) element).getLiteralType() == CSharpTokensImpl.INTERPOLATION_STRING_LITERAL ? element : null; } return null; } })); add(new Feature("dictionary initializer", CSharpLanguageVersion._6_0, new Function<PsiElement, PsiElement>() { @Override public PsiElement fun(PsiElement element) { if(element instanceof CSharpDictionaryInitializerImpl) { return element; } return null; } })); add(new Feature("await in catch/finally", CSharpLanguageVersion._6_0, new Function<PsiElement, PsiElement>() { @Override @RequiredReadAction public PsiElement fun(PsiElement element) { if(element instanceof CSharpAwaitExpressionImpl) { DotNetStatement statement = PsiTreeUtil.getParentOfType(element, CSharpFinallyStatementImpl.class, CSharpCatchStatementImpl.class); if(statement != null) { return ((CSharpAwaitExpressionImpl) element).getAwaitKeywordElement(); } return null; } return null; } })); add(new Feature("asynchronous functions", CSharpLanguageVersion._4_0, new Function<PsiElement, PsiElement>() { @Override @RequiredReadAction public PsiElement fun(PsiElement element) { if(element instanceof CSharpSimpleLikeMethodAsElement) { DotNetModifierList modifierList = ((CSharpSimpleLikeMethodAsElement) element).getModifierList(); if(modifierList == null) { return null; } return modifierList.getModifierElement(CSharpModifier.ASYNC); } return null; } })); add(new Feature("named arguments", CSharpLanguageVersion._4_0, new Function<PsiElement, PsiElement>() { @Override public PsiElement fun(PsiElement element) { return element instanceof CSharpNamedCallArgument ? element : null; } })); add(new Feature("tuples", CSharpLanguageVersion._7_0, new Function<PsiElement, PsiElement>() { @Override public PsiElement fun(PsiElement psiElement) { return psiElement instanceof CSharpTupleType || psiElement instanceof CSharpTupleExpressionImpl ? psiElement : null; } })); } }; private TokenSet myAllKeywords = TokenSet.orSet(CSharpTokenSets.KEYWORDS, CSharpSoftTokens.ALL); @RequiredReadAction @Nullable @Override public CompilerCheckBuilder checkImpl(@NotNull CSharpLanguageVersion languageVersion, @NotNull CSharpHighlightContext highlightContext, @NotNull PsiElement element) { for(Feature feature : myFeatures) { if(languageVersion.ordinal() < feature.myLanguageVersion.ordinal()) { PsiElement fun = feature.myFunc.fun(element); if(fun == null) { continue; } CompilerCheckBuilder result = newBuilder(fun, feature.myName, languageVersion.getPresentableName()); result.addQuickFix(new SetLanguageVersionFix(feature.myLanguageVersion)); IElementType elementType = fun.getNode().getElementType(); if(!myAllKeywords.contains(elementType)) { boolean foundKeywordAndItSolo = false; ASTNode[] children = fun.getNode().getChildren(null); for(ASTNode child : children) { if(CSharpTokenSets.COMMENTS.contains(child.getElementType()) || child.getElementType() == CSharpTokenSets.WHITE_SPACE) { continue; } if(myAllKeywords.contains(child.getElementType())) { foundKeywordAndItSolo = true; } else if(foundKeywordAndItSolo) // if we found keyword but parent have other elements - we cant highlight as error { return result; } } if(foundKeywordAndItSolo) { result.setHighlightInfoType(HighlightInfoType.WRONG_REF); } } else { result.setHighlightInfoType(HighlightInfoType.WRONG_REF); } return result; } } return null; } }