annotations.xml syntax checker

This commit is contained in:
Alexey Kudravtsev
2016-11-14 15:10:42 +03:00
parent 2999ef114a
commit 94a666a0df
13 changed files with 221 additions and 52 deletions
@@ -158,7 +158,7 @@ public abstract class BaseExternalAnnotationsManager extends ExternalAnnotations
}
@NotNull
private MostlySingularMultiMap<String, AnnotationData> getDataFromFile(@NotNull PsiFile file) {
MostlySingularMultiMap<String, AnnotationData> getDataFromFile(@NotNull PsiFile file) {
Pair<MostlySingularMultiMap<String, AnnotationData>, Long> cached = myAnnotationFileToDataAndModStampCache.get(file);
long fileModificationStamp = file.getModificationStamp();
if (cached != null && cached.getSecond() == fileModificationStamp) {
@@ -336,16 +336,16 @@ public abstract class BaseExternalAnnotationsManager extends ExternalAnnotations
throw new UnsupportedOperationException();
}
protected void cacheExternalAnnotations(@SuppressWarnings("UnusedParameters") @NotNull String packageName,
@NotNull PsiFile fromFile,
@NotNull List<PsiFile> annotationFiles) {
void cacheExternalAnnotations(@SuppressWarnings("UnusedParameters") @NotNull String packageName,
@NotNull PsiFile fromFile,
@NotNull List<PsiFile> annotationFiles) {
VirtualFile virtualFile = fromFile.getVirtualFile();
if (virtualFile != null) {
myExternalAnnotationsCache.put(virtualFile, annotationFiles);
}
}
private static class AnnotationData {
static class AnnotationData {
private final String annotationClassFqName;
private final String annotationParameters;
@@ -357,7 +357,7 @@ public abstract class BaseExternalAnnotationsManager extends ExternalAnnotations
}
@NotNull
private PsiAnnotation getAnnotation(@NotNull BaseExternalAnnotationsManager context) {
PsiAnnotation getAnnotation(@NotNull BaseExternalAnnotationsManager context) {
PsiAnnotation a = myAnnotation;
if (a == null) {
String text = "@" + annotationClassFqName + (annotationParameters.isEmpty() ? "" : "(" + annotationParameters + ")");
@@ -0,0 +1,186 @@
/*
* 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 com.intellij.codeInsight;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.application.ex.PathManagerEx;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.impl.JavaAwareProjectJdkTableImpl;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.*;
import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiFormatUtil;
import com.intellij.testFramework.IdeaTestCase;
import com.intellij.testFramework.PsiTestUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MostlySingularMultiMap;
import com.intellij.xml.util.XmlUtil;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
public class ExternalAnnotationsManagerTest extends IdeaTestCase {
@Override
protected Sdk getTestProjectJdk() {
Sdk jdk = JavaAwareProjectJdkTableImpl.getInstanceEx().getInternalJdk();
Sdk sdk = PsiTestUtil.addJdkAnnotations(jdk);
String home = jdk.getHomeDirectory().getParent().getPath();
String toolsPath = home + "/lib/tools.jar!/";
VfsRootAccess.allowRootAccess(getTestRootDisposable(), home);
VirtualFile toolsJar = JarFileSystem.getInstance().findFileByPath(toolsPath);
Sdk plusTools = PsiTestUtil.addRootsToJdk(sdk, OrderRootType.CLASSES, toolsJar);
Collection<String> utilClassPath = PathManager.getUtilClassPath();
VirtualFile[] files = utilClassPath.stream()
.map(path -> path.endsWith(".jar") ?
JarFileSystem.getInstance() .findFileByPath(FileUtil.toSystemIndependentName(path) + "!/") :
LocalFileSystem.getInstance() .findFileByPath(FileUtil.toSystemIndependentName(path)))
.toArray(VirtualFile[]::new);
Sdk result = PsiTestUtil.addRootsToJdk(plusTools, OrderRootType.CLASSES, files);
return result;
}
public void testBundledAnnotationXmls() {
String root = PathManagerEx.getCommunityHomePath() + "/java/jdkAnnotations";
findAnnotationsXmlAndCheck(root);
}
private void findAnnotationsXmlAndCheck(String root) {
VirtualFile jdkAnnoRoot = LocalFileSystem.getInstance().findFileByPath(root);
VfsUtilCore.visitChildrenRecursively(jdkAnnoRoot, new VirtualFileVisitor() {
@Override
public boolean visitFile(@NotNull VirtualFile file) {
if (file.getName().equals("annotations.xml")) {
check(file);
}
return true;
}
});
}
// some android classes are missing in IDEA, e.g. android.support.annotation.NonNull
public void _testAndroidAnnotationsXml() {
VirtualFile lib = LocalFileSystem.getInstance().findFileByPath(PathManagerEx.getCommunityHomePath() + "/android/android/lib");
VirtualFile[] androidJars = Arrays.stream(lib.getChildren())
.map(file -> file.getName().endsWith(".jar") ?
JarFileSystem.getInstance().getJarRootForLocalFile(file) :
file)
.toArray(VirtualFile[]::new);
ApplicationManager.getApplication().runWriteAction(() -> ProjectRootManager.getInstance(getProject())
.setProjectSdk(PsiTestUtil.addRootsToJdk(getTestProjectJdk(), OrderRootType.CLASSES, androidJars)));
String root = PathManagerEx.getCommunityHomePath() + "/android/android/annotations";
findAnnotationsXmlAndCheck(root);
}
private void check(VirtualFile file) {
//System.out.println("file = " + file);
ExternalAnnotationsManagerImpl manager = (ExternalAnnotationsManagerImpl)ExternalAnnotationsManager.getInstance(getProject());
PsiFile psiFile = getPsiManager().findFile(file);
MostlySingularMultiMap<String, BaseExternalAnnotationsManager.AnnotationData> map = manager.getDataFromFile(psiFile);
for (String externalName : map.keySet()) {
checkExternalName(psiFile, externalName);
// 'annotation name="org.jetbrains.annotations.NotNull"' should have FQN
for (BaseExternalAnnotationsManager.AnnotationData annotationData : map.get(externalName)) {
PsiAnnotation annotation = annotationData.getAnnotation(manager);
String nameText = annotation.getNameReferenceElement().getText();
assertClassFqn(nameText, psiFile, externalName);
}
}
}
private PsiClass assertClassFqn(String text, PsiFile psiFile, String externalName) {
if (!PsiNameHelper.getInstance(getProject()).isQualifiedName(text) || !text.contains(".")) {
fail("'" + text + "' doesn't seem like a FQN", psiFile, externalName);
}
PsiClass aClass = JavaPsiFacade.getInstance(getProject()).findClass(text, GlobalSearchScope.allScope(getProject()));
if (aClass == null) {
fail("'" + text + "' doesn't resolve to a class", psiFile, externalName);
}
return aClass;
}
private static void fail(String error, PsiFile psiFile, String externalName) {
int offset = psiFile.getText().indexOf(XmlUtil.escape(externalName));
int line = PsiDocumentManager.getInstance(psiFile.getProject()).getDocument(psiFile).getLineNumber(offset);
fail(error + "\nFile: " + psiFile.getVirtualFile().getPath() + ":" + (line+1) + " (offset: "+offset+")");
}
private void checkExternalName(PsiFile psiFile, String externalName) {
// 'item name="java.lang.ClassLoader java.net.URL getResource(java.lang.String) 0"' should have all FQNs
String unescaped = StringUtil.unescapeXml(externalName);
List<String> words = StringUtil.split(unescaped, " ");
String className = words.get(0);
PsiClass aClass = assertClassFqn(className, psiFile, externalName);
if (words.size() == 1) return;
String rest = unescaped.substring(className.length() + " ".length());
if (rest.indexOf('(') == -1) {
// field
String field = StringUtil.trim(rest);
PsiField psiField = aClass.findFieldByName(field, false);
if (psiField == null) {
fail("Field '"+field+"' not found in class '"+aClass.getQualifiedName()+"'", psiFile, externalName);
}
return;
}
String methodName = ContainerUtil.getLastItem(StringUtil.getWordsIn(rest.substring(0, rest.indexOf('('))));
String methodSignature = rest.substring(0, rest.indexOf(')') + 1);
String methodExternalName = className + " " + methodSignature;
List<PsiMethod> methods = Arrays.stream(aClass.getMethods())
.filter(method -> methodExternalName.equals(PsiFormatUtil.getExternalName(method, false, Integer.MAX_VALUE)))
.collect(Collectors.toList());
boolean found = !methods.isEmpty();
if (!found) {
List<String> candidates = Arrays.stream(aClass.findMethodsByName(methodName, false))
.map(method -> XmlUtil.escape(PsiFormatUtil.getExternalName(method, false, Integer.MAX_VALUE)))
.collect(Collectors.toList());
String additionalMsg = candidates.isEmpty() ? "" : "\nMaybe you have meant one of these methods instead:\n"+StringUtil.join(candidates, "\n")+"\n";
fail("This method was not found in class '"+aClass.getQualifiedName()+"':\n"+"'"+methodSignature+"'"+additionalMsg, psiFile, externalName);
}
String parameterNumberText = StringUtil.trim(rest.substring(rest.indexOf(')') + 1));
if (parameterNumberText.isEmpty()) return;
try {
int paramNumber = Integer.parseInt(parameterNumberText);
PsiMethod method = methods.get(0);
if (method.getParameterList().getParametersCount() <= paramNumber) {
fail("Parameter number '"+paramNumber+"' is too big for a method '"+methodSignature+"'", psiFile, externalName);
}
}
catch (NumberFormatException e) {
fail("Parameter number is not an integer: '"+parameterNumberText+"'", psiFile, externalName);
}
}
}
@@ -65,9 +65,6 @@
<val name="intValues" val="{java.awt.BasicStroke.JOIN_BEVEL, java.awt.BasicStroke.JOIN_MITER, java.awt.BasicStroke.JOIN_ROUND}" />
</annotation>
</item>
<item name="java.awt.CardLayout void show(Container parent, java.lang.String) 1">
<annotation name="org.jetbrains.annotations.NonNls" />
</item>
<item name="java.awt.CardLayout void show(java.awt.Container, java.lang.String) 1">
<annotation name="org.jetbrains.annotations.NonNls" />
</item>
@@ -2,7 +2,7 @@
<item name='java.awt.datatransfer.DataFlavor DataFlavor(java.lang.String) 0'>
<annotation name='org.jetbrains.annotations.NonNls'/>
</item>
<item name='java.awt.datatransfer.DataFlavor DataFlavor(java.lang.String, java.lang.String,java.lang.ClassLoader) 0'>
<item name='java.awt.datatransfer.DataFlavor DataFlavor(java.lang.String, java.lang.String, java.lang.ClassLoader) 0'>
<annotation name='org.jetbrains.annotations.NonNls'/>
</item>
</root>
@@ -30,7 +30,7 @@
<val name="flags" val="{java.awt.event.HierarchyEvent.PARENT_CHANGED,java.awt.event.HierarchyEvent.DISPLAYABILITY_CHANGED,java.awt.event.HierarchyEvent.SHOWING_CHANGED}" />
</annotation>
</item>
<item name="java.awt.event.HierarchyEvent int getChangeFlags()">
<item name="java.awt.event.HierarchyEvent long getChangeFlags()">
<annotation name="org.intellij.lang.annotations.MagicConstant">
<val name="flags" val="{java.awt.event.HierarchyEvent.PARENT_CHANGED,java.awt.event.HierarchyEvent.DISPLAYABILITY_CHANGED,java.awt.event.HierarchyEvent.SHOWING_CHANGED}" />
</annotation>
@@ -37,12 +37,6 @@
<item name="java.lang.Class java.lang.reflect.Method getMethod(java.lang.String, java.lang.Class&lt;?&gt;...) 0">
<annotation name="org.jetbrains.annotations.NonNls" />
</item>
<item name="java.lang.Class java.lang.reflect.Method getMethod(java.lang.String, java.lang.Class...) 0">
<annotation name="org.jetbrains.annotations.NonNls" />
</item>
<item name="java.lang.ClassLoader InputStream getResourceAsStream(java.lang.String) 0">
<annotation name="org.jetbrains.annotations.NonNls" />
</item>
<item name="java.lang.ClassLoader java.io.InputStream getResourceAsStream(java.lang.String) 0">
<annotation name="org.jetbrains.annotations.NonNls" />
</item>
@@ -70,9 +64,6 @@
<item name="java.lang.Readable int read(java.nio.CharBuffer) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name="java.lang.Runtime Process exec(java.lang.String[]) 0">
<annotation name="org.jetbrains.annotations.NonNls" />
</item>
<item name="java.lang.Runtime java.lang.Process exec(java.lang.String[]) 0">
<annotation name="org.jetbrains.annotations.NonNls" />
</item>
@@ -1,5 +1,5 @@
<root>
<item name='java.security.MessageDigest MessageDigest java.security.getInstance(java.lang.String) 0'>
<item name='java.security.MessageDigest java.security.MessageDigest getInstance(java.lang.String) 0'>
<annotation name='org.jetbrains.annotations.NonNls'/>
</item>
</root>
+1 -1
View File
@@ -120,7 +120,7 @@
<val name="intValues" val="{java.sql.Statement.RETURN_GENERATED_KEYS, java.sql.Statement.NO_GENERATED_KEYS}" />
</annotation>
</item>
<item name="java.sql.Statement int execute(java.lang.String, int) 1">
<item name="java.sql.Statement boolean execute(java.lang.String, int) 1">
<annotation name="org.intellij.lang.annotations.MagicConstant">
<val name="intValues" val="{java.sql.Statement.RETURN_GENERATED_KEYS, java.sql.Statement.NO_GENERATED_KEYS}" />
</annotation>
@@ -612,21 +612,12 @@
<val name="targetIsContainer" val="true"/>
</annotation>
</item>
<item name="java.util.Collection boolean containsAll(java.util.Collection&lt;?&gt; c) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name="java.util.Collection boolean containsAll(java.util.Collection&lt;?&gt;) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name="java.util.Collection boolean removeAll(java.util.Collection&lt;?&gt; c) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name="java.util.Collection boolean removeAll(java.util.Collection&lt;?&gt;) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name="java.util.Collection boolean retainAll(java.util.Collection&lt;?&gt; c) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name="java.util.Collection boolean retainAll(java.util.Collection&lt;?&gt;) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
@@ -981,12 +972,6 @@
<val name="targetIsContainer" val="true"/>
</annotation>
</item>
<item name="java.util.List List&lt;E&gt; subList(int fromIndex, int toIndex)">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name="java.util.List ListIterator&lt;E&gt; listIterator()">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name="java.util.List T[] toArray(T[])">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
+10 -10
View File
@@ -322,6 +322,16 @@
<val name="intValues" val="{javax.swing.JTabbedPane.WRAP_TAB_LAYOUT, javax.swing.JTabbedPane.SCROLL_TAB_LAYOUT}" />
</annotation>
</item>
<item name="javax.swing.JTabbedPane int getTabLayoutPolicy()">
<annotation name="org.intellij.lang.annotations.MagicConstant">
<val name="intValues" val="{javax.swing.JTabbedPane.WRAP_TAB_LAYOUT, javax.swing.JTabbedPane.SCROLL_TAB_LAYOUT}" />
</annotation>
</item>
<item name="javax.swing.JTabbedPane int getTabPlacement()">
<annotation name="org.intellij.lang.annotations.MagicConstant">
<val name="intValues" val="{javax.swing.SwingConstants.TOP, javax.swing.SwingConstants.BOTTOM, javax.swing.SwingConstants.LEFT, javax.swing.SwingConstants.RIGHT}" />
</annotation>
</item>
<item name="javax.swing.JTable JTable(java.lang.Object[][], java.lang.Object[]) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
@@ -331,16 +341,6 @@
<item name="javax.swing.JTable int columnAtPoint(java.awt.Point) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name="javax.swing.JTable int getTabLayoutPolicy()">
<annotation name="org.intellij.lang.annotations.MagicConstant">
<val name="intValues" val="{javax.swing.JTabbedPane.WRAP_TAB_LAYOUT, javax.swing.JTabbedPane.SCROLL_TAB_LAYOUT}" />
</annotation>
</item>
<item name="javax.swing.JTable int getTabPlacement()">
<annotation name="org.intellij.lang.annotations.MagicConstant">
<val name="intValues" val="{javax.swing.SwingConstants.TOP, javax.swing.SwingConstants.BOTTOM, javax.swing.SwingConstants.LEFT, javax.swing.SwingConstants.RIGHT}" />
</annotation>
</item>
<item name="javax.swing.JTable int rowAtPoint(java.awt.Point) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
@@ -10,7 +10,7 @@
<val name="intValues" val="{javax.swing.SwingConstants.NORTH,javax.swing.SwingConstants.SOUTH,javax.swing.SwingConstants.EAST,javax.swing.SwingConstants.WEST}" />
</annotation>
</item>
<item name="javax.swing.plaf.basic.BasicArrowButton void getDirection()">
<item name="javax.swing.plaf.basic.BasicArrowButton int getDirection()">
<annotation name="org.intellij.lang.annotations.MagicConstant">
<val name="intValues" val="{javax.swing.SwingConstants.NORTH,javax.swing.SwingConstants.SOUTH,javax.swing.SwingConstants.EAST,javax.swing.SwingConstants.WEST}" />
</annotation>
+3 -3
View File
@@ -36,13 +36,13 @@
<item name="org.jdom.Element Element(java.lang.String) 0">
<annotation name="org.jetbrains.annotations.NonNls" />
</item>
<item name="org.jdom.Element java.util.List getChildren()">
<item name="org.jdom.Element java.util.List&lt;org.jdom.Element&gt; getChildren()">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name="org.jdom.Element java.util.List getChildren(java.lang.String)">
<item name="org.jdom.Element java.util.List&lt;org.jdom.Element&gt; getChildren(java.lang.String)">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name="org.jdom.Element java.util.List getChildren(java.lang.String, org.jdom.Namespace)">
<item name="org.jdom.Element java.util.List&lt;org.jdom.Element&gt; getChildren(java.lang.String, org.jdom.Namespace)">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name='org.jdom.Element java.util.List&lt;org.jdom.Content&gt; getContent()'>
@@ -373,6 +373,14 @@ public class PsiTestUtil {
public static Sdk addJdkAnnotations(@NotNull Sdk sdk) {
String path = FileUtil.toSystemIndependentName(PlatformTestUtil.getCommunityPath()) + "/java/jdkAnnotations";
VirtualFile root = LocalFileSystem.getInstance().findFileByPath(path);
return addRootsToJdk(sdk, AnnotationOrderRootType.getInstance(), root);
}
@NotNull
@Contract(pure=true)
public static Sdk addRootsToJdk(@NotNull Sdk sdk,
@NotNull OrderRootType rootType,
@NotNull VirtualFile... roots) {
Sdk clone;
try {
clone = (Sdk)sdk.clone();
@@ -381,7 +389,9 @@ public class PsiTestUtil {
throw new RuntimeException(e);
}
SdkModificator sdkModificator = clone.getSdkModificator();
sdkModificator.addRoot(root, AnnotationOrderRootType.getInstance());
for (VirtualFile root : roots) {
sdkModificator.addRoot(root, rootType);
}
sdkModificator.commitChanges();
return clone;
}