parameterInfo : ((MethodInfo) generationInfo).parameterInfoMap.entrySet())
- {
-
- if(result.length() != resultLength)
- {
- result.append(",\n");
- for(int i = 0; i < indent; ++i)
- {
- result.append(" ");
- }
- }
- result.append(" ");
-
- String parameterType = parameterInfo.getValue().type;
- if(parameterType != null)
- {
- boolean optional = parameterInfo.getValue().optional;
-
- if(parameterType.endsWith("?"))
- {
- optional = true;
- parameterType = parameterType.substring(0, parameterType.length() - 1);
- }
-
-
- result.append("[ ");
- result.append(parameterType);
- if(optional)
- {
- result.append(", optional");
- }
- result.append(" ] ");
- }
-
- result.append(parameterInfo.getKey());
-
- final String initialValue = parameterInfo.getValue().initialValue;
- if(initialValue != null)
- {
- result.append(" = ").append(initialValue);
- }
- }
-
- result.append(" )\n");
- endNamedItem();
- }
-
- private void endNamedItem()
- {
- result.append("");
- }
-
- private int startNamedItem(final String functionName)
- {
- return startNamedItem(functionName, generationInfo);
- }
-
- private int startNamedItem(final String functionName, SymbolInfo generationInfo)
- {
- result.append("");
-
- StringBuffer options = new StringBuffer();
- int offset = result.length();
-
- if(generationInfo instanceof MethodInfo)
- {
- addVisibilityAndAccess(options, generationInfo);
- String returnType = ((MethodInfo) generationInfo).returnInfo.type;
- if(returnType != null)
- {
- if(options.length() > 0)
- {
- options.append(", ");
- }
- options.append(returnType);
- }
-
- final String type = ((MethodInfo) generationInfo).methodType;
- if(type != null)
- {
- if(options.length() > 0)
- {
- options.append(", ");
- }
- options.append(type);
- }
- }
- else
- {
- if(generationInfo.type != null)
- {
- if(options.length() > 0)
- {
- options.append(", ");
- }
- options.append(generationInfo.type);
- }
-
- addVisibilityAndAccess(options, generationInfo);
- }
-
- if(options.length() > 0)
- {
- result.append("[ ");
- result.append(options.toString());
- result.append(" ] ");
- }
-
- offset = result.length() - offset + functionName.length() + 1;
-
- result.append("");
- if(generationInfo.namespace != null && generationInfo.namespace.length() > 0)
- {
- result.append(generationInfo.namespace).append('.');
- }
- result.append(functionName);
- result.append("");
-
- return offset;
- }
-
- private void addVisibilityAndAccess(final StringBuffer options, SymbolInfo generationInfo)
- {
- if(generationInfo.visibility != null)
- {
- if(options.length() > 0)
- {
- options.append(", ");
- }
- options.append(generationInfo.visibility);
- }
-
- if(generationInfo.access != null)
- {
- if(options.length() > 0)
- {
- options.append(", ");
- }
- options.append(generationInfo.access);
- }
-
- if(generationInfo.deprecated)
- {
- if(options.length() > 0)
- {
- options.append(", ");
- }
- options.append("deprecated");
- }
- }
+class JSDocumentationBuilder implements JSDocumentationProcessor {
+ private static class SymbolInfo {
+ String visibility;
+ StringBuilder description = new StringBuilder();
+ String type;
+ String access;
+ boolean deprecated;
+ String namespace;
+ }
+
+ private static class MethodInfo extends SymbolInfo {
+ String methodType;
+ Map parameterInfoMap = new LinkedHashMap();
+ int parameterCount = -1;
+ SymbolInfo returnInfo = new SymbolInfo();
+ }
+
+ private static class ParameterInfo extends SymbolInfo {
+ boolean optional;
+ String initialValue;
+ }
+
+ private boolean myOptionalParametersStarted;
+ private boolean myEventsStarted;
+ private SymbolInfo generationInfo;
+ private ParameterInfo currentParameterInfo;
+ private
+ @NonNls
+ StringBuilder result;
+
+ private JSFunction function;
+ private JSNamedElement namedItem;
+ private final PsiElement myElement;
+ private PsiElement contextElement;
+
+ private int myNewLinesPendingCount;
+ private boolean seenPre;
+ private boolean seenSeeAlso;
+ private static final String BR_DELIMITER = "
\n";
+
+ @RequiredReadAction
+ JSDocumentationBuilder(PsiElement element, PsiElement _contextElement) {
+ myElement = element;
+ contextElement = _contextElement;
+ PsiElement parent = element.getParent();
+ if (element instanceof JSVariable variable
+ && variable.getInitializer() instanceof JSFunctionExpression functionExpression) {
+ element = functionExpression;
+ }
+
+ if (element instanceof JSFunction func) {
+ function = func;
+ generationInfo = new MethodInfo();
+
+ if (parent instanceof JSClass jsClass) {
+ generationInfo.namespace = jsClass.getQualifiedName();
+ }
+
+ for (JSParameter parameter : function.getParameterList().getParameters()) {
+ ParameterInfo paramInfo = new ParameterInfo();
+ ((MethodInfo)generationInfo).parameterInfoMap.put(parameter.getName(), paramInfo);
+ paramInfo.type = parameter.getTypeString();
+ paramInfo.initialValue = parameter.getInitializerText();
+ }
+
+ ((MethodInfo)generationInfo).returnInfo.type = function.getReturnTypeString();
+ }
+ else if (element instanceof JSNamedElement namedElement) {
+ namedItem = namedElement;
+ generationInfo = new SymbolInfo();
+
+ if (namedItem instanceof JSClass && parent instanceof JSPackageStatement packageStatement) {
+ generationInfo.namespace = packageStatement.getQualifiedName();
+ }
+ }
+ else {
+ generationInfo = new SymbolInfo();
+ }
+
+ result = generationInfo.description;
+ }
+
+ private void doAppend(String str) {
+ while (myNewLinesPendingCount > 0) {
+ result.append(seenPre ? "\n" : BR_DELIMITER);
+ --myNewLinesPendingCount;
+ }
+
+ result.append(str);
+ }
+
+ private static final Pattern ourTagStartPattern = Pattern.compile("<(/)?(\\w+)");
+
+ @Override
+ public boolean needsPlainCommentData() {
+ return true;
+ }
+
+ @Override
+ public boolean onCommentLine(@Nonnull String line) {
+ String trimmedLine = line.trim();
+ boolean parametersStarted = false;
+ boolean parametersEnded = false;
+
+ if (generationInfo instanceof MethodInfo methodInfo) {
+ parametersEnded = methodInfo.parameterCount + 1 == methodInfo.parameterInfoMap.size();
+ parametersStarted = methodInfo.parameterCount >= 0;
+ }
+
+ if (trimmedLine.length() == 0) {
+ if (result.length() == 0) {
+ return true;
+ }
+
+ int maxSubsequentBr = parametersStarted && !parametersEnded ? 0 : 2;
+ if (myNewLinesPendingCount < maxSubsequentBr) {
+ ++myNewLinesPendingCount;
+ }
+
+ if (parametersEnded && ((MethodInfo)generationInfo).returnInfo.description != result) {
+ setResult(generationInfo.description);
+ myNewLinesPendingCount = 1;
+ }
+
+ return true;
+ }
+
+ if (line.contains("")) {
+ seenPre = true;
+ }
+
+ if (!seenPre && line.indexOf('<') != -1) {
+ Matcher matcher = ourTagStartPattern.matcher(line);
+ int offset = 0;
+
+ while (matcher.find()) {
+ boolean isTagEnd = matcher.start(1) != matcher.end(1);
+ String s = matcher.group(2);
+ // tags that do not need escaping
+ if (tagNameThatDoNotNeedEscaping(s)) {
+ continue;
+ }
+ line = line.substring(0, offset + matcher.start(0)) + "<" + (isTagEnd ? "/" : "") + s +
+ line.substring(offset + matcher.end(0));
+ offset += 3;
+ }
+ }
+
+ doAppend(line);
+ myNewLinesPendingCount = parametersStarted && !parametersEnded ? 0 : 1;
+
+ if (line.contains("")) {
+ seenPre = false;
+ }
+ return true;
+ }
+
+ private static boolean tagNameThatDoNotNeedEscaping(@NonNls String s) {
+ return s.equalsIgnoreCase("p") || s.equalsIgnoreCase("i") || s.equalsIgnoreCase("code")
+ || s.equalsIgnoreCase("ul") || s.equalsIgnoreCase("li") || s.equalsIgnoreCase("b");
+ }
+
+ private void setResult(StringBuilder builder) {
+ result = builder;
+ myNewLinesPendingCount = 0;
+ }
+
+ @Override
+ public boolean onPatternMatch(
+ @Nonnull MetaDocType metaDocType,
+ @Nullable String matchName,
+ @Nullable String matchValue,
+ @Nullable String remainingLineContent,
+ @Nonnull String line,
+ String patternMatched
+ ) {
+ if (metaDocType == MetaDocType.DEFAULT) {
+ boolean color = remainingLineContent.startsWith("0x") && remainingLineContent.length() == 8;
+ remainingLineContent = appendCurrentOrDefaultValue(remainingLineContent.substring(color ? 2 : 0), color, true);
+ }
+
+ if (metaDocType == MetaDocType.SEE) {
+ if (!seenSeeAlso) {
+ seenSeeAlso = true;
+ result.append("- See also:
- ");
+ }
+ else {
+ result.append("
- ");
+ }
+ if (URLUtil.isAbsoluteURL(remainingLineContent)) {
+ result.append("").append(remainingLineContent).append("");
+ }
+ else if (StringUtil.containsAnyChar(remainingLineContent, JSDocumentationProvider.SEE_PLAIN_TEXT_CHARS)) {
+ result.append(StringUtil.stripQuotesAroundValue(remainingLineContent));
+ }
+ else {
+ JSDocumentationUtils.appendHyperLinkToElement(
+ null,
+ getSeeAlsoLink(remainingLineContent),
+ result,
+ remainingLineContent,
+ null
+ );
+ return true;
+ }
+ }
+ else if (seenSeeAlso) {
+ seenSeeAlso = false;
+ result.append("
");
+ }
+
+ if (metaDocType == MetaDocType.EVENT) {
+ if (myEventsStarted) {
+ result.append("");
+ }
+
+ result.append("\n");
+ if (!myEventsStarted) {
+ myEventsStarted = true;
+ result.append("- Events:
- ");
+ }
+ result.append("Event
").append(matchName).append(" -").append(remainingLineContent);
+ return true;
+ }
+ else if (myEventsStarted) {
+ myEventsStarted = false;
+ result.append("
");
+ }
+
+ if (metaDocType == MetaDocType.NOTE) {
+ result.append("\nNote: ").append(remainingLineContent);
+ return true;
+ }
+
+ if (metaDocType == MetaDocType.OPTIONAL_PARAMETERS) {
+ myOptionalParametersStarted = true;
+ if (currentParameterInfo != null) {
+ currentParameterInfo.optional = true;
+ }
+ if (remainingLineContent != null) {
+ onCommentLine(remainingLineContent);
+ }
+ return true;
+ }
+
+ if (metaDocType == MetaDocType.DEPRECATED) {
+ generationInfo.deprecated = true;
+ if (remainingLineContent != null) {
+ onCommentLine(remainingLineContent);
+ }
+ return true;
+ }
+
+ if (metaDocType == MetaDocType.DESCRIPTION) {
+ generationInfo.description.append(remainingLineContent);
+ return true;
+ }
+
+ if (metaDocType == MetaDocType.PRIVATE ||
+ metaDocType == MetaDocType.PUBLIC ||
+ metaDocType == MetaDocType.PROTECTED ||
+ metaDocType == MetaDocType.STATIC) {
+ String s = metaDocType.name().toLowerCase();
+ if (generationInfo.visibility == null) {
+ generationInfo.visibility = s;
+ }
+ else {
+ generationInfo.visibility += ", " + s;
+ }
+ return true;
+ }
+ else if (metaDocType == MetaDocType.TYPE) {
+ generationInfo.type = matchName;
+ return true;
+ }
+ else if (metaDocType == MetaDocType.FINAL) {
+ generationInfo.access = "final";
+ return true;
+ }
+ else if (metaDocType == MetaDocType.REQUIRES) {
+ //onCommentLine("Requires:"+matchName);
+ return true;
+ }
+ else if (metaDocType == MetaDocType.NAMESPACE) {
+ generationInfo.namespace = matchName;
+ return true;
+ }
+
+ if (function != null) {
+ MethodInfo methodGenerationInfo = ((MethodInfo)generationInfo);
+
+ if (metaDocType == MetaDocType.CONSTRUCTOR) {
+ methodGenerationInfo.methodType = "contructor";
+ }
+ else if (metaDocType == MetaDocType.METHOD) {
+ methodGenerationInfo.methodType = "method";
+ }
+ else if (metaDocType == MetaDocType.PARAMETER) {
+ ParameterInfo info = methodGenerationInfo.parameterInfoMap.get(matchName);
+
+ if (info != null) {
+ int index = 0;
+ for (SymbolInfo _info : methodGenerationInfo.parameterInfoMap.values()) {
+ if (info == _info) {
+ break;
+ }
+ ++index;
+ }
+
+ methodGenerationInfo.parameterCount = index;
+ }
+ else if (patternMatched.indexOf('@') != -1) {
+ // wrong doc (without parameter name)
+ methodGenerationInfo.parameterCount++;
+ int index = 0;
+
+ for (SymbolInfo _info : methodGenerationInfo.parameterInfoMap.values()) {
+ if (index == methodGenerationInfo.parameterCount) {
+ info = (ParameterInfo)_info;
+ break;
+ }
+ ++index;
+ }
+ }
+
+ if (info != null) {
+ if (matchValue != null) {
+ info.type = matchValue;
+ }
+ setResult(info.description);
+ info.description.append(remainingLineContent);
+ info.optional = myOptionalParametersStarted;
+ currentParameterInfo = info;
+ }
+ else {
+ onCommentLine(line);
+ }
+ }
+ else if (metaDocType == MetaDocType.RETURN) {
+ result = methodGenerationInfo.returnInfo.description;
+ if (matchName != null) {
+ methodGenerationInfo.returnInfo.type = matchName;
+ }
+ if (matchValue != null) {
+ methodGenerationInfo.returnInfo.description.append(matchValue);
+ }
+ if (remainingLineContent != null) {
+ methodGenerationInfo.returnInfo.description.append(remainingLineContent);
+ }
+ }
+ }
+ return true;
+ }
+
+ @RequiredReadAction
+ private String getSeeAlsoLink(String remainingLineContent) {
+ if (URLUtil.isAbsoluteURL(remainingLineContent)) {
+ return remainingLineContent;
+ }
+
+ if (!remainingLineContent.contains(".") && !remainingLineContent.startsWith("#")) {
+ // first try to find class in the same package, then in default one
+ JSQualifiedNamedElement qualifiedElement = JSDocumentationProvider.findParentQualifiedElement(myElement);
+ if (qualifiedElement != null) {
+ String qname = qualifiedElement.getQualifiedName();
+ String aPackage = qname.contains(".") ? qname.substring(0, qname.lastIndexOf('.') + 1) : "";
+ String resolvedLink = JSDocumentationProvider.getSeeAlsoLinkResolved(myElement, aPackage + remainingLineContent);
+ if (resolvedLink != null) {
+ return resolvedLink;
+ }
+ }
+ }
+
+ String resolvedLink = JSDocumentationProvider.getSeeAlsoLinkResolved(myElement, remainingLineContent);
+ return resolvedLink != null ? resolvedLink : remainingLineContent;
+ }
+
+ private String appendCurrentOrDefaultValue(String remainingLineContent, boolean color, boolean defaultValue) {
+ if (color) {
+ remainingLineContent = " ";
+ }
+ else {
+ remainingLineContent = "" + remainingLineContent + "";
+ }
+ result.append("
\n").append(defaultValue ? "Default" : "Current").append(" value:").append(remainingLineContent);
+ return remainingLineContent;
+ }
+
+ @RequiredReadAction
+ String getDoc() {
+ if (seenSeeAlso) {
+ seenSeeAlso = false;
+ result.append("
");
+ }
+ result = new StringBuilder();
+
+ if (function != null) {
+ startFunction(function);
+ result.append(generationInfo.description.toString());
+
+ result.append("\n");
+ MethodInfo methodInfo = ((MethodInfo)generationInfo);
+
+ if (methodInfo.parameterInfoMap.size() > 0) {
+ result.append("- ");
+ result.append(CodeInsightLocalize.javadocParameters().get());
+ result.append("
");
+ }
+
+ for (Map.Entry parameterInfo : methodInfo.parameterInfoMap.entrySet()) {
+ result.append("");
+ result.append(parameterInfo.getKey());
+ result.append("");
+
+ if (parameterInfo.getValue().description.length() > 0) {
+ result.append(" - ");
+ result.append(parameterInfo.getValue().description.toString());
+ }
+ result.append(" \n");
+ }
+
+ if (methodInfo.returnInfo.description.length() > 0) {
+ result.append("- ");
+ result.append(CodeInsightLocalize.javadocReturns().get());
+ result.append("
");
+
+ result.append("- ");
+ result.append(methodInfo.returnInfo.description.toString());
+ result.append("
");
+ }
+
+ result.append("
");
+ }
+ else {
+ if (namedItem != null) {
+ startNamedItem(namedItem.getName());
+ endNamedItem();
+ }
+
+ result.append(generationInfo.description.toString());
+ }
+
+ if (contextElement != null) {
+ String text = contextElement.getText();
+ if (text.startsWith("#") && text.length() == 7) {
+ appendCurrentOrDefaultValue(text.substring(1), true, false);
+ }
+ }
+
+ return result.toString();
+ }
+
+ public String getParameterDoc(String name) {
+ if (function != null) {
+ MethodInfo methodInfo = ((MethodInfo)generationInfo);
+ ParameterInfo parameterInfo = methodInfo.parameterInfoMap.get(name);
+
+ if (parameterInfo != null && parameterInfo.description.length() > 0) {
+ result = new StringBuilder();
+ startNamedItem(name, parameterInfo);
+ endNamedItem();
+ result.append(parameterInfo.description.toString());
+ return result.toString();
+ }
+ }
+
+ return null;
+ }
+
+ @RequiredReadAction
+ private void startFunction(JSFunction function) {
+ String functionName = function.getName();
+ PsiElement parent = function.getParent();
+
+ if (parent instanceof JSAssignmentExpression assignment) {
+ String unqualifiedFunctionName = functionName;
+ JSExpression expression = ((JSDefinitionExpression)assignment.getLOperand()).getExpression();
+ functionName = null;
+
+ if (expression instanceof JSReferenceExpression refExpr
+ && refExpr.getQualifier() instanceof JSReferenceExpression qualifierRefExpr) {
+ expression = JSSymbolUtil.findReferenceExpressionUsedForClassExtending(qualifierRefExpr);
+ functionName = expression.getText() + "." + unqualifiedFunctionName;
+ }
+
+ if (functionName == null) {
+ functionName = expression.getText();
+ }
+
+ if (generationInfo.namespace != null && functionName.equals(generationInfo.namespace + "." + unqualifiedFunctionName)) {
+ generationInfo.namespace = null;
+ }
+ }
+
+ if (functionName == null) {
+ functionName = "";
+ }
+
+ int indent = startNamedItem(functionName);
+
+ result.append("(");
+ int resultLength = result.length();
+
+ for (Map.Entry parameterInfo : ((MethodInfo)generationInfo).parameterInfoMap.entrySet()) {
+ if (result.length() != resultLength) {
+ result.append(",\n");
+ for (int i = 0; i < indent; ++i) {
+ result.append(" ");
+ }
+ }
+ result.append(" ");
+
+ String parameterType = parameterInfo.getValue().type;
+ if (parameterType != null) {
+ boolean optional = parameterInfo.getValue().optional;
+
+ if (parameterType.endsWith("?")) {
+ optional = true;
+ parameterType = parameterType.substring(0, parameterType.length() - 1);
+ }
+
+
+ result.append("[ ");
+ result.append(parameterType);
+ if (optional) {
+ result.append(", optional");
+ }
+ result.append(" ] ");
+ }
+
+ result.append(parameterInfo.getKey());
+
+ String initialValue = parameterInfo.getValue().initialValue;
+ if (initialValue != null) {
+ result.append(" = ").append(initialValue);
+ }
+ }
+
+ result.append(" )\n");
+ endNamedItem();
+ }
+
+ private void endNamedItem() {
+ result.append("");
+ }
+
+ private int startNamedItem(String functionName) {
+ return startNamedItem(functionName, generationInfo);
+ }
+
+ private int startNamedItem(String functionName, SymbolInfo generationInfo) {
+ result.append("");
+
+ StringBuffer options = new StringBuffer();
+ int offset = result.length();
+
+ if (generationInfo instanceof MethodInfo) {
+ addVisibilityAndAccess(options, generationInfo);
+ String returnType = ((MethodInfo)generationInfo).returnInfo.type;
+ if (returnType != null) {
+ if (options.length() > 0) {
+ options.append(", ");
+ }
+ options.append(returnType);
+ }
+
+ String type = ((MethodInfo)generationInfo).methodType;
+ if (type != null) {
+ if (options.length() > 0) {
+ options.append(", ");
+ }
+ options.append(type);
+ }
+ }
+ else {
+ if (generationInfo.type != null) {
+ if (options.length() > 0) {
+ options.append(", ");
+ }
+ options.append(generationInfo.type);
+ }
+
+ addVisibilityAndAccess(options, generationInfo);
+ }
+
+ if (options.length() > 0) {
+ result.append("[ ");
+ result.append(options.toString());
+ result.append(" ] ");
+ }
+
+ offset = result.length() - offset + functionName.length() + 1;
+
+ result.append("");
+ if (generationInfo.namespace != null && generationInfo.namespace.length() > 0) {
+ result.append(generationInfo.namespace).append('.');
+ }
+ result.append(functionName);
+ result.append("");
+
+ return offset;
+ }
+
+ private void addVisibilityAndAccess(StringBuffer options, SymbolInfo generationInfo) {
+ if (generationInfo.visibility != null) {
+ if (options.length() > 0) {
+ options.append(", ");
+ }
+ options.append(generationInfo.visibility);
+ }
+
+ if (generationInfo.access != null) {
+ if (options.length() > 0) {
+ options.append(", ");
+ }
+ options.append(generationInfo.access);
+ }
+
+ if (generationInfo.deprecated) {
+ if (options.length() > 0) {
+ options.append(", ");
+ }
+ options.append("deprecated");
+ }
+ }
}
diff --git a/base-impl/src/main/java/com/intellij/javascript/documentation/JSDocumentationProcessor.java b/base-impl/src/main/java/com/intellij/javascript/documentation/JSDocumentationProcessor.java
index 31b81845..c79a645b 100644
--- a/base-impl/src/main/java/com/intellij/javascript/documentation/JSDocumentationProcessor.java
+++ b/base-impl/src/main/java/com/intellij/javascript/documentation/JSDocumentationProcessor.java
@@ -22,21 +22,45 @@
*/
package com.intellij.javascript.documentation;
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
+import jakarta.annotation.Nonnull;
+import jakarta.annotation.Nullable;
-public interface JSDocumentationProcessor
-{
- enum MetaDocType
- {
- RETURN, CONSTRUCTOR, METHOD, PARAMETER, PRIVATE, PUBLIC, PROTECTED, STATIC, DESCRIPTION, FINAL, REQUIRES, TYPE, NAMESPACE,
- OPTIONAL_PARAMETERS, EVENT, NOTE, DEPRECATED, SEE, DEFAULT, EXTENDS, CLASS, FIELD
- }
+public interface JSDocumentationProcessor {
+ enum MetaDocType {
+ RETURN,
+ CONSTRUCTOR,
+ METHOD,
+ PARAMETER,
+ PRIVATE,
+ PUBLIC,
+ PROTECTED,
+ STATIC,
+ DESCRIPTION,
+ FINAL,
+ REQUIRES,
+ TYPE,
+ NAMESPACE,
+ OPTIONAL_PARAMETERS,
+ EVENT,
+ NOTE,
+ DEPRECATED,
+ SEE,
+ DEFAULT,
+ EXTENDS,
+ CLASS,
+ FIELD
+ }
- boolean needsPlainCommentData();
+ boolean needsPlainCommentData();
- boolean onCommentLine(@Nonnull String line);
+ boolean onCommentLine(@Nonnull String line);
- boolean onPatternMatch(@Nonnull MetaDocType type, @Nullable String matchName, @Nullable final String matchValue,
- @Nullable String remainingLineContent, @Nonnull final String line, final String patternMatched);
+ boolean onPatternMatch(
+ @Nonnull MetaDocType type,
+ @Nullable String matchName,
+ @Nullable String matchValue,
+ @Nullable String remainingLineContent,
+ @Nonnull String line,
+ String patternMatched
+ );
}
\ No newline at end of file
diff --git a/base-impl/src/main/java/com/intellij/javascript/documentation/JSDocumentationProvider.java b/base-impl/src/main/java/com/intellij/javascript/documentation/JSDocumentationProvider.java
index 93502b77..d47e3897 100644
--- a/base-impl/src/main/java/com/intellij/javascript/documentation/JSDocumentationProvider.java
+++ b/base-impl/src/main/java/com/intellij/javascript/documentation/JSDocumentationProvider.java
@@ -39,12 +39,11 @@
import consulo.project.Project;
import consulo.util.lang.Pair;
import consulo.util.lang.StringUtil;
-import consulo.util.lang.ref.Ref;
+import consulo.util.lang.ref.SimpleReference;
import consulo.xml.psi.xml.XmlToken;
-import org.jetbrains.annotations.NonNls;
+import jakarta.annotation.Nonnull;
+import jakarta.annotation.Nullable;
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
import java.io.File;
import java.lang.reflect.Method;
import java.util.Collections;
@@ -53,1109 +52,883 @@
import java.util.Map;
/**
- * User: Maxim.Mossienko
- * Date: Nov 4, 2005
- * Time: 5:04:28 PM
+ * @author Maxim.Mossienko
+ * @since 2005-11-04
*/
@ExtensionImpl
-public class JSDocumentationProvider implements CodeDocumentationProvider, LanguageDocumentationProvider
-{
- private DocumentationProvider cssProvider;
- @NonNls
- private static final String OBJECT_NAME = "Object";
- protected static final String SEE_PLAIN_TEXT_CHARS = "\t \"-\\/<>*";
-
- @NonNls
- protected static final String PACKAGE = "package";
- @NonNls
- protected static final String HTML_EXTENSION = ".html";
- @NonNls
- protected static final String PACKAGE_FILE = PACKAGE + HTML_EXTENSION;
-
- protected static final Map DOCUMENTED_ATTRIBUTES;
-
- static
- {
- DOCUMENTED_ATTRIBUTES = new HashMap();
- DOCUMENTED_ATTRIBUTES.put("Event", "event:");
- DOCUMENTED_ATTRIBUTES.put("Style", "style:");
- DOCUMENTED_ATTRIBUTES.put("Effect", "effect:");
- }
-
- private DocumentationProvider getCssProvider(Project project) throws Exception
- {
- if(cssProvider == null)
- {
- final Class> aClass = Class.forName("com.intellij.psi.css.impl.util.CssDocumentationProvider");
- cssProvider = (DocumentationProvider) aClass.getConstructor(new Class[]{}).newInstance(new Object[]{});
- }
-
- return cssProvider;
- }
-
- @Nullable
- public String getQuickNavigateInfo(PsiElement element, PsiElement element2)
- {
- if(element instanceof JSFunction)
- {
- final JSFunction function = (JSFunction) element;
- final PsiElement parent = element.getParent();
-
- if(function.isConstructor())
- {
- if(parent instanceof JSClass)
- {
- return createQuickNavigateForClazz((JSClass) parent);
- }
- }
- return createQuickNavigateForFunction(function);
- }
- else if(element instanceof JSClass)
- {
- return createQuickNavigateForClazz((JSClass) element);
- }
- else if(element instanceof JSVariable)
- {
- return createQuickNavigateForVariable((JSVariable) element);
- }
- else if(element instanceof JSAttributeNameValuePair)
- {
- return createQuickNavigateForAnnotationDerived(element);
- }
- else if(element instanceof XmlToken)
- {
- BaseJSSymbolProcessor.TagContextBuilder builder = new BaseJSSymbolProcessor.TagContextBuilder(element, "XmlTag");
- return StringUtil.stripQuotesAroundValue(element.getText()) + ":" + builder.typeName;
- }
- else if(element instanceof JSNamespaceDeclaration)
- {
- return createQuickNavigateForNamespace((JSNamespaceDeclaration) element);
- }
-
- return null;
- }
-
- private static String createQuickNavigateForAnnotationDerived(final PsiElement element)
- {
- final JSAttributeNameValuePair valuePair = (JSAttributeNameValuePair) element;
- final JSAttribute parent = (JSAttribute) valuePair.getParent();
- final StringBuilder builder = new StringBuilder();
- final JSClass clazz = PsiTreeUtil.getParentOfType(valuePair, JSClass.class);
- appendParentInfo(clazz != null ? clazz : parent.getContainingFile(), builder, parent);
- builder.append(parent.getName()).append(" ").append(valuePair.getSimpleValue());
- return builder.toString();
- }
-
- private static
- @Nullable
- String createQuickNavigateForFunction(final JSFunction function)
- {
- final PsiElement parent = JSResolveUtil.findParent(function);
- final StringBuilder result = new StringBuilder();
-
- appendParentInfo(parent, result, function);
-
- appendAttrList(function, result);
- final boolean get = function.isGetProperty();
- final boolean set = function.isSetProperty();
-
- result.append(get || set ? "property " : "function ");
- result.append(function.getName());
-
- if(!get && !set)
- {
- result.append('(');
- final JSParameterList jsParameterList = function.getParameterList();
-
- if(jsParameterList != null)
- {
- final int start = result.length();
-
- for(JSParameter p : jsParameterList.getParameters())
- {
- if(start != result.length())
- {
- result.append(", ");
- }
- result.append(p.getName());
- appendVarType(p, result);
- }
- }
-
- result.append(')');
- }
-
- String varType = null;
-
- if(get || !set)
- {
- varType = JSImportHandlingUtil.resolveTypeName(function.getReturnTypeString(), function);
- }
- else
- {
- final JSParameterList jsParameterList = function.getParameterList();
-
- if(jsParameterList != null)
- {
- final JSParameter[] jsParameters = jsParameterList.getParameters();
- if(jsParameters != null && jsParameters.length > 0)
- {
- varType = JSImportHandlingUtil.resolveTypeName(jsParameters[0].getTypeString(), function);
- }
- }
- }
-
- if(varType != null)
- {
- result.append(':').append(varType);
- }
- return result.toString();
- }
-
- private static void appendParentInfo(final PsiElement parent, final StringBuilder builder, PsiNamedElement element)
- {
- if(parent instanceof JSClass)
- {
- builder.append(((JSClass) parent).getQualifiedName()).append("\n");
- }
- else if(parent instanceof JSPackageStatement)
- {
- builder.append(((JSPackageStatement) parent).getQualifiedName()).append("\n");
- }
- else if(parent instanceof JSFile)
- {
- if(parent.getContext() != null)
- {
- final String mxmlPackage = JSResolveUtil.findPackageForMxml(parent);
- if(mxmlPackage != null)
- {
- builder.append(mxmlPackage).append(mxmlPackage.length() > 0 ? "." : "").append(parent.getContext().getContainingFile().getName()).append("\n");
- }
- }
- else
- {
- boolean foundQualified = false;
-
- if(element instanceof JSNamedElement)
- {
- PsiElement node = ((JSNamedElement) element).getNameIdentifier();
- if(node != null)
- {
- final String s = node.getText();
- int i = s.lastIndexOf('.');
- if(i != -1)
- {
- builder.append(s.substring(0, i)).append("\n");
- foundQualified = true;
- }
- }
- }
- if(!foundQualified)
- {
- builder.append(parent.getContainingFile().getName()).append("\n");
- }
- }
- }
- }
-
- @Nullable
- @RequiredReadAction
- private static String createQuickNavigateForVariable(final JSVariable variable)
- {
- final PsiElement parent = JSResolveUtil.findParent(variable);
- final StringBuilder result = new StringBuilder();
-
- appendParentInfo(parent, result, variable);
-
- appendAttrList(variable, result);
- result.append(variable.isConst() ? "const " : "var ");
- result.append(variable.getName());
- appendVarType(variable, result);
-
- JSExpression initializer = variable.getInitializer();
- if(initializer != null)
- {
- result.append(" = ");
- if(initializer instanceof JSLiteralExpression)
- {
- result.append(initializer.getText());
- }
- else if(initializer instanceof JSObjectLiteralExpression)
- {
- result.append("{...}");
- }
- else
- {
- result.append("...");
- }
- }
- return result.toString();
- }
-
- private static void appendVarType(final JSVariable variable, final StringBuilder builder)
- {
- final String varType = variable.getTypeString();
- if(varType != null)
- {
- builder.append(':').append(varType);
- }
- }
-
- private static
- @Nullable
- String createQuickNavigateForClazz(final JSClass jsClass)
- {
- final String qName = jsClass.getQualifiedName();
- if(qName == null)
- {
- return null;
- }
- StringBuilder result = new StringBuilder();
- String packageName = StringUtil.getPackageName(qName);
- if(packageName.length() > 0)
- {
- result.append(packageName).append("\n");
- }
-
- appendAttrList(jsClass, result);
- if(jsClass.isInterface())
- {
- result.append("interface");
- }
- else
- {
- result.append("class");
- }
-
- final String name = jsClass.getName();
- result.append(" ").append(name);
-
- String s = generateReferenceTargetList(jsClass.getExtendsList(), packageName);
- if(s == null && !OBJECT_NAME.equals(name))
- {
- s = OBJECT_NAME;
- }
- if(s != null)
- {
- result.append(" extends ").append(s);
- }
-
- s = generateReferenceTargetList(jsClass.getImplementsList(), packageName);
- if(s != null)
- {
- result.append("\nimplements ").append(s);
- }
-
- return result.toString();
- }
-
- private static
- @Nullable
- String createQuickNavigateForNamespace(final JSNamespaceDeclaration ns)
- {
- final String qName = ns.getQualifiedName();
- if(qName == null)
- {
- return null;
- }
- StringBuilder result = new StringBuilder();
- String packageName = StringUtil.getPackageName(qName);
- if(packageName.length() > 0)
- {
- result.append(packageName).append("\n");
- }
-
- result.append("namespace");
-
- final String name = ns.getName();
- result.append(" ").append(name);
-
- String s = ns.getInitialValueString();
- if(s != null)
- {
- result.append(" = ").append(s);
- }
- return result.toString();
- }
-
-
- private static void appendAttrList(final JSAttributeListOwner jsClass, final StringBuilder result)
- {
- final JSAttributeList attributeList = jsClass.getAttributeList();
- if(attributeList != null)
- {
- if(attributeList.hasModifier(JSAttributeList.ModifierType.OVERRIDE))
- {
- result.append("override ");
- }
- JSAttributeList.AccessType type = attributeList.getAccessType();
- String ns = attributeList.getNamespace();
- if(type != JSAttributeList.AccessType.PACKAGE_LOCAL || ns == null)
- {
- result.append(type.toString().toLowerCase());
- }
- else
- {
- result.append(ns);
- }
-
- result.append(" ");
-
- if(attributeList.hasModifier(JSAttributeList.ModifierType.STATIC))
- {
- result.append("static ");
- }
- if(attributeList.hasModifier(JSAttributeList.ModifierType.FINAL))
- {
- result.append("final ");
- }
- if(attributeList.hasModifier(JSAttributeList.ModifierType.DYNAMIC))
- {
- result.append("dynamic ");
- }
- if(attributeList.hasModifier(JSAttributeList.ModifierType.NATIVE))
- {
- result.append("native ");
- }
- }
- }
-
- private static
- @Nullable
- String generateReferenceTargetList(final @Nullable JSReferenceList implementsList, @Nonnull String packageName)
- {
- if(implementsList == null)
- {
- return null;
- }
- StringBuilder result = null;
-
- final String[] referenceExpressionTexts = implementsList.getReferenceTexts();
-
- for(String refExprText : referenceExpressionTexts)
- {
- refExprText = JSImportHandlingUtil.resolveTypeName(refExprText, implementsList);
- if(result == null)
- {
- result = new StringBuilder();
- }
- else
- {
- result.append(",");
- }
-
- final String referencedPackageName = StringUtil.getPackageName(refExprText);
- result.append(referencedPackageName.equals(packageName) ? refExprText.substring(refExprText.lastIndexOf('.') + 1) : refExprText);
- }
- return result == null ? null : result.toString();
- }
-
- @Override
- public List getUrlFor(PsiElement element, PsiElement originalElement)
- {
- String possibleCssName = findPossibleCssName(element);
-
- if(possibleCssName != null)
- {
- try
- {
- final DocumentationProvider documentationProvider = getCssProvider(element.getProject());
- final Method method = documentationProvider.getClass().getMethod("getUrlFor", new Class[]{String.class});
- final Object o = method.invoke(null, new Object[]{possibleCssName});
-
- if(o instanceof String)
- {
- return Collections.singletonList((String) o);
- }
- }
- catch(Exception e)
- {
- }
- }
- return null;
- }
-
- @Override
- public String generateDoc(PsiElement _element, PsiElement originalElement)
- {
- if(_element instanceof JSReferenceExpression)
- {
- StringBuilder buffer = null;
-
- // ambigious reference
- final JSReferenceExpression expression = (JSReferenceExpression) _element;
- for(ResolveResult r : expression.multiResolve(false))
- {
- if(buffer == null)
- {
- buffer = new StringBuilder();
- }
- final PsiElement element = r.getElement();
- final ItemPresentation presentation = ((NavigationItem) element).getPresentation();
-
- JSDocumentationUtils.appendHyperLinkToElement(element, expression.getReferencedName(), buffer, presentation.getPresentableText(),
- presentation.getLocationString());
- buffer.append("
\n");
- }
- return buffer != null ? buffer.toString() : null;
- }
-
- _element = _element.getNavigationElement();
- PsiElement element = findElementForWhichPreviousCommentWillBeSearched(_element);
-
- final boolean parameterDoc = element instanceof JSParameter;
- if(parameterDoc)
- {
- element = findElementForWhichPreviousCommentWillBeSearched(PsiTreeUtil.getParentOfType(element, JSFunction.class));
- }
-
- if(element != null)
- {
- PsiElement docComment = JSDocumentationUtils.findDocComment(element, _element instanceof JSAttributeNameValuePair ? originalElement : null);
-
- if(docComment != null)
- {
- docComment = findFirstDocComment(docComment);
- element = findTargetElement(_element, element);
- final JSDocumentationBuilder builder = new JSDocumentationBuilder(element, originalElement);
- JSDocumentationUtils.processDocumentationTextFromComment(docComment.getNode(), builder);
-
- return parameterDoc ? builder.getParameterDoc(((JSParameter) _element).getName()) : builder.getDoc();
- }
-
- element = findTargetElement(_element, element);
-
- if(element instanceof JSFunction)
- {
- ASTNode initialComment = JSDocumentationUtils.findLeadingCommentInFunctionBody(element);
-
- if(initialComment != null)
- {
- final JSDocumentationBuilder builder = new JSDocumentationBuilder(element, originalElement);
- JSDocumentationUtils.processDocumentationTextFromComment(initialComment, builder);
- return builder.getDoc();
- }
- }
- }
-
- String possibleCssName = findPossibleCssName(_element);
- if(possibleCssName != null)
- {
-
- try
- {
- final DocumentationProvider documentationProvider = getCssProvider(_element.getProject());
- final Method declaredMethod = documentationProvider.getClass().getDeclaredMethod("generateDoc", String.class, PsiElement.class);
- final Object o = declaredMethod.invoke(null, possibleCssName, null);
-
- if(o instanceof String)
- {
- return (String) o;
- }
- }
- catch(Exception e)
- {
- }
-
- }
-
- return null;
- }
-
- private static PsiElement findTargetElement(final PsiElement _element, PsiElement element)
- {
- if(_element instanceof JSDefinitionExpression)
- {
- final PsiElement parentElement = _element.getParent();
-
- if(parentElement instanceof JSAssignmentExpression)
- {
- final JSExpression rOperand = ((JSAssignmentExpression) parentElement).getROperand();
-
- if(rOperand instanceof JSFunctionExpression)
- {
- element = rOperand;
- }
- else
- {
- element = _element;
- }
- }
- }
- else if(_element instanceof JSFunctionExpression)
- {
- element = _element;
- }
- else if(_element instanceof JSProperty)
- {
- final JSExpression expression = ((JSProperty) _element).getValue();
-
- if(expression instanceof JSFunction)
- {
- element = expression;
- }
- }
- else if(_element instanceof JSVariable)
- {
- if(_element instanceof JSParameter)
- {
- return PsiTreeUtil.getParentOfType(_element, JSFunction.class);
- }
- element = _element;
- }
- else if(_element instanceof JSAttributeNameValuePair)
- {
- return _element;
- }
- return element;
- }
-
- private static PsiElement findFirstDocComment(PsiElement docComment)
- {
- if(docComment.getNode().getElementType() == JSTokenTypes.END_OF_LINE_COMMENT)
- {
- while(true)
- {
- PsiElement prev = docComment.getPrevSibling();
- if(prev instanceof PsiWhiteSpace)
- {
- prev = prev.getPrevSibling();
- }
- if(prev == null)
- {
- break;
- }
- if(prev.getNode().getElementType() != JSTokenTypes.END_OF_LINE_COMMENT)
- {
- break;
- }
- docComment = prev;
- }
- }
- return docComment;
- }
-
- private static String findPossibleCssName(PsiElement _element)
- {
- if(_element instanceof JSDefinitionExpression)
- {
- final JSExpression expression = ((JSDefinitionExpression) _element).getExpression();
-
- if(expression instanceof JSReferenceExpression)
- {
- final String text = ((JSReferenceExpression) expression).getReferencedName();
- if(text == null)
- {
- return null;
- }
- final StringBuffer buf = new StringBuffer(text.length());
-
- for(int i = 0; i < text.length(); ++i)
- {
- final char ch = text.charAt(i);
-
- if(Character.isUpperCase(ch))
- {
- buf.append('-').append(Character.toLowerCase(ch));
- }
- else
- {
- buf.append(ch);
- }
- }
-
- return buf.toString();
- }
- }
- return null;
- }
-
- @Override
- public PsiElement getDocumentationElementForLookupItem(PsiManager psiManager, Object object, PsiElement element)
- {
- final PsiElement psiElement = findElementForWhichPreviousCommentWillBeSearched(object);
- if(psiElement != null && JSDocumentationUtils.findDocComment(psiElement) != null)
- {
- return psiElement;
- }
- if(object instanceof PsiElement)
- {
- return (PsiElement) object;
- }
- return null;
- }
-
- public static PsiElement findElementForWhichPreviousCommentWillBeSearched(Object object)
- {
- if(object instanceof JSFunction)
- {
- PsiElement psiElement = (PsiElement) object;
- PsiElement parent = psiElement.getParent();
- if(parent instanceof JSNewExpression)
- {
- parent = parent.getParent();
- }
- if(parent instanceof JSProperty)
- {
- psiElement = parent;
- }
- else if(parent instanceof JSAssignmentExpression)
- {
- psiElement = parent.getParent();
- }
-
- JSFunction function = (JSFunction) object;
- if(function.isSetProperty() || function.isGetProperty())
- {
- for(PsiElement el = function.getPrevSibling(); el != null; el = el.getPrevSibling())
- {
- if(!(el instanceof PsiWhiteSpace) && !(el instanceof PsiComment))
- {
- if(el instanceof JSFunction)
- {
- JSFunction prevFunction = (JSFunction) el;
- String name = prevFunction.getName();
-
- if(name != null &&
- name.equals(function.getName()) &&
- ((prevFunction.isGetProperty() && function.isSetProperty()) || prevFunction.isSetProperty() && function.isGetProperty()))
- {
- PsiElement doc = JSDocumentationUtils.findDocComment(prevFunction);
- if(doc != null)
- {
- return prevFunction;
- }
- }
- }
- break;
- }
- }
- }
- return psiElement;
- }
- else if(object instanceof JSProperty || object instanceof JSStatement || object instanceof JSClass)
- {
- return (PsiElement) object;
- }
- else if(object instanceof PsiElement)
- {
- final PsiElement parent = ((PsiElement) object).getParent();
- if(parent instanceof JSAssignmentExpression)
- {
- return parent.getParent();
- }
- else if(parent instanceof JSVarStatement)
- {
- final PsiElement firstChild = parent.getFirstChild();
- if(firstChild instanceof JSAttributeList)
- {
- if(JSDocumentationUtils.findDocComment((PsiElement) object) != null)
- {
- return (PsiElement) object;
- }
- }
- return parent;
- }
- else if(parent instanceof JSAttribute)
- {
- final PsiElement grandParent = parent.getParent();
- if(grandParent.getFirstChild() == parent)
- {
- final PsiElement element = grandParent.getParent();
- if(element instanceof JSFile)
- {
- return grandParent;
- }
- return element;
- }
- return parent;
- }
- else if(parent instanceof JSSuppressionHolder)
- {
- return parent;
- }
- else
- {
- return (PsiElement) object;
- }
- }
-
- return null;
- }
-
- @Override
- @Nullable
- public PsiElement getDocumentationElementForLink(final PsiManager psiManager, String link, final PsiElement context)
- {
- return getDocumentationElementForLinkStatic(psiManager, link, context);
- }
-
- @Nullable
- private static PsiElement getDocumentationElementForLinkStatic(final PsiManager psiManager, String link, @Nonnull PsiElement context)
- {
- final int delimiterIndex = link.lastIndexOf(':');
-
- String attributeType = null;
- String attributeName = null;
- for(Map.Entry e : DOCUMENTED_ATTRIBUTES.entrySet())
- {
- final String pattern = "." + e.getValue();
- if(link.contains(pattern))
- {
- attributeType = e.getKey();
- attributeName = link.substring(link.indexOf(pattern) + pattern.length());
- link = link.substring(0, link.indexOf(pattern));
- break;
- }
- }
- if(delimiterIndex != -1 && attributeType == null)
- {
- final int delimiterIndex2 = link.lastIndexOf(':', delimiterIndex - 1);
- String fileName = link.substring(0, delimiterIndex2).replace(File.separatorChar, '/');
- String name = link.substring(delimiterIndex2 + 1, delimiterIndex);
- int offset = Integer.parseInt(link.substring(delimiterIndex + 1));
- return JavaScriptIndex.findSymbolByFileAndNameAndOffset(fileName, name, offset);
- }
- else if(attributeType != null)
- {
- PsiElement clazz = JSResolveUtil.findClassByQName(link, context.getResolveScope(), psiManager.getProject());
- if(!(clazz instanceof JSClass))
- {
- return null;
- }
- return findNamedAttribute((JSClass) clazz, attributeType, attributeName);
- }
- else
- {
- PsiElement clazz = JSResolveUtil.findClassByQName(link, context.getResolveScope(), psiManager.getProject());
- if(clazz == null && link.contains("."))
- {
- String qname = link.substring(0, link.lastIndexOf('.'));
- clazz = JSResolveUtil.findClassByQName(qname, context.getResolveScope(), psiManager.getProject());
- if(clazz instanceof JSClass)
- {
- JSClass jsClass = (JSClass) clazz;
- String member = link.substring(link.lastIndexOf('.') + 1);
-
- if(member.endsWith("()"))
- {
- member = member.substring(0, member.length() - 2);
-
- PsiElement result = findMethod(jsClass, member);
- if(result == null)
- {
- result = findProperty(jsClass, member); // user might refer to a property
- }
- return result;
- }
- else
- {
- PsiElement result = jsClass.findFieldByName(member);
- if(result == null)
- {
- result = findProperty(jsClass, member);
- }
- if(result == null)
- {
- result = findMethod(jsClass, member); // user might forget brackets
- }
- return result;
- }
- }
- }
-
- if(clazz instanceof JSVariable)
- {
- return clazz;
- }
-
- if(link.endsWith("()"))
- {
- link = link.substring(0, link.length() - 2);
- clazz = JSResolveUtil.findClassByQName(link, context.getResolveScope(), psiManager.getProject());
- if(clazz instanceof JSFunction)
- {
- return clazz;
- }
- }
- return clazz;
- }
- }
-
- @Nullable
- protected static JSAttributeNameValuePair findNamedAttribute(JSClass clazz, final String type, final String name)
- {
- final Ref attribute = new Ref();
- JSResolveUtil.processMetaAttributesForClass(clazz, new JSResolveUtil.MetaDataProcessor()
- {
- @Override
- public boolean process(@Nonnull JSAttribute jsAttribute)
- {
- if(type.equals(jsAttribute.getName()))
- {
- final JSAttributeNameValuePair jsAttributeNameValuePair = jsAttribute.getValueByName("name");
- if(jsAttributeNameValuePair != null && name.equals(jsAttributeNameValuePair.getSimpleValue()))
- {
- attribute.set(jsAttributeNameValuePair);
- return false;
- }
- }
- return true;
- }
-
- @Override
- public boolean handleOtherElement(PsiElement el, PsiElement context, @Nullable Ref continuePassElement)
- {
- return true;
- }
- });
- return attribute.get();
- }
-
- private static PsiElement findProperty(JSClass jsClass, String name)
- {
- PsiElement result = jsClass.findFunctionByNameAndKind(name, JSFunction.FunctionKind.GETTER);
- if(result == null)
- {
- result = jsClass.findFunctionByNameAndKind(name, JSFunction.FunctionKind.SETTER);
- }
- return result;
- }
-
- private static PsiElement findMethod(JSClass jsClass, String name)
- {
- PsiElement result = jsClass.findFunctionByNameAndKind(name, JSFunction.FunctionKind.CONSTRUCTOR);
- if(result == null)
- {
- result = jsClass.findFunctionByNameAndKind(name, JSFunction.FunctionKind.SIMPLE);
- }
- return result;
- }
-
- @Override
- @Nullable
- public PsiComment findExistingDocComment(PsiComment contextElement)
- {
- return contextElement;
- }
-
- @Nullable
- @Override
- public Pair parseContext(@Nonnull PsiElement element)
- {
- return null;
- }
-
- @Override
- @Nullable
- public String generateDocumentationContentStub(PsiComment contextComment)
- {
- for(PsiElement el = contextComment.getParent(); el != null; el = el.getNextSibling())
- {
- if(el instanceof JSProperty)
- {
- final JSExpression propertyValue = ((JSProperty) el).getValue();
- if(propertyValue instanceof JSFunction)
- {
- return doGenerateDoc((JSFunction) propertyValue);
- }
- }
- else if(el instanceof JSFunction)
- {
- final JSFunction function = ((JSFunction) el);
- return doGenerateDoc(function);
- }
- else if(el instanceof JSExpressionStatement)
- {
- final JSExpression expression = ((JSExpressionStatement) el).getExpression();
-
- if(expression instanceof JSAssignmentExpression)
- {
- final JSExpression rOperand = ((JSAssignmentExpression) expression).getROperand();
-
- if(rOperand instanceof JSFunctionExpression)
- {
- return doGenerateDoc(((JSFunctionExpression) rOperand).getFunction());
- }
- }
- }
- else if(el instanceof JSVarStatement)
- {
- JSVariable[] variables = ((JSVarStatement) el).getVariables();
- if(variables.length > 0)
- {
- JSExpression expression = variables[0].getInitializer();
- if(expression instanceof JSFunctionExpression)
- {
- return doGenerateDoc(((JSFunctionExpression) expression).getFunction());
- }
- }
- break;
- }
- }
- return null;
- }
-
- private static String doGenerateDoc(final JSFunction function)
- {
- StringBuilder builder = new StringBuilder();
- final JSParameterList parameterList = function.getParameterList();
- final PsiFile containingFile = function.getContainingFile();
- final boolean ecma = containingFile.getLanguage() == JavaScriptSupportLoader.ECMA_SCRIPT_L4;
-
- if(parameterList != null)
- {
- for(JSParameter parameter : parameterList.getParameters())
- {
- builder.append("* @param ").append(parameter.getName());
- //String s = JSPsiImplUtils.getTypeFromDeclaration(parameter);
- //if (s != null) builder.append(" : ").append(s);
- builder.append("\n");
- }
- }
-
- if(ecma)
- {
- String s = JSPsiImplUtils.getTypeFromDeclaration(function);
-
- if(s != null && !"void".equals(s))
- {
- builder.append("* @return ");
-
- //builder.append(s);
- builder.append("\n");
- }
- }
-
- return builder.toString();
- }
-
- @Nullable
- protected static String getSeeAlsoLinkResolved(PsiElement originElement, String link)
- {
- JSQualifiedNamedElement qualifiedElement = findParentQualifiedElement(originElement);
- if(qualifiedElement == null)
- {
- return null;
- }
- String linkToResolve = getLinkToResolve(qualifiedElement, link);
- final PsiElement resolvedElement = getDocumentationElementForLinkStatic(originElement.getManager(), linkToResolve, originElement);
- if(resolvedElement != null)
- {
- return linkToResolve;
- }
- return null;
- }
-
- private static String getLinkToResolve(JSQualifiedNamedElement origin, String link)
- {
- String originQname = origin.getQualifiedName();
- if(link.length() == 0)
- {
- return originQname;
- }
- else if(StringUtil.startsWithChar(link, '#'))
- {
- if(origin instanceof JSClass)
- {
- return originQname + "." + link.substring(1);
- }
- else
- {
- String aPackage = StringUtil.getPackageName(originQname);
- return aPackage + "." + link.substring(1);
- }
- }
- else
- {
- String linkFile = link.contains("#") ? link.substring(0, link.lastIndexOf('#')) : link;
- String linkAnchor = link.contains("#") ? link.substring(link.lastIndexOf('#') + 1) : null;
-
- final String qname;
- if(StringUtil.endsWithIgnoreCase(linkFile, HTML_EXTENSION))
- {
- String prefix = StringUtil.getPackageName(originQname);
- while(linkFile.startsWith("../"))
- {
- linkFile = linkFile.substring("../".length());
- prefix = StringUtil.getPackageName(prefix);
- }
-
- String linkFilename;
- if(linkFile.endsWith(PACKAGE_FILE))
- {
- linkFilename = StringUtil.trimEnd(linkFile, PACKAGE_FILE);
- linkFilename = StringUtil.trimEnd(linkFilename, "/");
- }
- else
- {
- linkFilename = linkFile.substring(0, linkFile.lastIndexOf("."));
- }
- if(linkFilename.length() > 0)
- {
- qname = (prefix.length() > 0 ? prefix + "." : prefix) + linkFilename.replaceAll("/", ".");
- }
- else
- {
- qname = prefix;
- }
- }
- else
- {
- qname = linkFile;
- }
-
- return linkAnchor != null ? (qname.length() > 0 ? qname + "." : qname) + linkAnchor : qname;
- }
- }
-
- @Nullable
- protected static JSQualifiedNamedElement findParentQualifiedElement(PsiElement element)
- {
- if(element instanceof JSClass)
- {
- return (JSClass) element;
- }
- if(element instanceof JSFunction || element instanceof JSVariable)
- {
- final PsiElement parent = JSResolveUtil.findParent(element);
- if(parent instanceof JSClass)
- {
- return (JSClass) parent;
- }
- else if(parent instanceof JSFile)
- {
- return (JSQualifiedNamedElement) element;
- }
- }
-
- JSAttribute attribute = null;
- if(element instanceof JSAttribute)
- {
- attribute = (JSAttribute) element;
- }
- else if(element instanceof JSAttributeNameValuePair)
- {
- attribute = (JSAttribute) element.getParent();
- }
-
- if(attribute != null && DOCUMENTED_ATTRIBUTES.containsKey(attribute.getName()))
- {
- final JSClass jsClass = PsiTreeUtil.getParentOfType(element, JSClass.class);
- if(jsClass != null)
- {
- return jsClass;
- }
- }
-
- return null;
- }
-
-
- @Nonnull
- @Override
- public Language getLanguage()
- {
- return JavaScriptLanguage.INSTANCE;
- }
+public class JSDocumentationProvider implements CodeDocumentationProvider, LanguageDocumentationProvider {
+ private DocumentationProvider cssProvider;
+ private static final String OBJECT_NAME = "Object";
+ protected static final String SEE_PLAIN_TEXT_CHARS = "\t \"-\\/<>*";
+
+ protected static final String PACKAGE = "package";
+ protected static final String HTML_EXTENSION = ".html";
+ protected static String PACKAGE_FILE = PACKAGE + HTML_EXTENSION;
+
+ protected static final Map DOCUMENTED_ATTRIBUTES;
+
+ static {
+ DOCUMENTED_ATTRIBUTES = new HashMap<>();
+ DOCUMENTED_ATTRIBUTES.put("Event", "event:");
+ DOCUMENTED_ATTRIBUTES.put("Style", "style:");
+ DOCUMENTED_ATTRIBUTES.put("Effect", "effect:");
+ }
+
+ private DocumentationProvider getCssProvider(Project project) throws Exception {
+ if (cssProvider == null) {
+ Class> aClass = Class.forName("com.intellij.psi.css.impl.util.CssDocumentationProvider");
+ cssProvider = (DocumentationProvider)aClass.getConstructor(new Class[]{}).newInstance(new Object[]{});
+ }
+
+ return cssProvider;
+ }
+
+ @Override
+ @Nullable
+ @RequiredReadAction
+ public String getQuickNavigateInfo(PsiElement element, PsiElement element2) {
+ if (element instanceof JSFunction function) {
+ if (function.isConstructor() && function.getParent() instanceof JSClass jsClass) {
+ return createQuickNavigateForClazz(jsClass);
+ }
+ return createQuickNavigateForFunction(function);
+ }
+ else if (element instanceof JSClass jsClass) {
+ return createQuickNavigateForClazz(jsClass);
+ }
+ else if (element instanceof JSVariable variable) {
+ return createQuickNavigateForVariable(variable);
+ }
+ else if (element instanceof JSAttributeNameValuePair attributeNameValuePair) {
+ return createQuickNavigateForAnnotationDerived(attributeNameValuePair);
+ }
+ else if (element instanceof XmlToken xmlToken) {
+ BaseJSSymbolProcessor.TagContextBuilder builder = new BaseJSSymbolProcessor.TagContextBuilder(xmlToken, "XmlTag");
+ return StringUtil.stripQuotesAroundValue(xmlToken.getText()) + ":" + builder.typeName;
+ }
+ else if (element instanceof JSNamespaceDeclaration namespaceDeclaration) {
+ return createQuickNavigateForNamespace(namespaceDeclaration);
+ }
+
+ return null;
+ }
+
+ @RequiredReadAction
+ private static String createQuickNavigateForAnnotationDerived(PsiElement element) {
+ JSAttributeNameValuePair valuePair = (JSAttributeNameValuePair)element;
+ JSAttribute parent = (JSAttribute)valuePair.getParent();
+ StringBuilder builder = new StringBuilder();
+ JSClass clazz = PsiTreeUtil.getParentOfType(valuePair, JSClass.class);
+ appendParentInfo(clazz != null ? clazz : parent.getContainingFile(), builder, parent);
+ builder.append(parent.getName()).append(" ").append(valuePair.getSimpleValue());
+ return builder.toString();
+ }
+
+ @Nullable
+ @RequiredReadAction
+ private static String createQuickNavigateForFunction(JSFunction function) {
+ PsiElement parent = JSResolveUtil.findParent(function);
+ StringBuilder result = new StringBuilder();
+
+ appendParentInfo(parent, result, function);
+
+ appendAttrList(function, result);
+ boolean get = function.isGetProperty();
+ boolean set = function.isSetProperty();
+
+ result.append(get || set ? "property " : "function ");
+ result.append(function.getName());
+
+ if (!get && !set) {
+ result.append('(');
+ JSParameterList jsParameterList = function.getParameterList();
+
+ if (jsParameterList != null) {
+ int start = result.length();
+
+ for (JSParameter p : jsParameterList.getParameters()) {
+ if (start != result.length()) {
+ result.append(", ");
+ }
+ result.append(p.getName());
+ appendVarType(p, result);
+ }
+ }
+
+ result.append(')');
+ }
+
+ String varType = null;
+
+ if (get || !set) {
+ varType = JSImportHandlingUtil.resolveTypeName(function.getReturnTypeString(), function);
+ }
+ else {
+ JSParameterList jsParameterList = function.getParameterList();
+
+ if (jsParameterList != null) {
+ JSParameter[] jsParameters = jsParameterList.getParameters();
+ if (jsParameters != null && jsParameters.length > 0) {
+ varType = JSImportHandlingUtil.resolveTypeName(jsParameters[0].getTypeString(), function);
+ }
+ }
+ }
+
+ if (varType != null) {
+ result.append(':').append(varType);
+ }
+ return result.toString();
+ }
+
+ @RequiredReadAction
+ private static void appendParentInfo(PsiElement parent, StringBuilder builder, PsiNamedElement element) {
+ if (parent instanceof JSClass jsClass) {
+ builder.append(jsClass.getQualifiedName()).append("\n");
+ }
+ else if (parent instanceof JSPackageStatement packageStatement) {
+ builder.append(packageStatement.getQualifiedName()).append("\n");
+ }
+ else if (parent instanceof JSFile) {
+ if (parent.getContext() != null) {
+ String mxmlPackage = JSResolveUtil.findPackageForMxml(parent);
+ if (mxmlPackage != null) {
+ builder.append(mxmlPackage)
+ .append(mxmlPackage.length() > 0 ? "." : "")
+ .append(parent.getContext().getContainingFile().getName())
+ .append("\n");
+ }
+ }
+ else {
+ boolean foundQualified = false;
+
+ if (element instanceof JSNamedElement namedElement) {
+ PsiElement node = namedElement.getNameIdentifier();
+ if (node != null) {
+ String s = node.getText();
+ int i = s.lastIndexOf('.');
+ if (i != -1) {
+ builder.append(s.substring(0, i)).append("\n");
+ foundQualified = true;
+ }
+ }
+ }
+ if (!foundQualified) {
+ builder.append(parent.getContainingFile().getName()).append("\n");
+ }
+ }
+ }
+ }
+
+ @Nullable
+ @RequiredReadAction
+ private static String createQuickNavigateForVariable(JSVariable variable) {
+ PsiElement parent = JSResolveUtil.findParent(variable);
+ StringBuilder result = new StringBuilder();
+
+ appendParentInfo(parent, result, variable);
+
+ appendAttrList(variable, result);
+ result.append(variable.isConst() ? "const " : "var ");
+ result.append(variable.getName());
+ appendVarType(variable, result);
+
+ JSExpression initializer = variable.getInitializer();
+ if (initializer != null) {
+ result.append(" = ");
+ if (initializer instanceof JSLiteralExpression) {
+ result.append(initializer.getText());
+ }
+ else if (initializer instanceof JSObjectLiteralExpression) {
+ result.append("{...}");
+ }
+ else {
+ result.append("...");
+ }
+ }
+ return result.toString();
+ }
+
+ private static void appendVarType(JSVariable variable, StringBuilder builder) {
+ String varType = variable.getTypeString();
+ if (varType != null) {
+ builder.append(':').append(varType);
+ }
+ }
+
+ @Nullable
+ @RequiredReadAction
+ private static String createQuickNavigateForClazz(JSClass jsClass) {
+ String qName = jsClass.getQualifiedName();
+ if (qName == null) {
+ return null;
+ }
+ StringBuilder result = new StringBuilder();
+ String packageName = StringUtil.getPackageName(qName);
+ if (packageName.length() > 0) {
+ result.append(packageName).append("\n");
+ }
+
+ appendAttrList(jsClass, result);
+ result.append(jsClass.isInterface() ? "interface" : "class");
+
+ String name = jsClass.getName();
+ result.append(" ").append(name);
+
+ String s = generateReferenceTargetList(jsClass.getExtendsList(), packageName);
+ if (s == null && !OBJECT_NAME.equals(name)) {
+ s = OBJECT_NAME;
+ }
+ if (s != null) {
+ result.append(" extends ").append(s);
+ }
+
+ s = generateReferenceTargetList(jsClass.getImplementsList(), packageName);
+ if (s != null) {
+ result.append("\nimplements ").append(s);
+ }
+
+ return result.toString();
+ }
+
+ @Nullable
+ @RequiredReadAction
+ private static String createQuickNavigateForNamespace(JSNamespaceDeclaration ns) {
+ String qName = ns.getQualifiedName();
+ if (qName == null) {
+ return null;
+ }
+ StringBuilder result = new StringBuilder();
+ String packageName = StringUtil.getPackageName(qName);
+ if (packageName.length() > 0) {
+ result.append(packageName).append("\n");
+ }
+
+ result.append("namespace");
+
+ result.append(" ").append(ns.getName());
+
+ String s = ns.getInitialValueString();
+ if (s != null) {
+ result.append(" = ").append(s);
+ }
+ return result.toString();
+ }
+
+ @RequiredReadAction
+ private static void appendAttrList(JSAttributeListOwner jsClass, StringBuilder result) {
+ JSAttributeList attributeList = jsClass.getAttributeList();
+ if (attributeList != null) {
+ if (attributeList.hasModifier(JSAttributeList.ModifierType.OVERRIDE)) {
+ result.append("override ");
+ }
+ JSAttributeList.AccessType type = attributeList.getAccessType();
+ String ns = attributeList.getNamespace();
+ if (type != JSAttributeList.AccessType.PACKAGE_LOCAL || ns == null) {
+ result.append(type.toString().toLowerCase());
+ }
+ else {
+ result.append(ns);
+ }
+
+ result.append(" ");
+
+ if (attributeList.hasModifier(JSAttributeList.ModifierType.STATIC)) {
+ result.append("static ");
+ }
+ if (attributeList.hasModifier(JSAttributeList.ModifierType.FINAL)) {
+ result.append("final ");
+ }
+ if (attributeList.hasModifier(JSAttributeList.ModifierType.DYNAMIC)) {
+ result.append("dynamic ");
+ }
+ if (attributeList.hasModifier(JSAttributeList.ModifierType.NATIVE)) {
+ result.append("native ");
+ }
+ }
+ }
+
+ @Nullable
+ @RequiredReadAction
+ private static String generateReferenceTargetList(@Nullable JSReferenceList implementsList, @Nonnull String packageName) {
+ if (implementsList == null) {
+ return null;
+ }
+ StringBuilder result = null;
+
+ String[] referenceExpressionTexts = implementsList.getReferenceTexts();
+
+ for (String refExprText : referenceExpressionTexts) {
+ refExprText = JSImportHandlingUtil.resolveTypeName(refExprText, implementsList);
+ if (result == null) {
+ result = new StringBuilder();
+ }
+ else {
+ result.append(",");
+ }
+
+ String referencedPackageName = StringUtil.getPackageName(refExprText);
+ result.append(referencedPackageName.equals(packageName)
+ ? refExprText.substring(refExprText.lastIndexOf('.') + 1)
+ : refExprText);
+ }
+ return result == null ? null : result.toString();
+ }
+
+ @Override
+ public List getUrlFor(PsiElement element, PsiElement originalElement) {
+ String possibleCssName = findPossibleCssName(element);
+
+ if (possibleCssName != null) {
+ try {
+ DocumentationProvider documentationProvider = getCssProvider(element.getProject());
+ Method method = documentationProvider.getClass().getMethod("getUrlFor", new Class[]{String.class});
+ Object o = method.invoke(null, new Object[]{possibleCssName});
+
+ if (o instanceof String) {
+ return Collections.singletonList((String)o);
+ }
+ }
+ catch (Exception ignored) {
+ }
+ }
+ return null;
+ }
+
+ @Override
+ @RequiredReadAction
+ public String generateDoc(PsiElement _element, PsiElement originalElement) {
+ if (_element instanceof JSReferenceExpression expression) {
+ StringBuilder buffer = null;
+
+ // ambigious reference
+ for (ResolveResult r : expression.multiResolve(false)) {
+ if (buffer == null) {
+ buffer = new StringBuilder();
+ }
+ PsiElement element = r.getElement();
+ ItemPresentation presentation = ((NavigationItem)element).getPresentation();
+
+ JSDocumentationUtils.appendHyperLinkToElement(
+ element,
+ expression.getReferencedName(),
+ buffer,
+ presentation.getPresentableText(),
+ presentation.getLocationString()
+ );
+ buffer.append("
\n");
+ }
+ return buffer != null ? buffer.toString() : null;
+ }
+
+ _element = _element.getNavigationElement();
+ PsiElement element = findElementForWhichPreviousCommentWillBeSearched(_element);
+
+ boolean parameterDoc = element instanceof JSParameter;
+ if (parameterDoc) {
+ element = findElementForWhichPreviousCommentWillBeSearched(PsiTreeUtil.getParentOfType(element, JSFunction.class));
+ }
+
+ if (element != null) {
+ PsiElement docComment =
+ JSDocumentationUtils.findDocComment(element, _element instanceof JSAttributeNameValuePair ? originalElement : null);
+
+ if (docComment != null) {
+ docComment = findFirstDocComment(docComment);
+ element = findTargetElement(_element, element);
+ JSDocumentationBuilder builder = new JSDocumentationBuilder(element, originalElement);
+ JSDocumentationUtils.processDocumentationTextFromComment(docComment.getNode(), builder);
+
+ return parameterDoc ? builder.getParameterDoc(((JSParameter)_element).getName()) : builder.getDoc();
+ }
+
+ element = findTargetElement(_element, element);
+
+ if (element instanceof JSFunction) {
+ ASTNode initialComment = JSDocumentationUtils.findLeadingCommentInFunctionBody(element);
+
+ if (initialComment != null) {
+ JSDocumentationBuilder builder = new JSDocumentationBuilder(element, originalElement);
+ JSDocumentationUtils.processDocumentationTextFromComment(initialComment, builder);
+ return builder.getDoc();
+ }
+ }
+ }
+
+ String possibleCssName = findPossibleCssName(_element);
+ if (possibleCssName != null) {
+ try {
+ DocumentationProvider documentationProvider = getCssProvider(_element.getProject());
+ Method declaredMethod =
+ documentationProvider.getClass().getDeclaredMethod("generateDoc", String.class, PsiElement.class);
+ Object o = declaredMethod.invoke(null, possibleCssName, null);
+
+ if (o instanceof String) {
+ return (String)o;
+ }
+ }
+ catch (Exception ignored) {
+ }
+ }
+
+ return null;
+ }
+
+ @RequiredReadAction
+ private static PsiElement findTargetElement(PsiElement _element, PsiElement element) {
+ if (_element instanceof JSDefinitionExpression definition) {
+ if (definition.getParent() instanceof JSAssignmentExpression assignment) {
+ JSExpression rOperand = assignment.getROperand();
+ element = rOperand instanceof JSFunctionExpression ? rOperand : definition;
+ }
+ }
+ else if (_element instanceof JSFunctionExpression function) {
+ element = function;
+ }
+ else if (_element instanceof JSProperty property) {
+ if (property.getValue() instanceof JSFunction function) {
+ element = function;
+ }
+ }
+ else if (_element instanceof JSVariable variable) {
+ if (variable instanceof JSParameter parameter) {
+ return PsiTreeUtil.getParentOfType(parameter, JSFunction.class);
+ }
+ element = variable;
+ }
+ else if (_element instanceof JSAttributeNameValuePair nameValuePair) {
+ return nameValuePair;
+ }
+ return element;
+ }
+
+ @RequiredReadAction
+ private static PsiElement findFirstDocComment(PsiElement docComment) {
+ if (docComment.getNode().getElementType() == JSTokenTypes.END_OF_LINE_COMMENT) {
+ while (true) {
+ PsiElement prev = docComment.getPrevSibling();
+ if (prev instanceof PsiWhiteSpace whiteSpace) {
+ prev = whiteSpace.getPrevSibling();
+ }
+ if (prev == null) {
+ break;
+ }
+ if (prev.getNode().getElementType() != JSTokenTypes.END_OF_LINE_COMMENT) {
+ break;
+ }
+ docComment = prev;
+ }
+ }
+ return docComment;
+ }
+
+ private static String findPossibleCssName(PsiElement _element) {
+ if (_element instanceof JSDefinitionExpression definition) {
+ JSExpression expression = definition.getExpression();
+
+ if (expression instanceof JSReferenceExpression reference) {
+ String text = reference.getReferencedName();
+ if (text == null) {
+ return null;
+ }
+ StringBuilder sb = new StringBuilder(text.length());
+
+ for (int i = 0; i < text.length(); ++i) {
+ char ch = text.charAt(i);
+
+ if (Character.isUpperCase(ch)) {
+ sb.append('-').append(Character.toLowerCase(ch));
+ }
+ else {
+ sb.append(ch);
+ }
+ }
+
+ return sb.toString();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public PsiElement getDocumentationElementForLookupItem(PsiManager psiManager, Object object, PsiElement element) {
+ PsiElement psiElement = findElementForWhichPreviousCommentWillBeSearched(object);
+ if (psiElement != null && JSDocumentationUtils.findDocComment(psiElement) != null) {
+ return psiElement;
+ }
+ if (object instanceof PsiElement objPsiElement) {
+ return objPsiElement;
+ }
+ return null;
+ }
+
+ @RequiredReadAction
+ public static PsiElement findElementForWhichPreviousCommentWillBeSearched(Object object) {
+ if (object instanceof JSFunction function) {
+ PsiElement psiElement = function;
+ PsiElement parent = psiElement.getParent();
+ if (parent instanceof JSNewExpression newExpr) {
+ parent = newExpr.getParent();
+ }
+ if (parent instanceof JSProperty property) {
+ psiElement = property;
+ }
+ else if (parent instanceof JSAssignmentExpression assignment) {
+ psiElement = assignment.getParent();
+ }
+
+ if (function.isSetProperty() || function.isGetProperty()) {
+ for (PsiElement el = function.getPrevSibling(); el != null; el = el.getPrevSibling()) {
+ if (!(el instanceof PsiWhiteSpace) && !(el instanceof PsiComment)) {
+ if (el instanceof JSFunction prevFunction) {
+ String name = prevFunction.getName();
+
+ if (name != null && name.equals(function.getName()) && (
+ prevFunction.isGetProperty() && function.isSetProperty()
+ || prevFunction.isSetProperty() && function.isGetProperty()
+ )) {
+ PsiElement doc = JSDocumentationUtils.findDocComment(prevFunction);
+ if (doc != null) {
+ return prevFunction;
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ return psiElement;
+ }
+ else if (object instanceof JSProperty || object instanceof JSStatement || object instanceof JSClass) {
+ return (PsiElement)object;
+ }
+ else if (object instanceof PsiElement psiElement) {
+ PsiElement parent = psiElement.getParent();
+ if (parent instanceof JSAssignmentExpression assignment) {
+ return assignment.getParent();
+ }
+ else if (parent instanceof JSVarStatement varStatement) {
+ if (varStatement.getFirstChild() instanceof JSAttributeList && JSDocumentationUtils.findDocComment(psiElement) != null) {
+ return psiElement;
+ }
+ return varStatement;
+ }
+ else if (parent instanceof JSAttribute attribute) {
+ PsiElement attrParent = attribute.getParent();
+ if (attrParent.getFirstChild() == attribute) {
+ PsiElement attrGrandParent = attrParent.getParent();
+ if (attrGrandParent instanceof JSFile) {
+ return attrParent;
+ }
+ return attrGrandParent;
+ }
+ return attribute;
+ }
+ else if (parent instanceof JSSuppressionHolder suppressionHolder) {
+ return suppressionHolder;
+ }
+ else {
+ return psiElement;
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public PsiElement getDocumentationElementForLink(PsiManager psiManager, String link, PsiElement context) {
+ return getDocumentationElementForLinkStatic(psiManager, link, context);
+ }
+
+ @Nullable
+ private static PsiElement getDocumentationElementForLinkStatic(PsiManager psiManager, String link, @Nonnull PsiElement context) {
+ int delimiterIndex = link.lastIndexOf(':');
+
+ String attributeType = null;
+ String attributeName = null;
+ for (Map.Entry e : DOCUMENTED_ATTRIBUTES.entrySet()) {
+ String pattern = "." + e.getValue();
+ if (link.contains(pattern)) {
+ attributeType = e.getKey();
+ attributeName = link.substring(link.indexOf(pattern) + pattern.length());
+ link = link.substring(0, link.indexOf(pattern));
+ break;
+ }
+ }
+ if (delimiterIndex != -1 && attributeType == null) {
+ int delimiterIndex2 = link.lastIndexOf(':', delimiterIndex - 1);
+ String fileName = link.substring(0, delimiterIndex2).replace(File.separatorChar, '/');
+ String name = link.substring(delimiterIndex2 + 1, delimiterIndex);
+ int offset = Integer.parseInt(link.substring(delimiterIndex + 1));
+ return JavaScriptIndex.findSymbolByFileAndNameAndOffset(fileName, name, offset);
+ }
+ else if (attributeType != null) {
+ PsiElement clazz = JSResolveUtil.findClassByQName(link, context.getResolveScope(), psiManager.getProject());
+ if (!(clazz instanceof JSClass)) {
+ return null;
+ }
+ return findNamedAttribute((JSClass)clazz, attributeType, attributeName);
+ }
+ else {
+ PsiElement clazz = JSResolveUtil.findClassByQName(link, context.getResolveScope(), psiManager.getProject());
+ if (clazz == null && link.contains(".")) {
+ String qname = link.substring(0, link.lastIndexOf('.'));
+ clazz = JSResolveUtil.findClassByQName(qname, context.getResolveScope(), psiManager.getProject());
+ if (clazz instanceof JSClass jsClass) {
+ String member = link.substring(link.lastIndexOf('.') + 1);
+
+ if (member.endsWith("()")) {
+ member = member.substring(0, member.length() - 2);
+
+ PsiElement result = findMethod(jsClass, member);
+ if (result == null) {
+ result = findProperty(jsClass, member); // user might refer to a property
+ }
+ return result;
+ }
+ else {
+ PsiElement result = jsClass.findFieldByName(member);
+ if (result == null) {
+ result = findProperty(jsClass, member);
+ }
+ if (result == null) {
+ result = findMethod(jsClass, member); // user might forget brackets
+ }
+ return result;
+ }
+ }
+ }
+
+ if (clazz instanceof JSVariable) {
+ return clazz;
+ }
+
+ if (link.endsWith("()")) {
+ link = link.substring(0, link.length() - 2);
+ clazz = JSResolveUtil.findClassByQName(link, context.getResolveScope(), psiManager.getProject());
+ if (clazz instanceof JSFunction) {
+ return clazz;
+ }
+ }
+ return clazz;
+ }
+ }
+
+ @Nullable
+ @RequiredReadAction
+ protected static JSAttributeNameValuePair findNamedAttribute(JSClass clazz, String type, String name) {
+ SimpleReference attribute = new SimpleReference<>();
+ JSResolveUtil.processMetaAttributesForClass(clazz, new JSResolveUtil.MetaDataProcessor() {
+ @Override
+ @RequiredReadAction
+ public boolean process(@Nonnull JSAttribute jsAttribute) {
+ if (type.equals(jsAttribute.getName())) {
+ JSAttributeNameValuePair jsAttributeNameValuePair = jsAttribute.getValueByName("name");
+ if (jsAttributeNameValuePair != null && name.equals(jsAttributeNameValuePair.getSimpleValue())) {
+ attribute.set(jsAttributeNameValuePair);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean handleOtherElement(PsiElement el, PsiElement context, @Nullable SimpleReference continuePassElement) {
+ return true;
+ }
+ });
+ return attribute.get();
+ }
+
+ private static PsiElement findProperty(JSClass jsClass, String name) {
+ PsiElement result = jsClass.findFunctionByNameAndKind(name, JSFunction.FunctionKind.GETTER);
+ if (result == null) {
+ result = jsClass.findFunctionByNameAndKind(name, JSFunction.FunctionKind.SETTER);
+ }
+ return result;
+ }
+
+ private static PsiElement findMethod(JSClass jsClass, String name) {
+ PsiElement result = jsClass.findFunctionByNameAndKind(name, JSFunction.FunctionKind.CONSTRUCTOR);
+ if (result == null) {
+ result = jsClass.findFunctionByNameAndKind(name, JSFunction.FunctionKind.SIMPLE);
+ }
+ return result;
+ }
+
+ @Nullable
+ @Override
+ public PsiComment findExistingDocComment(PsiComment contextElement) {
+ return contextElement;
+ }
+
+ @Nullable
+ @Override
+ public Pair parseContext(@Nonnull PsiElement element) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ @RequiredReadAction
+ public String generateDocumentationContentStub(PsiComment contextComment) {
+ for (PsiElement el = contextComment.getParent(); el != null; el = el.getNextSibling()) {
+ if (el instanceof JSProperty property) {
+ if (property.getValue() instanceof JSFunction function) {
+ return doGenerateDoc(function);
+ }
+ }
+ else if (el instanceof JSFunction function) {
+ return doGenerateDoc(function);
+ }
+ else if (el instanceof JSExpressionStatement expression) {
+ if (expression.getExpression() instanceof JSAssignmentExpression assignment
+ && assignment.getROperand() instanceof JSFunctionExpression functionExpr) {
+ return doGenerateDoc(functionExpr.getFunction());
+ }
+ }
+ else if (el instanceof JSVarStatement varStatement) {
+ JSVariable[] variables = varStatement.getVariables();
+ if (variables.length > 0 && variables[0].getInitializer() instanceof JSFunctionExpression functionExpr) {
+ return doGenerateDoc(functionExpr.getFunction());
+ }
+ break;
+ }
+ }
+ return null;
+ }
+
+ @RequiredReadAction
+ private static String doGenerateDoc(JSFunction function) {
+ StringBuilder builder = new StringBuilder();
+ JSParameterList parameterList = function.getParameterList();
+ PsiFile containingFile = function.getContainingFile();
+ boolean ecma = containingFile.getLanguage() == JavaScriptSupportLoader.ECMA_SCRIPT_L4;
+
+ if (parameterList != null) {
+ for (JSParameter parameter : parameterList.getParameters()) {
+ builder.append("* @param ").append(parameter.getName());
+ //String s = JSPsiImplUtils.getTypeFromDeclaration(parameter);
+ //if (s != null) builder.append(" : ").append(s);
+ builder.append("\n");
+ }
+ }
+
+ if (ecma) {
+ String s = JSPsiImplUtils.getTypeFromDeclaration(function);
+
+ if (s != null && !"void".equals(s)) {
+ builder.append("* @return ");
+
+ //builder.append(s);
+ builder.append("\n");
+ }
+ }
+
+ return builder.toString();
+ }
+
+ @Nullable
+ @RequiredReadAction
+ protected static String getSeeAlsoLinkResolved(PsiElement originElement, String link) {
+ JSQualifiedNamedElement qualifiedElement = findParentQualifiedElement(originElement);
+ if (qualifiedElement == null) {
+ return null;
+ }
+ String linkToResolve = getLinkToResolve(qualifiedElement, link);
+ PsiElement resolvedElement = getDocumentationElementForLinkStatic(originElement.getManager(), linkToResolve, originElement);
+ if (resolvedElement != null) {
+ return linkToResolve;
+ }
+ return null;
+ }
+
+ private static String getLinkToResolve(JSQualifiedNamedElement origin, String link) {
+ String originQname = origin.getQualifiedName();
+ if (link.length() == 0) {
+ return originQname;
+ }
+ else if (StringUtil.startsWithChar(link, '#')) {
+ if (origin instanceof JSClass) {
+ return originQname + "." + link.substring(1);
+ }
+ else {
+ String aPackage = StringUtil.getPackageName(originQname);
+ return aPackage + "." + link.substring(1);
+ }
+ }
+ else {
+ String linkFile = link.contains("#") ? link.substring(0, link.lastIndexOf('#')) : link;
+ String linkAnchor = link.contains("#") ? link.substring(link.lastIndexOf('#') + 1) : null;
+
+ String qname;
+ if (StringUtil.endsWithIgnoreCase(linkFile, HTML_EXTENSION)) {
+ String prefix = StringUtil.getPackageName(originQname);
+ while (linkFile.startsWith("../")) {
+ linkFile = linkFile.substring("../".length());
+ prefix = StringUtil.getPackageName(prefix);
+ }
+
+ String linkFilename;
+ if (linkFile.endsWith(PACKAGE_FILE)) {
+ linkFilename = StringUtil.trimEnd(linkFile, PACKAGE_FILE);
+ linkFilename = StringUtil.trimEnd(linkFilename, "/");
+ }
+ else {
+ linkFilename = linkFile.substring(0, linkFile.lastIndexOf("."));
+ }
+ if (linkFilename.length() > 0) {
+ qname = (prefix.length() > 0 ? prefix + "." : prefix) + linkFilename.replaceAll("/", ".");
+ }
+ else {
+ qname = prefix;
+ }
+ }
+ else {
+ qname = linkFile;
+ }
+
+ return linkAnchor != null ? (qname.length() > 0 ? qname + "." : qname) + linkAnchor : qname;
+ }
+ }
+
+ @Nullable
+ @RequiredReadAction
+ protected static JSQualifiedNamedElement findParentQualifiedElement(PsiElement element) {
+ if (element instanceof JSClass jsClass) {
+ return jsClass;
+ }
+ if (element instanceof JSFunction || element instanceof JSVariable) {
+ PsiElement parent = JSResolveUtil.findParent(element);
+ if (parent instanceof JSClass jsClass) {
+ return jsClass;
+ }
+ else if (parent instanceof JSFile) {
+ return (JSQualifiedNamedElement) element;
+ }
+ }
+
+ JSAttribute attribute = null;
+ if (element instanceof JSAttribute jsAttribute) {
+ attribute = jsAttribute;
+ }
+ else if (element instanceof JSAttributeNameValuePair nameValuePair) {
+ attribute = (JSAttribute)nameValuePair.getParent();
+ }
+
+ if (attribute != null && DOCUMENTED_ATTRIBUTES.containsKey(attribute.getName())) {
+ JSClass jsClass = PsiTreeUtil.getParentOfType(element, JSClass.class);
+ if (jsClass != null) {
+ return jsClass;
+ }
+ }
+
+ return null;
+ }
+
+ @Nonnull
+ @Override
+ public Language getLanguage() {
+ return JavaScriptLanguage.INSTANCE;
+ }
}
diff --git a/base-impl/src/main/java/com/intellij/javascript/documentation/JSDocumentationUtils.java b/base-impl/src/main/java/com/intellij/javascript/documentation/JSDocumentationUtils.java
index 7501dd11..41c3a360 100644
--- a/base-impl/src/main/java/com/intellij/javascript/documentation/JSDocumentationUtils.java
+++ b/base-impl/src/main/java/com/intellij/javascript/documentation/JSDocumentationUtils.java
@@ -26,6 +26,7 @@
import com.intellij.lang.javascript.JSTokenTypes;
import com.intellij.lang.javascript.psi.*;
import com.intellij.lang.javascript.psi.resolve.JSResolveUtil;
+import consulo.annotation.access.RequiredReadAction;
import consulo.language.ast.ASTNode;
import consulo.language.ast.IElementType;
import consulo.language.ast.TokenSet;
@@ -34,11 +35,10 @@
import consulo.language.psi.*;
import consulo.language.psi.util.PsiTreeUtil;
import consulo.util.lang.StringUtil;
-import consulo.util.lang.ref.Ref;
-import org.jetbrains.annotations.NonNls;
+import consulo.util.lang.ref.SimpleReference;
+import jakarta.annotation.Nonnull;
+import jakarta.annotation.Nullable;
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
@@ -46,933 +46,811 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-public class JSDocumentationUtils
-{
- private static final
- @NonNls
- Pattern ourDojoParametersPattern = Pattern.compile("^\\s*(\\w+):(.*)$");
- private static final
- @NonNls
- Pattern ourJSDocParametersPattern = Pattern.compile("^\\s*@param\\s*(?:\\{([^\\}]+)\\}\\s*)?(\\w+)?(?:\\s:\\s(\\S+))?(?:\\s*\\[(\\w+)(?:\\.(\\w+))?" +
- "(?:=([^\\]]*))?\\])?(.*)$");
- private static final
- @NonNls
- Pattern ourJSDocEventPattern = Pattern.compile("^\\s*@event\\s*(\\w+)(?:\\s:\\s(\\S+))?(.*)$");
- private static final
- @NonNls
- Pattern ourJSDocRemarkPattern = Pattern.compile("^\\s*@remarks (.*)$");
- private static final
- @NonNls
- Pattern ourJSDocMethodPattern = Pattern.compile("^\\s*@method\\s*(\\w+)(.*)$");
- private static final
- @NonNls
- Pattern ourJSDocClassPattern = Pattern.compile("^\\s*@class\\s*(\\w+(?:\\.\\w+)*)(.*)$");
- private static final
- @NonNls
- Pattern ourJSDocDeprecatedPattern = Pattern.compile("^\\s*@deprecated\\s*(.*)$");
-
- private static final
- @NonNls
- Pattern ourJSDocConstructorPattern = Pattern.compile("^\\s*@constructor$");
- private static final
- @NonNls
- Pattern ourJSDocFinalPattern = Pattern.compile("^\\s*@final$");
- private static final
- @NonNls
- Pattern ourJSDocPrivatePattern = Pattern.compile("^\\s*@private$");
- private static final
- @NonNls
- Pattern ourJSDocPublicPattern = Pattern.compile("^\\s*@public$");
- private static final
- @NonNls
- Pattern ourJSDocProtectedPattern = Pattern.compile("^\\s*@protected$");
-
- private static final
- @NonNls
- Pattern ourJSDocOptionalPattern = Pattern.compile("^\\s*@optional(.*)$");
- private static final
- @NonNls
- Pattern ourJSDocStaticPattern = Pattern.compile("^\\s*@static$");
- private static final
- @NonNls
- Pattern ourJSDocSeePattern = Pattern.compile("^\\s*@see (.*)$");
- private static final
- @NonNls
- Pattern ourJSDocDescriptionPattern = Pattern.compile("^\\s*@description\\s*(.+)$");
- private static final
- @NonNls
- Pattern ourJSDocReturnPattern = Pattern.compile("^\\s*@return(?:s)?\\s*(?:(?:\\{|:)?\\s*([^\\s\\}]+)\\s*\\}?\\s*)?(.*)$");
- private static final
- @NonNls
- Pattern ourJSDocNamespacePattern = Pattern.compile("^\\s*@namespace\\s*([\\w\\.]+)(.*)$");
-
- private static final
- @NonNls
- Pattern ourJSDocPropertyPattern = Pattern.compile("^\\s*@property\\s*(\\w+)(.*)$");
- private static final
- @NonNls
- Pattern ourJSDocTypePattern = Pattern.compile("^\\s*@type\\s*\\{?([^\\s\\}]+)\\}?(.*)$");
- private static final
- @NonNls
- Pattern ourJSDocRequiresPattern = Pattern.compile("^\\s*@requires\\s*(\\S+)(.*)$");
- private static final
- @NonNls
- Pattern ourJSDocDefaultPattern = Pattern.compile("^\\s*@default\\s*(.*)$");
- private static final
- @NonNls
- Pattern ourJSDocExtendsPattern = Pattern.compile("^\\s*@extends\\s*(.*)$");
-
- private static final
- @NonNls
- Map patternToHintMap = new HashMap();
- private static final
- @NonNls
- Map patternToMetaDocTypeMap = new HashMap();
-
- static
- {
- patternToHintMap.put(ourDojoParametersPattern, ":");
- patternToMetaDocTypeMap.put(ourDojoParametersPattern, JSDocumentationProcessor.MetaDocType.PARAMETER);
- patternToHintMap.put(ourJSDocParametersPattern, "@pa");
- patternToMetaDocTypeMap.put(ourJSDocParametersPattern, JSDocumentationProcessor.MetaDocType.PARAMETER);
-
- patternToHintMap.put(ourJSDocMethodPattern, "@m");
- patternToMetaDocTypeMap.put(ourJSDocMethodPattern, JSDocumentationProcessor.MetaDocType.METHOD);
-
- patternToHintMap.put(ourJSDocOptionalPattern, "@o");
- patternToMetaDocTypeMap.put(ourJSDocOptionalPattern, JSDocumentationProcessor.MetaDocType.OPTIONAL_PARAMETERS);
-
- patternToHintMap.put(ourJSDocEventPattern, "@ev");
- patternToMetaDocTypeMap.put(ourJSDocEventPattern, JSDocumentationProcessor.MetaDocType.EVENT);
-
- patternToHintMap.put(ourJSDocExtendsPattern, "@ex");
- patternToMetaDocTypeMap.put(ourJSDocExtendsPattern, JSDocumentationProcessor.MetaDocType.EXTENDS);
-
- patternToHintMap.put(ourJSDocRemarkPattern, "@rem");
- patternToMetaDocTypeMap.put(ourJSDocRemarkPattern, JSDocumentationProcessor.MetaDocType.NOTE);
-
- patternToHintMap.put(ourJSDocReturnPattern, "@ret");
- patternToMetaDocTypeMap.put(ourJSDocReturnPattern, JSDocumentationProcessor.MetaDocType.RETURN);
-
- patternToHintMap.put(ourJSDocPublicPattern, "@pu");
- patternToMetaDocTypeMap.put(ourJSDocPublicPattern, JSDocumentationProcessor.MetaDocType.PUBLIC);
-
- patternToHintMap.put(ourJSDocProtectedPattern, "@prot");
- patternToMetaDocTypeMap.put(ourJSDocProtectedPattern, JSDocumentationProcessor.MetaDocType.PROTECTED);
-
- patternToHintMap.put(ourJSDocStaticPattern, "@st");
- patternToMetaDocTypeMap.put(ourJSDocStaticPattern, JSDocumentationProcessor.MetaDocType.STATIC);
-
- patternToHintMap.put(ourJSDocSeePattern, "@se");
- patternToMetaDocTypeMap.put(ourJSDocSeePattern, JSDocumentationProcessor.MetaDocType.SEE);
-
- patternToHintMap.put(ourJSDocDescriptionPattern, "@des");
- patternToMetaDocTypeMap.put(ourJSDocDescriptionPattern, JSDocumentationProcessor.MetaDocType.DESCRIPTION);
-
- patternToHintMap.put(ourJSDocDeprecatedPattern, "@dep");
- patternToMetaDocTypeMap.put(ourJSDocDeprecatedPattern, JSDocumentationProcessor.MetaDocType.DEPRECATED);
-
- patternToHintMap.put(ourJSDocConstructorPattern, "@co");
-
- patternToMetaDocTypeMap.put(ourJSDocConstructorPattern, JSDocumentationProcessor.MetaDocType.CONSTRUCTOR);
-
- patternToHintMap.put(ourJSDocClassPattern, "@cl");
- patternToMetaDocTypeMap.put(ourJSDocClassPattern, JSDocumentationProcessor.MetaDocType.CLASS);
- patternToHintMap.put(ourJSDocPrivatePattern, "@pri");
- patternToMetaDocTypeMap.put(ourJSDocPrivatePattern, JSDocumentationProcessor.MetaDocType.PRIVATE);
-
- patternToHintMap.put(ourJSDocNamespacePattern, "@n");
- patternToMetaDocTypeMap.put(ourJSDocNamespacePattern, JSDocumentationProcessor.MetaDocType.NAMESPACE);
-
- patternToHintMap.put(ourJSDocPropertyPattern, "@prop");
- patternToHintMap.put(ourJSDocTypePattern, "@t");
- patternToMetaDocTypeMap.put(ourJSDocTypePattern, JSDocumentationProcessor.MetaDocType.TYPE);
-
- patternToHintMap.put(ourJSDocFinalPattern, "@f");
- patternToMetaDocTypeMap.put(ourJSDocFinalPattern, JSDocumentationProcessor.MetaDocType.FINAL);
- patternToHintMap.put(ourJSDocRequiresPattern, "@req");
- patternToMetaDocTypeMap.put(ourJSDocRequiresPattern, JSDocumentationProcessor.MetaDocType.REQUIRES);
-
- patternToHintMap.put(ourJSDocDefaultPattern, "@def");
- patternToMetaDocTypeMap.put(ourJSDocDefaultPattern, JSDocumentationProcessor.MetaDocType.DEFAULT);
- }
-
- private static final
- @NonNls
- Map prefixToPatternToHintMap = new HashMap();
-
- static
- {
- prefixToPatternToHintMap.put(Pattern.compile("^\\s*description:(.*)$"), "descr");
-
- prefixToPatternToHintMap.put(Pattern.compile("^ summary(?:\\:)?(.*)$"), "summ");
-
- prefixToPatternToHintMap.put(Pattern.compile("^\\s*\\*(?:\\*)?(.*)$"), "*");
-
- prefixToPatternToHintMap.put(Pattern.compile("^[/]+(.*)$"), "/");
-
- prefixToPatternToHintMap.put(Pattern.compile("^\\s*Parameters:(.*)$"), "Parame");
- }
-
- public static void processDocumentationTextFromComment(ASTNode _initialComment, JSDocumentationProcessor processor)
- {
- ASTNode prev = _initialComment.getTreePrev();
-
- if(prev != null && prev.getPsi() instanceof OuterLanguageElement)
- {
- while(prev.getPsi() instanceof OuterLanguageElement)
- {
- prev = prev.getTreePrev();
- }
- }
- else
- {
- prev = null;
- }
-
- if(prev != null && !(prev.getPsi() instanceof PsiComment))
- {
- prev = null;
- }
-
- final ASTNode initialComment = prev != null ? prev : _initialComment;
- Enumeration