From b3a96dcba31c43e0472fc036da2c7145d8b9c18a Mon Sep 17 00:00:00 2001 From: Daniel Espendiller Date: Wed, 20 Dec 2017 20:53:25 +0100 Subject: [PATCH] fix assets reading and provide explicit resolving for asset files instead of rescanning them to improve performance an drop massive `opendirectoryd` cpu time #809 #1118 --- .../asset/AssetDirectoryReader.java | 200 ++++++++++++++++++ .../asset/{dic => }/AssetEnum.java | 2 +- .../idea/symfony2plugin/asset/AssetFile.java | 46 ++++ .../asset/AssetGoToDeclarationHandler.java | 18 +- .../asset/AssetLookupElement.java | 2 - .../asset/dic/AssetDirectoryReader.java | 98 --------- .../symfony2plugin/asset/dic/AssetFile.java | 43 ---- .../provider/AssetCompletionProvider.java | 4 +- .../TwigTemplateCompletionContributor.java | 3 +- .../TwigHtmlCompletionContributor.java | 4 +- .../TwigAssetMissingInspection.java | 40 ++-- .../templating/util/TwigUtil.java | 52 +---- .../tests/asset/AssetDirectoryReaderTest.java | 42 ++++ 13 files changed, 341 insertions(+), 213 deletions(-) create mode 100644 src/fr/adrienbrault/idea/symfony2plugin/asset/AssetDirectoryReader.java rename src/fr/adrienbrault/idea/symfony2plugin/asset/{dic => }/AssetEnum.java (72%) create mode 100644 src/fr/adrienbrault/idea/symfony2plugin/asset/AssetFile.java delete mode 100644 src/fr/adrienbrault/idea/symfony2plugin/asset/dic/AssetDirectoryReader.java delete mode 100644 src/fr/adrienbrault/idea/symfony2plugin/asset/dic/AssetFile.java create mode 100644 tests/fr/adrienbrault/idea/symfony2plugin/tests/asset/AssetDirectoryReaderTest.java diff --git a/src/fr/adrienbrault/idea/symfony2plugin/asset/AssetDirectoryReader.java b/src/fr/adrienbrault/idea/symfony2plugin/asset/AssetDirectoryReader.java new file mode 100644 index 000000000..4132cc117 --- /dev/null +++ b/src/fr/adrienbrault/idea/symfony2plugin/asset/AssetDirectoryReader.java @@ -0,0 +1,200 @@ +package fr.adrienbrault.idea.symfony2plugin.asset; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileVisitor; +import com.intellij.psi.PsiDirectory; +import com.jetbrains.php.PhpIndex; +import fr.adrienbrault.idea.symfony2plugin.Settings; +import fr.adrienbrault.idea.symfony2plugin.util.SymfonyBundleUtil; +import fr.adrienbrault.idea.symfony2plugin.util.dict.SymfonyBundle; +import org.apache.commons.lang.StringUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author Daniel Espendiller + */ +public class AssetDirectoryReader { + + public static AssetDirectoryReader INSTANCE = new AssetDirectoryReader(); + + final private boolean includeBundleDir; + + @NotNull + final private Collection filterExtension = new HashSet<>(); + + public AssetDirectoryReader() { + includeBundleDir = false; + } + + public AssetDirectoryReader(@NotNull String[] filterExtension, boolean includeBundleDir) { + this.includeBundleDir = includeBundleDir; + this.filterExtension.addAll(Arrays.asList(filterExtension)); + } + + @Nullable + private static VirtualFile getProjectAssetRoot(@NotNull Project project) { + VirtualFile projectDirectory = project.getBaseDir(); + String webDirectoryName = Settings.getInstance(project).directoryToWeb; + return VfsUtil.findRelativeFile(projectDirectory, webDirectoryName.split("/")); + } + + @NotNull + public Collection getAssetFiles(@NotNull Project project) { + Collection files = new ArrayList<>(); + + VirtualFile webDirectory = getProjectAssetRoot(project); + if (null == webDirectory) { + return files; + } + + VfsUtil.visitChildrenRecursively(webDirectory, new VirtualFileVisitor() { + @Override + public boolean visitFile(@NotNull VirtualFile virtualFile) { + if(isValidFile(virtualFile)) { + files.add(new AssetFile(virtualFile, AssetEnum.Position.Web, webDirectory)); + } + return super.visitFile(virtualFile); + } + }); + + if(!this.includeBundleDir) { + return files; + } + + SymfonyBundleUtil symfonyBundleUtil = new SymfonyBundleUtil(PhpIndex.getInstance(project)); + for(SymfonyBundle bundle : symfonyBundleUtil.getBundles()) { + PsiDirectory bundleDirectory = bundle.getDirectory(); + if(null == bundleDirectory) { + continue; + } + + VirtualFile bundleDirectoryVirtual = bundleDirectory.getVirtualFile(); + VirtualFile resourceDirectory = VfsUtil.findRelativeFile(bundleDirectoryVirtual, "Resources"); + + if (null != resourceDirectory) { + VfsUtil.visitChildrenRecursively(resourceDirectory, new VirtualFileVisitor() { + @Override + public boolean visitFile(@NotNull VirtualFile virtualFile) { + if(isValidFile(virtualFile)) { + files.add(new AssetFile(virtualFile, AssetEnum.Position.Bundle, bundleDirectoryVirtual, '@' + bundle.getName() + "/")); + } + return super.visitFile(virtualFile); + } + }); + } + } + + return files; + } + + /** + * '@SampleBundle/Resources/public/js/*' + * 'assets/js/*' + * 'assets/js/*.js' + */ + @NotNull + public Collection resolveAssetFile(@NotNull Project project, @NotNull String filename) { + String assetName = StringUtils.stripStart(filename.replace("\\", "/").replaceAll("/+", "/"), "/"); + + // '@SampleBundle/Resources/public/js/foo,js' + // TODO: '@SampleBundle/Resources/public/js/*' + // TODO: '@SampleBundle/Resources/public/js/*.js' + if(filename.startsWith("@")) { + Collection files = new ArrayList<>(); + + int i = filename.indexOf("/"); + if(i > 0) { + String relativeFilename = filename.substring(1, i); + SymfonyBundle bundle = new SymfonyBundleUtil(PhpIndex.getInstance(project)).getBundle(relativeFilename); + if(bundle != null) { + String assetPath = filename.substring(i + 1); + + Matcher matcher = Pattern.compile("^(.*[/\\\\])\\*([.\\w+]*)$").matcher(assetPath); + if (!matcher.find()) { + VirtualFile relative = bundle.getRelative(assetPath); + if(relative != null) { + files.add(relative); + } + } else { + // "/*" + // "/*.js" + PsiDirectory directory = bundle.getDirectory(); + if(directory != null) { + files.addAll(collectWildcardDirectories(matcher, directory.getVirtualFile())); + } + } + } + } + + return files; + } + + Collection files = new ArrayList<>(); + + VirtualFile webDirectory = getProjectAssetRoot(project); + if (null == webDirectory) { + return files; + } + + Matcher matcher = Pattern.compile("^(.*[/\\\\])\\*([.\\w+]*)$").matcher(assetName); + if (!matcher.find()) { + VirtualFile assetFile = VfsUtil.findRelativeFile(webDirectory, assetName.split("/")); + if(assetFile != null) { + files.add(assetFile); + } + } else { + // "/*" + // "/*.js" + files.addAll(collectWildcardDirectories(matcher, webDirectory)); + } + + return files; + } + + private Collection collectWildcardDirectories(@NotNull Matcher matcher, @NotNull VirtualFile directory) { + Collection files = new HashSet<>(); + + String pathName = matcher.group(1); + String fileExtension = matcher.group(2).length() > 0 ? matcher.group(2) : null; + + pathName = StringUtils.stripEnd(pathName, "/"); + + if(fileExtension == null) { + // @TODO: filter files + // 'assets/js/*' + VirtualFile assetFile = VfsUtil.findRelativeFile(directory, pathName.split("/")); + if(assetFile != null) { + files.add(assetFile); + } + } else { + // @TODO: filter files + // 'assets/js/*.js' + VirtualFile assetFile = VfsUtil.findRelativeFile(directory, pathName.split("/")); + if(assetFile != null) { + files.add(assetFile); + } + } + + return files; + } + + private boolean isValidFile(@NotNull VirtualFile virtualFile) { + if (virtualFile.isDirectory()) { + return false; + } + + if(filterExtension.size() == 0) { + return true; + } + + String extension = virtualFile.getExtension(); + return extension != null && this.filterExtension.contains(extension); + } +} diff --git a/src/fr/adrienbrault/idea/symfony2plugin/asset/dic/AssetEnum.java b/src/fr/adrienbrault/idea/symfony2plugin/asset/AssetEnum.java similarity index 72% rename from src/fr/adrienbrault/idea/symfony2plugin/asset/dic/AssetEnum.java rename to src/fr/adrienbrault/idea/symfony2plugin/asset/AssetEnum.java index b256d80ef..66ead664f 100644 --- a/src/fr/adrienbrault/idea/symfony2plugin/asset/dic/AssetEnum.java +++ b/src/fr/adrienbrault/idea/symfony2plugin/asset/AssetEnum.java @@ -1,4 +1,4 @@ -package fr.adrienbrault.idea.symfony2plugin.asset.dic; +package fr.adrienbrault.idea.symfony2plugin.asset; /** * @author Daniel Espendiller diff --git a/src/fr/adrienbrault/idea/symfony2plugin/asset/AssetFile.java b/src/fr/adrienbrault/idea/symfony2plugin/asset/AssetFile.java new file mode 100644 index 000000000..799f20772 --- /dev/null +++ b/src/fr/adrienbrault/idea/symfony2plugin/asset/AssetFile.java @@ -0,0 +1,46 @@ +package fr.adrienbrault.idea.symfony2plugin.asset; + +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; + +/** + * @author Daniel Espendiller + */ +public class AssetFile { + @NotNull + private VirtualFile assetFile; + + @NotNull + private AssetEnum.Position assetPosition; + + @NotNull + private VirtualFile relativeFolder; + + private String prefix = ""; + + public AssetFile(@NotNull VirtualFile assetFile, @NotNull AssetEnum.Position assetPosition, @NotNull VirtualFile relativeFolder, @NotNull String prefix) { + this(assetFile, assetPosition, relativeFolder); + this.prefix = prefix; + } + + public AssetFile(@NotNull VirtualFile assetFile, @NotNull AssetEnum.Position assetPosition, @NotNull VirtualFile relativeFolder) { + this.assetFile = assetFile; + this.assetPosition = assetPosition; + this.relativeFolder = relativeFolder; + } + + @NotNull + public VirtualFile getFile() { + return assetFile; + } + + @NotNull + public AssetEnum.Position getAssetPosition() { + return assetPosition; + } + + public String toString() { + return this.prefix + VfsUtil.getRelativePath(assetFile, relativeFolder, '/'); + } +} diff --git a/src/fr/adrienbrault/idea/symfony2plugin/asset/AssetGoToDeclarationHandler.java b/src/fr/adrienbrault/idea/symfony2plugin/asset/AssetGoToDeclarationHandler.java index 0026cce66..659beb652 100644 --- a/src/fr/adrienbrault/idea/symfony2plugin/asset/AssetGoToDeclarationHandler.java +++ b/src/fr/adrienbrault/idea/symfony2plugin/asset/AssetGoToDeclarationHandler.java @@ -12,8 +12,8 @@ import org.apache.commons.lang.ArrayUtils; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.List; +import java.util.Collection; +import java.util.HashSet; /** * @author Daniel Espendiller @@ -23,7 +23,6 @@ public class AssetGoToDeclarationHandler implements GotoDeclarationHandler { @Nullable @Override public PsiElement[] getGotoDeclarationTargets(PsiElement psiElement, int i, Editor editor) { - if(!Symfony2ProjectComponent.isEnabled(psiElement)) { return null; } @@ -33,9 +32,18 @@ public PsiElement[] getGotoDeclarationTargets(PsiElement psiElement, int i, Edit return null; } - List psiElements = new ArrayList<>(); + Collection psiElements = new HashSet<>(); for (VirtualFile virtualFile : TwigUtil.resolveAssetsFiles(psiElement.getProject(), psiElement.getText(), fileExtensionFilterIfValidTag)) { - psiElements.add(PsiManager.getInstance(psiElement.getProject()).findFile(virtualFile)); + PsiElement target; + if(virtualFile.isDirectory()) { + target = PsiManager.getInstance(psiElement.getProject()).findDirectory(virtualFile); + } else { + target = PsiManager.getInstance(psiElement.getProject()).findFile(virtualFile); + } + + if(target != null) { + psiElements.add(target); + } } return psiElements.toArray(new PsiElement[psiElements.size()]); diff --git a/src/fr/adrienbrault/idea/symfony2plugin/asset/AssetLookupElement.java b/src/fr/adrienbrault/idea/symfony2plugin/asset/AssetLookupElement.java index 9efbc2de6..8783a2dc5 100644 --- a/src/fr/adrienbrault/idea/symfony2plugin/asset/AssetLookupElement.java +++ b/src/fr/adrienbrault/idea/symfony2plugin/asset/AssetLookupElement.java @@ -6,8 +6,6 @@ import com.intellij.codeInsight.lookup.LookupElementPresentation; import com.intellij.openapi.project.Project; import com.intellij.util.IconUtil; -import fr.adrienbrault.idea.symfony2plugin.asset.dic.AssetEnum; -import fr.adrienbrault.idea.symfony2plugin.asset.dic.AssetFile; import fr.adrienbrault.idea.symfony2plugin.util.dict.ResourceFileInsertHandler; import org.jetbrains.annotations.NotNull; diff --git a/src/fr/adrienbrault/idea/symfony2plugin/asset/dic/AssetDirectoryReader.java b/src/fr/adrienbrault/idea/symfony2plugin/asset/dic/AssetDirectoryReader.java deleted file mode 100644 index 83c555da2..000000000 --- a/src/fr/adrienbrault/idea/symfony2plugin/asset/dic/AssetDirectoryReader.java +++ /dev/null @@ -1,98 +0,0 @@ -package fr.adrienbrault.idea.symfony2plugin.asset.dic; - -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.VirtualFileVisitor; -import com.intellij.psi.PsiDirectory; -import com.jetbrains.php.PhpIndex; -import fr.adrienbrault.idea.symfony2plugin.Settings; -import fr.adrienbrault.idea.symfony2plugin.util.SymfonyBundleUtil; -import fr.adrienbrault.idea.symfony2plugin.util.dict.SymfonyBundle; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.*; - -/** - * @author Daniel Espendiller - */ -public class AssetDirectoryReader { - final private boolean includeBundleDir; - - @NotNull - final private Collection filterExtension = new HashSet<>(); - - public AssetDirectoryReader() { - includeBundleDir = false; - } - - public AssetDirectoryReader(@NotNull String[] filterExtension, boolean includeBundleDir) { - this.includeBundleDir = includeBundleDir; - this.filterExtension.addAll(Arrays.asList(filterExtension)); - } - - @Nullable - public static VirtualFile getProjectAssetRoot(@NotNull Project project) { - VirtualFile projectDirectory = project.getBaseDir(); - String webDirectoryName = Settings.getInstance(project).directoryToWeb; - return VfsUtil.findRelativeFile(projectDirectory, webDirectoryName.split("/")); - } - - public List getAssetFiles(@NotNull Project project) { - List files = new ArrayList<>(); - - VirtualFile webDirectory = getProjectAssetRoot(project); - if (null == webDirectory) { - return files; - } - - VfsUtil.visitChildrenRecursively(webDirectory, new VirtualFileVisitor() { - @Override - public boolean visitFile(@NotNull VirtualFile virtualFile) { - if(isValidFile(virtualFile)) { - files.add(new AssetFile(virtualFile, AssetEnum.Position.Web, webDirectory)); - } - return super.visitFile(virtualFile); - } - }); - - if(!this.includeBundleDir) { - return files; - } - - SymfonyBundleUtil symfonyBundleUtil = new SymfonyBundleUtil(PhpIndex.getInstance(project)); - for(final SymfonyBundle bundle : symfonyBundleUtil.getBundles()) { - PsiDirectory bundleDirectory = bundle.getDirectory(); - if(null == bundleDirectory) { - continue; - } - - VirtualFile bundleDirectoryVirtual = bundleDirectory.getVirtualFile(); - VirtualFile resourceDirectory = VfsUtil.findRelativeFile(bundleDirectoryVirtual, "Resources"); - - if (null != resourceDirectory) { - VfsUtil.visitChildrenRecursively(resourceDirectory, new VirtualFileVisitor() { - @Override - public boolean visitFile(@NotNull VirtualFile virtualFile) { - if(isValidFile(virtualFile)) { - files.add(new AssetFile(virtualFile, AssetEnum.Position.Bundle, bundleDirectoryVirtual, '@' + bundle.getName() + "/")); - } - return super.visitFile(virtualFile); - } - }); - } - } - - return files; - } - - private boolean isValidFile(@NotNull VirtualFile virtualFile) { - if (this.filterExtension.size() == 0 || virtualFile.isDirectory()) { - return false; - } - - String extension = virtualFile.getExtension(); - return extension != null && this.filterExtension.contains(extension); - } -} diff --git a/src/fr/adrienbrault/idea/symfony2plugin/asset/dic/AssetFile.java b/src/fr/adrienbrault/idea/symfony2plugin/asset/dic/AssetFile.java deleted file mode 100644 index ccef621b8..000000000 --- a/src/fr/adrienbrault/idea/symfony2plugin/asset/dic/AssetFile.java +++ /dev/null @@ -1,43 +0,0 @@ -package fr.adrienbrault.idea.symfony2plugin.asset.dic; - -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.openapi.vfs.VirtualFile; - -/** - * @author Daniel Espendiller - */ -public class AssetFile { - - VirtualFile assetFile; - AssetEnum.Position assetPosition; - VirtualFile relativeFolder; - String prefix = ""; - - public AssetFile(VirtualFile assetFile, AssetEnum.Position assetPosition, VirtualFile relativeFolder, String prefix) { - this(assetFile, assetPosition, relativeFolder); - this.prefix = prefix; - } - - public AssetFile(VirtualFile assetFile, AssetEnum.Position assetPosition, VirtualFile relativeFolder) { - this.assetFile = assetFile; - this.assetPosition = assetPosition; - this.relativeFolder = relativeFolder; - } - - public VirtualFile getFile() { - return this.assetFile; - } - - public AssetEnum.Position getAssetPosition() { - return this.assetPosition; - } - - public VirtualFile getRelativeFolder() { - return this.relativeFolder; - } - - public String toString() { - return this.prefix + VfsUtil.getRelativePath(this.getFile(), this.getRelativeFolder(), '/'); - } - -} diff --git a/src/fr/adrienbrault/idea/symfony2plugin/asset/provider/AssetCompletionProvider.java b/src/fr/adrienbrault/idea/symfony2plugin/asset/provider/AssetCompletionProvider.java index a87ea257a..ebde04d79 100644 --- a/src/fr/adrienbrault/idea/symfony2plugin/asset/provider/AssetCompletionProvider.java +++ b/src/fr/adrienbrault/idea/symfony2plugin/asset/provider/AssetCompletionProvider.java @@ -9,8 +9,8 @@ import com.intellij.util.ProcessingContext; import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent; import fr.adrienbrault.idea.symfony2plugin.asset.AssetLookupElement; -import fr.adrienbrault.idea.symfony2plugin.asset.dic.AssetDirectoryReader; -import fr.adrienbrault.idea.symfony2plugin.asset.dic.AssetFile; +import fr.adrienbrault.idea.symfony2plugin.asset.AssetDirectoryReader; +import fr.adrienbrault.idea.symfony2plugin.asset.AssetFile; import fr.adrienbrault.idea.symfony2plugin.twig.assets.TwigNamedAssetsServiceParser; import fr.adrienbrault.idea.symfony2plugin.util.service.ServiceXmlParserFactory; import org.jetbrains.annotations.NotNull; diff --git a/src/fr/adrienbrault/idea/symfony2plugin/templating/TwigTemplateCompletionContributor.java b/src/fr/adrienbrault/idea/symfony2plugin/templating/TwigTemplateCompletionContributor.java index ac6951338..e424f635c 100644 --- a/src/fr/adrienbrault/idea/symfony2plugin/templating/TwigTemplateCompletionContributor.java +++ b/src/fr/adrienbrault/idea/symfony2plugin/templating/TwigTemplateCompletionContributor.java @@ -20,7 +20,7 @@ import com.jetbrains.twig.elements.TwigElementTypes; import fr.adrienbrault.idea.symfony2plugin.Symfony2Icons; import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent; -import fr.adrienbrault.idea.symfony2plugin.asset.dic.AssetDirectoryReader; +import fr.adrienbrault.idea.symfony2plugin.asset.AssetDirectoryReader; import fr.adrienbrault.idea.symfony2plugin.asset.provider.AssetCompletionProvider; import fr.adrienbrault.idea.symfony2plugin.routing.RouteHelper; import fr.adrienbrault.idea.symfony2plugin.templating.completion.QuotedInsertionLookupElement; @@ -258,6 +258,7 @@ public void addCompletions(@NotNull CompletionParameters parameters, ProcessingC // assets completion: // stylesheets and javascripts tags + // {{ asset('') }} extend(CompletionType.BASIC, TwigPattern.getAutocompletableAssetPattern(), new AssetCompletionProvider( new AssetDirectoryReader() )); diff --git a/src/fr/adrienbrault/idea/symfony2plugin/templating/completion/TwigHtmlCompletionContributor.java b/src/fr/adrienbrault/idea/symfony2plugin/templating/completion/TwigHtmlCompletionContributor.java index 1be7dc848..5261dd562 100644 --- a/src/fr/adrienbrault/idea/symfony2plugin/templating/completion/TwigHtmlCompletionContributor.java +++ b/src/fr/adrienbrault/idea/symfony2plugin/templating/completion/TwigHtmlCompletionContributor.java @@ -8,8 +8,8 @@ import com.intellij.util.ProcessingContext; import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent; import fr.adrienbrault.idea.symfony2plugin.asset.AssetLookupElement; -import fr.adrienbrault.idea.symfony2plugin.asset.dic.AssetDirectoryReader; -import fr.adrienbrault.idea.symfony2plugin.asset.dic.AssetFile; +import fr.adrienbrault.idea.symfony2plugin.asset.AssetDirectoryReader; +import fr.adrienbrault.idea.symfony2plugin.asset.AssetFile; import fr.adrienbrault.idea.symfony2plugin.routing.RouteHelper; import fr.adrienbrault.idea.symfony2plugin.routing.RouteLookupElement; import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigHtmlCompletionUtil; diff --git a/src/fr/adrienbrault/idea/symfony2plugin/templating/inspection/TwigAssetMissingInspection.java b/src/fr/adrienbrault/idea/symfony2plugin/templating/inspection/TwigAssetMissingInspection.java index 9b37555a1..a38c48fd3 100644 --- a/src/fr/adrienbrault/idea/symfony2plugin/templating/inspection/TwigAssetMissingInspection.java +++ b/src/fr/adrienbrault/idea/symfony2plugin/templating/inspection/TwigAssetMissingInspection.java @@ -6,9 +6,8 @@ import com.intellij.psi.PsiElementVisitor; import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent; import fr.adrienbrault.idea.symfony2plugin.templating.TwigPattern; -import fr.adrienbrault.idea.symfony2plugin.asset.dic.AssetDirectoryReader; -import fr.adrienbrault.idea.symfony2plugin.asset.dic.AssetFile; import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigUtil; +import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.NotNull; /** @@ -23,25 +22,34 @@ public PsiElementVisitor buildVisitor(final @NotNull ProblemsHolder holder, bool return super.buildVisitor(holder, isOnTheFly); } - return new PsiElementVisitor() { - @Override - public void visitElement(PsiElement element) { - if(TwigPattern.getAutocompletableAssetPattern().accepts(element) && TwigUtil.isValidStringWithoutInterpolatedOrConcat(element)) { - invoke(element, holder); - } + return new MyPsiElementVisitor(holder); + } + + private class MyPsiElementVisitor extends PsiElementVisitor { + + private final ProblemsHolder holder; - super.visitElement(element); + MyPsiElementVisitor(ProblemsHolder holder) { + this.holder = holder; + } + + @Override + public void visitElement(PsiElement element) { + if(TwigPattern.getAutocompletableAssetPattern().accepts(element) && TwigUtil.isValidStringWithoutInterpolatedOrConcat(element)) { + invoke(element, holder); } - }; - } - private void invoke(@NotNull PsiElement element, @NotNull ProblemsHolder holder) { - for (final AssetFile assetFile : new AssetDirectoryReader().getAssetFiles(element.getProject())) { - if(assetFile.toString().equals(element.getText())) { + super.visitElement(element); + } + + private void invoke(@NotNull PsiElement element, @NotNull ProblemsHolder holder) { + String asset = element.getText(); + + if(StringUtils.isBlank(asset) || TwigUtil.resolveAssetsFiles(element.getProject(), asset).size() > 0) { return; } - } - holder.registerProblem(element, "Missing asset"); + holder.registerProblem(element, "Missing asset"); + } } } diff --git a/src/fr/adrienbrault/idea/symfony2plugin/templating/util/TwigUtil.java b/src/fr/adrienbrault/idea/symfony2plugin/templating/util/TwigUtil.java index f36bbf010..1e137e84e 100644 --- a/src/fr/adrienbrault/idea/symfony2plugin/templating/util/TwigUtil.java +++ b/src/fr/adrienbrault/idea/symfony2plugin/templating/util/TwigUtil.java @@ -35,8 +35,7 @@ import de.espend.idea.php.annotation.util.AnnotationUtil; import fr.adrienbrault.idea.symfony2plugin.Settings; import fr.adrienbrault.idea.symfony2plugin.action.comparator.ValueComparator; -import fr.adrienbrault.idea.symfony2plugin.asset.dic.AssetDirectoryReader; -import fr.adrienbrault.idea.symfony2plugin.asset.dic.AssetFile; +import fr.adrienbrault.idea.symfony2plugin.asset.AssetDirectoryReader; import fr.adrienbrault.idea.symfony2plugin.extension.TwigNamespaceExtension; import fr.adrienbrault.idea.symfony2plugin.extension.TwigNamespaceExtensionParameter; import fr.adrienbrault.idea.symfony2plugin.stubs.SymfonyProcessors; @@ -1385,15 +1384,16 @@ public static String getTwigMethodString(@Nullable PsiElement transPsiElement) { return null; } - public static Set resolveAssetsFiles(@NotNull Project project, @NotNull String templateName, @NotNull String... fileTypes) { - Set virtualFiles = new HashSet<>(); + @NotNull + public static Collection resolveAssetsFiles(@NotNull Project project, @NotNull String assetName, @NotNull String... fileTypes) { + Collection virtualFiles = new HashSet<>(); // {% javascripts [...] @jquery_js2'%} - if(templateName.startsWith("@") && templateName.length() > 1) { + if(assetName.startsWith("@") && assetName.length() > 1) { TwigNamedAssetsServiceParser twigPathServiceParser = ServiceXmlParserFactory.getInstance(project, TwigNamedAssetsServiceParser.class); - String assetName = templateName.substring(1); - if(twigPathServiceParser.getNamedAssets().containsKey(assetName)) { - for (String s : twigPathServiceParser.getNamedAssets().get(assetName)) { + String assetNameShortcut = assetName.substring(1); + if(twigPathServiceParser.getNamedAssets().containsKey(assetNameShortcut)) { + for (String s : twigPathServiceParser.getNamedAssets().get(assetNameShortcut)) { VirtualFile fileByURL = VfsUtil.findFileByIoFile(new File(s), false); if(fileByURL != null) { virtualFiles.add(fileByURL); @@ -1403,41 +1403,7 @@ public static Set resolveAssetsFiles(@NotNull Project project, @Not } - // dont matches wildcard: - // {% javascripts '@SampleBundle/Resources/public/js/*' %} - // {% javascripts 'assets/js/*' %} - // {% javascripts 'assets/js/*.js' %} - Matcher matcher = Pattern.compile("^(.*[/\\\\])\\*([.\\w+]*)$").matcher(templateName); - if (!matcher.find()) { - - // directly resolve - VirtualFile projectAssetRoot = AssetDirectoryReader.getProjectAssetRoot(project); - if(projectAssetRoot != null) { - VirtualFile relativeFile = VfsUtil.findRelativeFile(projectAssetRoot, templateName); - if(relativeFile != null) { - virtualFiles.add(relativeFile); - } - } - - for (final AssetFile assetFile : new AssetDirectoryReader(fileTypes, true).getAssetFiles(project)) { - if(assetFile.toString().equals(templateName)) { - virtualFiles.add(assetFile.getFile()); - } - } - - return virtualFiles; - } - - String pathName = matcher.group(1); - String fileExtension = matcher.group(2).length() > 0 ? matcher.group(2) : null; - - for (final AssetFile assetFile : new AssetDirectoryReader(fileTypes, true).getAssetFiles(project)) { - if(fileExtension == null && assetFile.toString().matches(Pattern.quote(pathName) + "(?!.*[/\\\\]).*\\.\\w+")) { - virtualFiles.add(assetFile.getFile()); - } else if(fileExtension != null && assetFile.toString().matches(Pattern.quote(pathName) + "(?!.*[/\\\\]).*" + Pattern.quote(fileExtension))) { - virtualFiles.add(assetFile.getFile()); - } - } + virtualFiles.addAll(new AssetDirectoryReader(fileTypes, true).resolveAssetFile(project, assetName)); return virtualFiles; } diff --git a/tests/fr/adrienbrault/idea/symfony2plugin/tests/asset/AssetDirectoryReaderTest.java b/tests/fr/adrienbrault/idea/symfony2plugin/tests/asset/AssetDirectoryReaderTest.java new file mode 100644 index 000000000..a7490a3bc --- /dev/null +++ b/tests/fr/adrienbrault/idea/symfony2plugin/tests/asset/AssetDirectoryReaderTest.java @@ -0,0 +1,42 @@ +package fr.adrienbrault.idea.symfony2plugin.tests.asset; + +import fr.adrienbrault.idea.symfony2plugin.asset.AssetDirectoryReader; +import fr.adrienbrault.idea.symfony2plugin.tests.SymfonyTempCodeInsightFixtureTestCase; + +/** + * @author Daniel Espendiller + */ +public class AssetDirectoryReaderTest extends SymfonyTempCodeInsightFixtureTestCase { + public void testSimpleFileResolving() { + createFile("web/test.js"); + createFile("web/foobar/foo/foobar.js"); + + assertNotNull(new AssetDirectoryReader() + .resolveAssetFile(getProject(), "test.js").stream() + .filter(virtualFile -> "test.js".equals(virtualFile.getName())) + .findFirst() + .orElseGet(null) + ); + + assertNotNull(new AssetDirectoryReader() + .resolveAssetFile(getProject(), "foobar///foo\\/foobar.js").stream() + .filter(virtualFile -> "foobar.js".equals(virtualFile.getName())) + .findFirst() + .orElseGet(null) + ); + + assertNotNull(new AssetDirectoryReader() + .resolveAssetFile(getProject(), "foobar///foo\\/*").stream() + .filter(virtualFile -> virtualFile.isDirectory() && "foo".equals(virtualFile.getName())) + .findFirst() + .orElseGet(null) + ); + + assertNotNull(new AssetDirectoryReader() + .resolveAssetFile(getProject(), "foobar///foo\\/*.js").stream() + .filter(virtualFile -> virtualFile.isDirectory() && "foo".equals(virtualFile.getName())) + .findFirst() + .orElseGet(null) + ); + } +}