package com.hyway.html;
import com.intellij.lang.html.HTMLLanguage;
public class HywayLanguage extends HTMLLanguage {
public static final HywayLanguage INSTANCE = new HywayLanguage();
private HywayLanguage() {
super(HywayLanguage.INSTANCE, "Hyway", "text/hyway", "application/xhtml+xml");
}
}
package com.hyway.html;
import com.intellij.openapi.util.IconLoader;
import javax.swing.*;
/**
* @author tangrufei
* @date 2023-02-20 14:35
*/
public class HywayIcons {
public static final Icon FILE = IconLoader.getIcon("/icons/icon_file.png", HywayIcons.class);
public static final Icon TYPE = IconLoader.getIcon("/icons/icon_type.png", HywayIcons.class);
}
package com.hyway.html;
import com.intellij.lang.html.HTMLLanguage;
import com.intellij.openapi.fileTypes.LanguageFileType;
import com.intellij.openapi.util.NlsContexts;
import com.intellij.openapi.util.NlsSafe;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
/**
* @author tangrufei
* @date 2023-02-20 14:39
*/
public class HywayFileType extends LanguageFileType {
public static final HywayFileType INSTANCE = new HywayFileType();
private HywayFileType() {
super(HTMLLanguage.INSTANCE);
}
@Override
public @NonNls @NotNull String getName() {
return "Hyway File Type";
}
@Override
public @NlsContexts.Label @NotNull String getDescription() {
return "Hyway language file";
}
@Override
public @NlsSafe @NotNull String getDefaultExtension() {
return "hyway";
}
@Override
public @NonNls @Nullable String getCharset(@NotNull VirtualFile file, byte @NotNull [] content) {
return "UTF-8";
}
@Override
public Icon getIcon() {
return HywayIcons.FILE;
}
}
/*
package com.hyway;
import com.intellij.openapi.fileTypes.FileTypeConsumer;
import com.intellij.openapi.fileTypes.FileTypeFactory;
import org.jetbrains.annotations.NotNull;
*/
/**
*//*
public class HywayFileTypeFactory extends FileTypeFactory {
@Override
public void createFileTypes(@NotNull FileTypeConsumer fileTypeConsumer) {
fileTypeConsumer.consume(HywayFileType.INSTANCE);
}
}
*/
位于resource下面的META-INFO文件夹下的plug.xml文件中申明拓展
<extensions defaultExtensionNs="com.intellij">
<!-- <fileTypeFactory implementation="com.hyway.HywayFileTypeFactory" >-->
<!-- </fileTypeFactory>-->
<fileType name="Hyway File Type" language="HTML" fieldName="INSTANCE" extensions="hyway"
implementationClass="com.hyway.html.HywayFileType"
>
</fileType>
</extensions>
方式一:通过自带的Swing ui-Desiger(通过拖拽)->然后创建一个action,action为new group->new file
方式二:自己手写去创建 这里提供我的工具类
package com.hyway.base.action;
import com.hyway.html.HywayTemplateFactory;
import com.intellij.CommonBundle;
import com.intellij.ide.actions.CreateElementActionBase;
import com.intellij.openapi.fileTypes.LanguageFileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NonNls;
import javax.swing.*;
/** create by tangrufei by 2023/2/27 */
public abstract class NewHywayFileActionBase extends CreateElementActionBase {
public NewHywayFileActionBase(String text, String description, Icon icon) {
super(text, description, icon);
}
public NewHywayFileActionBase() {
}
@Override
protected PsiElement[] invokeDialog(Project project, PsiDirectory psiDirectory) {
MyInputValidator inputValidator = new MyInputValidator(project, psiDirectory);
Messages.showInputDialog(project, getDialogPrompt(), getDialogTitle(), null, "", inputValidator);
return inputValidator.getCreatedElements();
}
@Override
protected PsiElement[] create(String s, PsiDirectory psiDirectory) throws Exception {
return doCreate(s, psiDirectory);
}
@Override
protected String getErrorTitle() {
return CommonBundle.getErrorTitle();
}
public PsiFile createFileFromTemplate(final PsiDirectory directory,
String className,
@NonNls String templateName,
String defaultExtension,
@NonNls String... parameters
) throws IncorrectOperationException {
final String ext = "." +defaultExtension;
String filename = (className.endsWith(ext)) ? className : className + ext;
return new HywayTemplateFactory(){
}.createFromTemplate(directory, className, filename, templateName, parameters);
}
protected abstract PsiElement[] doCreate(String name, PsiDirectory directory);
protected abstract String getDialogPrompt();
protected abstract String getDialogTitle();
}
package com.hyway.html;
import com.hyway.base.action.NewHywayFileActionBase;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
/**
* Created by moxun on 16/10/11.
*/
public class NewHywayFileAction extends NewHywayFileActionBase {
public NewHywayFileAction() {
super(HywayBundle.message("newfile.menu.action.text"),
HywayBundle.message("newfile.menu.action.description"),
HywayIcons.FILE);
}
@Override
protected PsiElement[] doCreate(String name, PsiDirectory directory) {
PsiFile file = createFileFromTemplate(directory, name, HywayTemplateFactory.NEW_Hyway_TEMPLATE_NAME,HywayFileType.INSTANCE.getDefaultExtension() );
PsiElement child = file.getLastChild();
return child != null ? new PsiElement[]{file, child} : new PsiElement[]{file};
}
@Override
protected String getDialogPrompt() {
return HywayBundle.message("newfile.dialog.prompt");
}
@Override
protected String getDialogTitle() {
return HywayBundle.message("newfile.dialog.title");
}
@Override
protected String getCommandName() {
return HywayBundle.message("newfile.command.name");
}
@Override
protected String getActionName(PsiDirectory psiDirectory, String s) {
return HywayBundle.message("newfile.menu.action.text");
}
}
package com.hyway.html;
import com.intellij.lang.Language;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import org.jetbrains.annotations.NotNull;
//文件视图提供程序
public class HywayFileViewProviderFactory implements FileViewProviderFactory {
@NotNull
@Override
public FileViewProvider createFileViewProvider(@NotNull VirtualFile file, @NotNull Language language, @NotNull PsiManager psiManager, boolean eventSystemEnabled) {
return new SingleRootFileViewProvider(psiManager, file, eventSystemEnabled) {
@NotNull
@Override
protected PsiFile createFile(@NotNull Language lang) {
return lang == HywayLanguage.INSTANCE ? new HywayFile(this) : super.createFile(lang);
}
};
}
}
package com.hyway.js.token
import com.hyway.js.language.HywayJSLanguage
import com.intellij.psi.tree.IElementType
import org.jetbrains.annotations.NonNls
class HywayJSTokenType(@NonNls debugName: String) : IElementType(debugName, HywayJSLanguage.INSTANCE) {
override fun toString(): String {
return "HywayJSToken." + super.toString()
}
}
class HywayJSElementType(debugName:String) : IElementType (debugName, HywayJSLanguage.INSTANCE) {
}
词法分析器定义了一个文件的内容是如何被分解为Token的,创建Lexer的一个简单的方式是使用JFlex。定义一个lexer规则文件然后通过IDE即可生成Lexer。
实现 ParserDefinition 接口,创建一个Parser定义,并在plugin.xml中的 extensions 节点中使用 lang.parserDefinition 标签注册该parser
package com.hyway.html.annotator;
import com.hyway.base.util.CodeUtil;
import com.hyway.html.lint.*;
import com.hyway.html.quickfix.QuickFixAction;
import com.hyway.html.utils.HywayFileUtil;
import com.intellij.lang.annotation.Annotation;
import com.intellij.lang.annotation.AnnotationHolder;
import com.intellij.lang.annotation.Annotator;
import com.intellij.lang.javascript.psi.*;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.html.HtmlTag;
import com.intellij.psi.xml.*;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
/** create by tangrufei by 2023/2/21 */
/**
* 注释器
*/
public class HywayAnnotator implements Annotator {
/**
* js嵌入内容
*/
private JSEmbeddedContent script;
/**
* JS对象文字表达式
*/
private JSObjectLiteralExpression moduleExports;
@Override
public void annotate(@NotNull PsiElement psiElement, @NotNull AnnotationHolder annotationHolder) {
/**
* 判断项目树是否包含.hyway结尾的文件,不包含则不启用·
*/
if (!psiElement.getContainingFile().getVirtualFile().getName().toLowerCase().endsWith(".hyway")) {
return;
}
if (psiElement instanceof XmlDocument) {
checkStructure(psiElement, annotationHolder);
}
/** 通过检查 XML 标签的属性来判断数据类型。*/
if (psiElement instanceof XmlTag && ((XmlTag) psiElement).getName().equals("script")) {
label:
for (PsiElement element : psiElement.getChildren()) {
if (element instanceof JSEmbeddedContent) {
script = (JSEmbeddedContent) element;
for (PsiElement element1 : script.getChildren()) {
if (element1 instanceof JSExpressionStatement) {
for (PsiElement element2 : element1.getChildren()) {
if (element2 instanceof JSAssignmentExpression) {
PsiElement[] children = element2.getChildren();
if (children.length == 2) {
if (children[0].getText().equals("module.exports")) {
moduleExports = (JSObjectLiteralExpression) children[1];
break label;
}
}
}
}
}
}
}
}
}
}
private void checkAttributes(XmlTag tag, @NotNull AnnotationHolder annotationHolder) {
if (tag.getLocalName().toLowerCase().equals("template")) {
for (XmlTag child : tag.getSubTags()) {
checkAttributeValue(child, annotationHolder);
}
}
}
private
@NotNull
LintResult verifyDataType(String type, String s) {
if ("function".equals(type.toLowerCase())) {
return verifyFunction(s);
}
LintResult result = new LintResult();
if (s.contains(".") || Pattern.compile("\\[\\d+\\]").matcher(s).matches()) {
result.setCode(LintResultType.PASSED);
result.setDesc("Skip analyse array or object");
return result;
}
s = CodeUtil.getVarNameFromMustache(s);
if (type != null && type.equalsIgnoreCase("boolean")) {
if (CodeUtil.maybeBooleanExpression(s)) {
result.setCode(LintResultType.PASSED);
result.setDesc("Boolean Expression");
return result;
}
}
if (moduleExports == null) {
result.setCode(LintResultType.UNRESOLVED_VAR);
result.setDesc("Unresolved property '" + s + "'");
return result;
}
JSProperty data = moduleExports.findProperty("data");
if (data == null || data.getValue() == null) {
result.setCode(LintResultType.UNRESOLVED_VAR);
result.setDesc("Unresolved property '" + s + "'");
return result;
}
for (PsiElement element : data.getValue().getChildren()) {
if (!(element instanceof JSProperty)) {
result.setCode(LintResultType.PASSED);
result.setDesc("Unsupported data type: " + element.getClass().getSimpleName());
return result;
}
String varName = ((JSProperty) element).getName();
if (varName == null) {
result.setCode(LintResultType.UNRESOLVED_VAR);
result.setDesc("Unresolved property '" + s + "'");
return result;
}
if (varName.equals(s)) {
String typeString = HywayFileUtil.getJSPropertyType((JSProperty) element);
if (match(type, typeString)) {
result.setCode(LintResultType.PASSED);
result.setDesc("Lint passed");
return result;
} else {
JSExpression expression = ((JSProperty) element).getValue();
if (expression != null) {
String value = expression.getText();
String guessedType = CodeUtil.guessStringType(value);
if (match(type, guessedType)) {
result.setCode(LintResultType.PASSED);
result.setDesc("Lint passed");
return result;
}
}
result.setCode(LintResultType.WRONG_VALUE_TYPE);
result.setDesc("Wrong property type. expect " + type + ", found " + typeString);
return result;
}
}
}
result.setCode(LintResultType.UNRESOLVED_VAR);
result.setDesc("Unresolved property '" + s + "'");
return result;
}
private
@NotNull
LintResult verifyFunction(String s) {
s = CodeUtil.getFunctionNameFromMustache(s);
LintResult result = new LintResult();
if (moduleExports == null) {
result.setCode(LintResultType.UNRESOLVED_VAR);
result.setDesc("Unresolved function '" + s + "'");
return result;
}
JSProperty data = moduleExports.findProperty("methods");
if (data == null || data.getValue() == null) {
result.setCode(LintResultType.UNRESOLVED_VAR);
result.setDesc("Unresolved function '" + s + "'");
return result;
}
for (PsiElement e : data.getValue().getChildren()) {
if (e instanceof JSProperty) {
for (PsiElement e1 : e.getChildren()) {
if (e1 instanceof JSFunctionExpression) {
if (s.equals(((JSFunctionExpression) e1).getName())) {
result.setCode(LintResultType.PASSED);
result.setDesc("Lint passed");
return result;
}
}
}
}
}
result.setCode(LintResultType.UNRESOLVED_VAR);
result.setDesc("Unresolved function '" + s + "'");
return result;
}
private LintResult verifyVarAndFunction(String type, String s) {
LintResult l1 = verifyDataType(type, s);
if (!l1.passed()) {
LintResult l2 = verifyFunction(s);
if (l2.passed()) {
return l2;
} else {
return l1;
}
} else {
return l1;
}
}
private boolean match(String type, String jsType) {
if (type.equals(jsType.toLowerCase())) {
return true;
}
if (type.equals("var")) {
return true;
}
return false;
}
/**
* 检查属性值
* @param xmlTag
* @param annotationHolder
*/
private void checkAttributeValue(XmlTag xmlTag, @NotNull AnnotationHolder annotationHolder) {
HywayTag tag = DirectiveLint.getHywayTag(xmlTag.getName());
if (tag != null) {
//check self attrs
List<String> parent = tag.parent;
if (xmlTag.getParentTag() != null) {
String name = xmlTag.getParentTag().getName();
if (parent != null && !parent.contains(name)) {
annotationHolder.createErrorAnnotation(xmlTag, "Element '" + xmlTag.getName()
+ "' only allowed " + tag.parent.toString() + " as parent element");
}
}
Set<String> extAttrs = tag.getExtAttrs();
for (XmlAttribute attr : xmlTag.getAttributes()) {
String attrName = attr.getName();
String attrValue = attr.getValue();
Attribute validAttr = tag.getAttribute(attrName);
if (HywayFileUtil.isMustacheValue(attrValue)) {
String bindVar = attrValue.replaceAll("\\{+", "").replaceAll("\\}+", "");
LintResult ret = null;
String type = "var";
if (validAttr == null) {
ret = verifyVarAndFunction("var", bindVar);
} else {
if (!inRepeat(xmlTag) || "repeat".equals(attrName)) {
type = validAttr.valueType;
ret = verifyDataType(validAttr.valueType, bindVar);
}
}
if (inRepeat(xmlTag) && !"repeat".equals(attrName)) {
//repeat 绑定数组内的数据在lint时可能不存在, 跳过检测
ret = new LintResult(LintResultType.PASSED, "Skip repeat tag");
}
if (!ret.passed()) {
Annotation annotation = annotationHolder
.createErrorAnnotation(attr.getValueElement(), ret.getDesc());
if (ret.getCode() == LintResultType.UNRESOLVED_VAR) {
annotation.registerFix(new QuickFixAction(bindVar, type));
}
}
}
if (extAttrs.contains(attrName)) {
if (!HywayFileUtil.isMustacheValue(attrValue) && validAttr != null && !CodeUtil.maybeInLineExpression(attrValue)) {
//正则匹配
if (validAttr.valuePattern != null) {
if (!validAttr.match(attrValue)) {
annotationHolder.createErrorAnnotation(attr.getValueElement(), "Attribute '" + attr.getName()
+ "' only allowed value that match '" + validAttr.valuePattern + "'");
}
} else {
//枚举匹配
if (validAttr.valueEnum != null) {
if (!validAttr.valueEnum.contains(attr.getValue()) && attr.getValueElement() != null) {
annotationHolder.createErrorAnnotation(attr.getValueElement(), "Attribute '" + attr.getName()
+ "' only allowed " + validAttr.valueEnum.toString() + " or mustache template as value");
}
}
}
}
} else {
if (CodeUtil.maybeInLineExpression(attrValue)) {
//TODO: checking expression
} else {
annotationHolder.createInfoAnnotation(attr, "Not weex defined attribute '" + attrName + "'");
}
}
}
if (xmlTag.getSubTags().length == 0 && !inRepeat(xmlTag)) {
String value = xmlTag.getValue().getText();
if (HywayFileUtil.containsMustacheValue(value)) {
Map<String, TextRange> vars = HywayFileUtil.getVars(value);
for (Map.Entry<String, TextRange> entry : vars.entrySet()) {
String bindVar = entry.getKey();
LintResult ret = verifyDataType("var", bindVar);
if (!ret.passed()) {
TextRange base = xmlTag.getValue().getTextRange();
TextRange range = new TextRange(base.getStartOffset() + entry.getValue().getStartOffset() + 2,
base.getStartOffset() + entry.getValue().getEndOffset() - 2);
Annotation annotator = annotationHolder.
createErrorAnnotation(range, ret.getDesc());
if (ret.getCode() == LintResultType.UNRESOLVED_VAR) {
annotator.registerFix(new QuickFixAction(bindVar, "var"));
}
}
}
}
}
//check sub tags
XmlTag[] subTags = xmlTag.getSubTags();
for (XmlTag t : subTags) {
List<String> validChild = tag.child;
if (validChild != null && !validChild.contains(t.getName().toLowerCase())) {
annotationHolder.createErrorAnnotation(t, "Element '" + xmlTag.getName()
+ "' only allowed " + tag.child.toString() + " as child element");
}
checkAttributeValue(t, annotationHolder);
}
} else {
//unsupported tag
}
}
private boolean inRepeat(XmlTag tag) {
if (tag == null) {
return false;
}
if ("template".equals(tag.getName())) {
return false;
}
if (tag.getAttribute("repeat") != null) {
return true;
} else {
return inRepeat(tag.getParentTag());
}
}
private void checkStructure(PsiElement document, @NotNull AnnotationHolder annotationHolder) {
PsiElement[] children = document.getChildren();
List<String> acceptedTag = Arrays.asList("template","element","script", "style");
for (PsiElement element : children) {
if (element instanceof HtmlTag) {
if (!acceptedTag.contains(((HtmlTag) element).getName().toLowerCase())) {
annotationHolder.createErrorAnnotation(element, "Invalid tag '"
+ ((HtmlTag) element).getName() + "', only the [template, element, script, style] tags are allowed here.");
}
checkAttributes((XmlTag) element, annotationHolder);
} else {
if (!(element instanceof PsiWhiteSpace)
&& !(element instanceof XmlProlog)
&& !(element instanceof XmlText)
&& !(element instanceof XmlComment)) {
String s = element.getText();
if (s.length() > 20) {
s = s.substring(0, 20);
}
annotationHolder.createErrorAnnotation(element, "Invalid content '" + s +
"', only the [template, script, style] tags are allowed here.");
}
}
}
}
}
package com.hyway.html.attributes;
import com.intellij.lang.javascript.psi.JSImplicitElementProvider;
import com.intellij.psi.PsiElement;
import com.intellij.psi.stubs.StubIndexKey;
import com.intellij.util.ArrayUtil;
import com.intellij.xml.impl.BasicXmlAttributeDescriptor;
import com.intellij.xml.impl.XmlAttributeDescriptorEx;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* @desc desc
* @author tangrufei
* @date 2023/2/21 15:01
* @return null
*/
/**
这段代码是实现一个自定义的 XML 属性描述符类,用于在编辑器中显示和编辑具有特定属性的 XML 元素。该类实现了
XmlAttributeDescriptorEx 接口,并继承了 BasicXmlAttributeDescriptor 类,实现了接口中的方法
,以提供对 XML 元素属性的访问和编辑支持。在该类中,定义了 XML 元素的属性名称和属性值(如果该属性具有固定的值域)
,以及一个 StubIndexKey 对象,用于检索指定名称的元素。通过实现 XmlAttributeDescriptorEx 接口,
该类提供了获取属性的默认值、属性的可能值、属性是否是必需的等方法。
具体而言,该类的作用是为自定义的 XML 语言插件提供对特定属性的访问和编辑支持,方便用户在编辑器中更好地编写和修改 XML 文件。
*/
public class HywayAttrDescriptor extends BasicXmlAttributeDescriptor implements XmlAttributeDescriptorEx {
private final String attributeName;
private final List<String> enumValue;
private final StubIndexKey<String, JSImplicitElementProvider> index;
public HywayAttrDescriptor(String attributeName, List<String> enumValue, final StubIndexKey<String, JSImplicitElementProvider> index) {
this.enumValue = enumValue;
this.attributeName = attributeName;
this.index = index;
}
@Nullable
@Override
public String handleTargetRename(@NotNull @NonNls String newTargetName) {
return newTargetName;
}
@Override
public boolean isRequired() {
return false;
}
@Override
public boolean hasIdType() {
return false;
}
@Override
public boolean hasIdRefType() {
return false;
}
@Override
public boolean isEnumerated() {
return index != null;
}
@Override
public PsiElement getDeclaration() {
return null;
}
@Override
public String getName() {
return attributeName;
}
@Override
public void init(PsiElement element) {
}
@Override
public Object[] getDependences() {
return ArrayUtil.EMPTY_OBJECT_ARRAY;
}
@Override
public boolean isFixed() {
return false;
}
@Override
public String getDefaultValue() {
return null;
}
@Override
public String[] getEnumeratedValues() {
if (enumValue == null) {
return ArrayUtil.EMPTY_STRING_ARRAY;
}
return enumValue.toArray(new String[enumValue.size()]);
}
}
package com.hyway.html.attributes;
import com.hyway.html.lint.DirectiveLint;
import com.hyway.html.lint.HywayTag;
import com.hyway.html.utils.HywayFileUtil;
import com.intellij.psi.xml.XmlTag;
import com.intellij.xml.XmlAttributeDescriptor;
import com.intellij.xml.XmlAttributeDescriptorsProvider;
import org.jetbrains.annotations.Nullable;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @desc desc
* @author tangrufei
* @date 2023/2/20 15:59
* @param null
* @return null
*/
/**
* 属性描述提供程序
*/
/**
* 这段代码是为自定义的 Hyway 语言的标签提供 XML 属性描述符的提供者,用于支持代码自动完成、语法高亮和代码导航等功能。
*
* HywayAttrDescriptorProvider 实现了 XmlAttributeDescriptorsProvider 接口,提供了两个方法,
*
* getAttributeDescriptors 和 getAttributeDescriptor。getAttributeDescriptors 方法用于返回指定标签的所有
*
* XML 属性描述符。该方法首先通过 HywayFileUtil.isOnHywayFile 方法判断当前文件是否为 Hyway 文件,如果不是,
*
* 则返回空数组。如果当前标签为 Hyway 标签,该方法将遍历 Hyway 标签定义的所有扩展属性,并将每个属性名称及其描述符添加到
*
* result 中。其中,HywayAttrDescriptor 是自定义的 XML 属性描述符,用于描述 Hyway 标签的扩展属性。
*
* getAttributeDescriptor 方法用于返回指定标签的指定属性描述符。该方法的实现类似于 getAttributeDescriptors,
*
* 首先判断当前文件是否为 Hyway 文件,如果不是,则返回 null。如果当前标签为 Hyway 标签,该方法将检查 Hyway 标签
*
* 定义的扩展属性是否包含指定的属性名称,如果是,则返回相应的 HywayAttrDescriptor 对象,否则返回 null。
*/
public class HywayAttrDescriptorProvider implements XmlAttributeDescriptorsProvider {
@Override
public XmlAttributeDescriptor[] getAttributeDescriptors(XmlTag xmlTag) {
if (!HywayFileUtil.isOnHywayFile(xmlTag)) {
return new XmlAttributeDescriptor[0];
}
final Map<String, XmlAttributeDescriptor> result = new LinkedHashMap<String, XmlAttributeDescriptor>();
HywayTag tag = DirectiveLint.getHywayTag(xmlTag.getName());
if (tag == null) {
return new XmlAttributeDescriptor[0];
}
for (String attributeName : tag.getExtAttrs()) {
result.put(attributeName, new HywayAttrDescriptor(attributeName,
tag.getAttribute(attributeName).valueEnum,
null));
}
return result.values().toArray(new XmlAttributeDescriptor[result.size()]);
}
@Nullable
@Override
public XmlAttributeDescriptor getAttributeDescriptor(String s, XmlTag xmlTag) {
if (!HywayFileUtil.isOnHywayFile(xmlTag)) {
return null;
}
HywayTag tag = DirectiveLint.getHywayTag(xmlTag.getName());
if (tag == null) {
return null;
}
if (tag.getExtAttrs().contains(s)) {
return new HywayAttrDescriptor(s, tag.getAttribute(s).valueEnum, null);
}
return null;
}
}
package com.hyway.html.complection;
import com.hyway.base.util.CodeUtil;
import com.hyway.html.HywayIcons;
import com.hyway.html.utils.HywayFileUtil;
import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.lang.javascript.psi.JSProperty;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.psi.PsiElement;
import com.intellij.psi.xml.XmlAttributeValue;
import com.intellij.util.ProcessingContext;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
/**
* @desc desc
* @author tangrufei
* @date 2023/2/20 16:48
* @return null
*/
/**
这段代码是一个 Java 类的构造函数,该类实现了一个 IntelliJ IDEA 的代码补全功能扩展,
名为 "HywayCompletionContributor"。该构造函数通过调用 extend() 方法,注册了一个 CompletionProvider 对象
,用于处理基础代码补全(CompletionType.BASIC)。注册时,使用了 PlatformPatterns.psiElement(PsiElement.class)
,表示对所有类型的 PsiElement 元素进行代码补全。在代码补全时,首先通过 HywayFileUtil.isOnHywayFile()
方法判断当前的文件是否是 Hyway 文件,如果不是,则直接返回。如果是 Hyway 文件,则检查当前光标所在位置的上下文
是否是 XML 属性值(XmlAttributeValue)。如果是 XML 属性值,则根据该属性值的类型进行代码补全,如果类型为 "function",
则自动填充所有函数名称;否则自动填充所有变量名称,并根据变量类型为代码补全项添加类型信息。
该代码补全功能通过 HywayFileUtil 和 CodeUtil 类提供的工具方法实现。其中,HywayFileUtil 提供了与 Hyway 文件相关
的一些辅助方法,如获取 Hyway 文件中所有变量和函数的名称,以及获取变量的声明语句。而 CodeUtil
则提供了一些常见的代码分析方法,如猜测一个字符串的类型。
*/
public class HywayCompletionContributor extends CompletionContributor {
public HywayCompletionContributor() {
extend(CompletionType.BASIC,
PlatformPatterns.psiElement(PsiElement.class),
new CompletionProvider<CompletionParameters>() {
@Override
protected void addCompletions(@NotNull CompletionParameters completionParameters, ProcessingContext processingContext, @NotNull CompletionResultSet resultSet) {
if (!HywayFileUtil.isOnHywayFile(completionParameters.getPosition())) {
return;
}
if (completionParameters.getPosition().getContext() instanceof XmlAttributeValue) {
final XmlAttributeValue value = (XmlAttributeValue) completionParameters.getPosition().getContext();
if (HywayFileUtil.getValueType(value).equals("function")) {
for (String s : HywayFileUtil.getAllFunctionNames(value)) {
resultSet.addElement(LookupElementBuilder.create("{{" + s + "}}")
.withLookupString(s)
.withIcon(HywayIcons.TYPE)
.withInsertHandler(new InsertHandler<LookupElement>() {
@Override
public void handleInsert(InsertionContext insertionContext, LookupElement lookupElement) {
performInsert(value, insertionContext, lookupElement);
}
})
.withBoldness(true)
.withTypeText("Function"));
}
} else {
Map<String, String> vars = HywayFileUtil.getAllVarNames(value);
for (String s : vars.keySet()) {
String typeText = vars.get(s);
String vType = HywayFileUtil.getValueType(value);
if (!HywayFileUtil.hasSameType(vType, vars.get(s))) {
JSProperty property = HywayFileUtil.getVarDeclaration(completionParameters.getPosition(), s);
if (property == null || property.getValue() == null) {
continue;
} else {
String v = property.getValue().getText();
String guessedType = CodeUtil.guessStringType(v);
if (!HywayFileUtil.hasSameType(vType, guessedType)) {
continue;
} else {
typeText = guessedType + " string";
}
}
}
resultSet.addElement(LookupElementBuilder.create("{{" + s + "}}")
.withLookupString(s)
.withIcon(HywayIcons.TYPE)
.withInsertHandler(new InsertHandler<LookupElement>() {
@Override
public void handleInsert(InsertionContext insertionContext, LookupElement lookupElement) {
performInsert(value, insertionContext, lookupElement);
}
})
.withBoldness(true)
.withTypeText(typeText));
}
}
}
}
});
}
private void performInsert(XmlAttributeValue value, InsertionContext insertionContext, LookupElement lookupElement) {
if (value.getText().startsWith("\"")) {
insertionContext.getDocument().replaceString(
value.getTextOffset(),
value.getTextOffset() + getTailLength(value) + lookupElement.getLookupString().length(),
lookupElement.getLookupString());
} else {
insertionContext.getDocument().replaceString(
value.getTextOffset() - 1,
value.getTextOffset() + getTailLength(value) + lookupElement.getLookupString().length() - 1,
"\"" + lookupElement.getLookupString() + "\"");
}
}
/** 获取尾部长度 */
private int getTailLength(XmlAttributeValue value) {
String[] temp = value.getValue().split(CompletionInitializationContext.DUMMY_IDENTIFIER);
if (temp.length == 2) {
return temp[1].length();
}
return 0;
}
}
package com.hyway.html.intention;
import com.google.gson.Gson;
import com.hyway.html.HywayFileType;
import com.hyway.html.lint.DirectiveLint;
import com.hyway.html.lint.HywayTag;
import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.ui.popup.PopupStep;
import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
import com.intellij.psi.PsiElement;
import com.intellij.psi.xml.XmlToken;
import com.intellij.ui.EditorTextField;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
/**
*用于编辑 XML 文件时,当用户选中某个 XML 标签名后,可以通过快捷键或右键菜单打开一个浏览器页面或样例代码编辑器。
*
* 具体实现方式如下:
*
* isAvailable 方法会检查传入的 PsiElement 对象是否为 XML 标签名,如果是,并且这个标签名存在于某个列表中,
*
* 那么返回 true,表示这个操作可用。否则返回 false。getText 和 getFamilyName 方法分别返回右键菜单中这个操作
*
* 的名称和组名。invoke 方法会根据用户选择的选项,调用 openDocument 或 openSample 方法。前者会根据 XML标签名
*
* 找到相应的文档链接,并用系统默认浏览器打开该链接;后者会打开一个样例代码编辑器。具体来说,openSample 方法会创建一个
*
* EditorTextField 组件,然后将其放入一个弹出窗口中,并显示在合适的位置。
*/
public class DocumentIntention extends PsiElementBaseIntentionAction {
@Override
public void invoke(@NotNull final Project project, final Editor editor, @NotNull final PsiElement psiElement) throws IncorrectOperationException {
JBPopupFactory.getInstance().createListPopup(new BaseListPopupStep<String>("Chosen", "Document", "Sample") {
@Override
public PopupStep onChosen(String selectedValue, boolean finalChoice) {
if ("Document".equals(selectedValue)) {
openDocument(psiElement.getText());
} else if ("Sample".equals(selectedValue)) {
openSample(project, editor);
}
return super.onChosen(selectedValue, finalChoice);
}
}).showInBestPositionFor(editor);
}
@Override
public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement psiElement) {
if (psiElement instanceof XmlToken && false) {
String tokenType = ((XmlToken) psiElement).getTokenType().toString();
if ("XML_NAME".equals(tokenType)) {
String tagName = psiElement.getText();
return DirectiveLint.containsTag(tagName);
}
}
return false;
}
@NotNull
@Override
public String getText() {
return "Open document or sample";
}
@Nls
@NotNull
@Override
public String getFamilyName() {
return getText();
}
private void openDocument(String tagName) {
HywayTag tag = DirectiveLint.getHywayTag(tagName);
if (tag != null && tag.document != null) {
Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null;
if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) {
try {
desktop.browse(URI.create(tag.document));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
private void openSample(Project project, Editor editor) {
EditorTextField field = new EditorTextField(editor.getDocument(), project, HywayFileType.INSTANCE, true, false) {
@Override
protected EditorEx createEditor() {
EditorEx editor1 = super.createEditor();
//设置滚动条可见
editor1.setVerticalScrollbarVisible(true);
//设置水平滚动条可见
editor1.setHorizontalScrollbarVisible(true);
return editor1;
}
};
//获取组件字体并设置
field.setFont(editor.getContentComponent().getFont());
JBPopup jbPopup = JBPopupFactory.getInstance().createComponentPopupBuilder(field, null)
.createPopup();
jbPopup.setSize(new Dimension(500, 500));
//显示最佳位置
jbPopup.showInBestPositionFor(editor);
}
private ComponentBean load(String name) {
InputStream is = DirectiveLint.class.getResourceAsStream("/samples/index.json");
Gson gson = new Gson();
ComponentBean[] beans = gson.fromJson(new InputStreamReader(is), ComponentBean[].class);
for (ComponentBean bean : beans) {
if (name.equals(bean.component)) {
return bean;
}
}
return null;
}
}
package com.hyway.html.intention;
import com.hyway.html.utils.HywayFileUtil;
import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction;
import com.intellij.lang.javascript.psi.JSBlockStatement;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.ui.popup.ListPopup;
import com.intellij.openapi.ui.popup.PopupStep;
import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.css.CssDeclaration;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlAttributeValue;
import com.intellij.psi.xml.XmlText;
import com.intellij.psi.xml.XmlToken;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.PlatformIcons;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
import java.util.HashSet;
import java.util.Set;
/**
* @desc desc
* @author tangrufei
* @date 2023/2/21 15:09
* @return null
*/
/**
* 这段代码是一个Intention Action,它的作用是在编辑Hyway文件时,当光标位于文本或属性值中时
*
* ,提供插入Mustache变量的快捷方式。在插入之前,该Intention Action会扫描当前光标所在位置的上下文,
*
* 从而猜测是插入一个变量还是一个函数。在弹出列表中选择变量或函数后,它将在光标位置插入相应的胡须表达式。
*/
public class TagTextIntention extends PsiElementBaseIntentionAction {
@Nls
@NotNull
@Override
public String getText() {
return "Insert mustache variable";/** 插入胡须表达式变量 */
}
@Nls
@NotNull
@Override
public String getFamilyName() {
return "Hyway";
}
@Override
public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement element) {
if (!HywayFileUtil.isOnHywayFile(element)) {
return false;
}
int offset = editor.getCaretModel().getOffset();
Document document = editor.getDocument();
if (!element.isWritable() || element.getContext() == null || !element.getContext().isWritable()) {
return false;
}
if (element instanceof XmlToken && ((XmlToken) element).getTokenType().toString().equals("XML_END_TAG_START")) {
String next = document.getText(new TextRange(offset, offset + 1));
if (next != null && next.equals("<")) {
return true;
}
}
return available(element);
}
@Override
public void invoke(@NotNull final Project project, final Editor editor, @NotNull PsiElement element) throws IncorrectOperationException {
Set<String> vars = new HashSet<String>();
boolean isFunc = false;
if (guessFunction(element)) {
vars = HywayFileUtil.getAllFunctionNames(element);
isFunc = true;
} else {
vars = HywayFileUtil.getAllVarNames(element).keySet();
isFunc = false;
}
final boolean isFuncFinal = isFunc;
ListPopup listPopup = JBPopupFactory.getInstance()
.createListPopup(new BaseListPopupStep<String>(null, vars.toArray(new String[vars.size()])) {
@Override
public Icon getIconFor(String value) {
return isFuncFinal ? PlatformIcons.FUNCTION_ICON : PlatformIcons.VARIABLE_ICON;
}
@Override
public PopupStep onChosen(final String selectedValue, final boolean finalChoice) {
new WriteCommandAction(project) {
@Override
protected void run(@NotNull Result result) throws Throwable {
editor.getDocument().insertString(editor.getCaretModel().getOffset(), "{{" + selectedValue + "}}");
int start = editor.getSelectionModel().getSelectionStart();
int end = editor.getSelectionModel().getSelectionEnd();
editor.getDocument().replaceString(start, end, "");
}
}.execute();
return super.onChosen(selectedValue, finalChoice);
}
});
listPopup.setMinimumSize(new Dimension(240, -1));
listPopup.showInBestPositionFor(editor);
}
@Override
public boolean startInWriteAction() {
return true;
}
private boolean available(PsiElement element) {
PsiElement context = element.getContext();
return context instanceof JSBlockStatement
|| context instanceof CssDeclaration
|| context instanceof XmlAttributeValue
|| context instanceof XmlText;
}
/** 判断是否为XML文件 */
private boolean guessFunction(PsiElement element) {
if (element.getContext() instanceof XmlAttributeValue) {
try {
XmlAttributeValue value = (XmlAttributeValue) element.getContext();
return ((XmlAttribute) value.getContext()).getName().startsWith("on");
} catch (Exception e) {
return false;
}
}
return element.getContext() instanceof JSBlockStatement;
}
}
package com.hyway.html.lint;
import com.google.gson.Gson;
import com.hyway.html.custom.Settings;
import com.hyway.html.utils.ExtraModulesUtil;
import com.hyway.html.utils.Logger;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.*;
/** create by tangrufei by 2023/2/21 */
/**
这是一个名为“DirectiveLint”的Java类。它提供了一组方法,用于管理、合并和检索基于HTML的应用程序中使用的各种类型的标记,
*/
public class DirectiveLint {
private static HywayTag[] tags = null;
private static Set<String> tagNames = null;
private static String[] htmlTags = {
"div",
"text",
"video",
"a",
"qunimade"
};
/** 打印内存中当前标记和标记名称的信息*/
public static void dump() {
if (tags != null) {
List list = Arrays.asList(tags);
Logger.warn("Tags: (" + list.size() + ") " + list.toString());
} else {
Logger.warn("Tags: null");
}
if (tagNames != null) {
Logger.warn("Tag names: (" + tagNames.size() + ") " + tagNames.toString());
} else {
Logger.warn("Tag names: null");
}
List list1 = ExtraModulesUtil.getTagsFromNodeModules();
Logger.warn("Tag from node_modules: (" + list1.size() + ") " + list1.toString());
}
/**重新设置(加载内置规则,将其与自定义规则合并,并清除内存中的任何现有标记。 */
public static void reset() {
loadBuiltInRules();
if (tagNames != null) {
tagNames.clear();
}
mergeToBuiltIn(Settings.getRules());
Logger.info("Rebuild Hyway component caches, " + tags.length + " tags found.");
}
/** 载内置规则并将其与自定义规则合并。合并标签(list<HywayTag>from):将标签列表与现有内置标签合并。
loadBuiltinRules(:从JSON文件加载内置规则 */
public static void prepare() {
if (tags == null) {
loadBuiltInRules();
}
mergeToBuiltIn(Settings.getRules());
}
/** 标签合并 */
private static void mergeToTags(List<HywayTag> from) {
List<HywayTag> builtIn = new ArrayList<HywayTag>(Arrays.asList(tags));
if (from != null) {
for (HywayTag tag : from) {
if (containsTag(tag.tag)) {
HywayTag builtInTag = getBuiltInHywayTag(tag.tag);
if (builtInTag != null) {
mergeCustom(builtInTag, tag);
}
} else {
builtIn.add(tag);
}
}
}
tags = builtIn.toArray(new HywayTag[builtIn.size()]);
if (tagNames != null) {
tagNames.clear();
}
}
/** (:从JSON文件加载内置规则 */
private static void loadBuiltInRules() {
InputStream is = DirectiveLint.class.getResourceAsStream("/directives/directives.json");
Gson gson = new Gson();
tags = gson.fromJson(new InputStreamReader(is), HywayTag[].class);
if (tags == null) {
tags = new HywayTag[0];
}
loadNodeModules();
}
/** 加载节点模块(:将来自外部模块的标签与现有的内置标签合并 */
private static void loadNodeModules() {
List<HywayTag> HywayTags = ExtraModulesUtil.getTagsFromNodeModules();
mergeToTags(HywayTags);
Logger.debug(HywayTags.toString());
}
/** 将一系列自定义规则与现有的内置规则合并 */
private static void mergeToBuiltIn(HywayTag[] customRules) {
if (customRules != null) {
mergeToTags(Arrays.asList(customRules));
}
}
/** HywayTag内置,HywayTag自定义) :将单个自定义标记与内置标记合并。 */
private static void mergeCustom(HywayTag builtIn, HywayTag custom) {
if (custom.parent != null && custom.parent.size() > 0) {
if (builtIn.parent != null) {
builtIn.parent.addAll(custom.parent);
} else {
builtIn.parent = custom.parent;
}
}
if (custom.child != null && custom.child.size() > 0) {
if (builtIn.child != null) {
builtIn.child.addAll(custom.child);
} else {
builtIn.child = custom.child;
}
}
if (custom.attrs != null && custom.attrs.size() > 0) {
if (builtIn.attrs != null) {
for (Attribute attr : builtIn.attrs) {
if (custom.getAttribute(attr.name) != null) {
attr.merge(custom.getAttribute(attr.name));
}
}
}
for (Attribute attr1 : custom.attrs) {
if (builtIn.getAttribute(attr1.name) == null) {
builtIn.attrs.add(attr1);
}
}
}
}
/** GetHywayTagNames ():重新生成响应和标记名 */
public static Set<String> getHywayTagNames() {
if (tagNames == null) {
tagNames = new HashSet<String>();
}
if (tagNames.size() == 0) {
if (tags == null) {
prepare();
}
for (HywayTag tag : tags) {
if (!"common".equals(tag.tag)) {
tagNames.add(tag.tag);
}
}
}
Logger.debug(tagNames.toString());
Logger.debug(Arrays.asList(tags).toString());
return tagNames;
}
/** 标签(标签):返回基本HTML标记的列表 */
public static List<String> getHtmlTags() {
return Arrays.asList(htmlTags);
}
public static boolean containsTag(String tagName) {
if (tagName == null || "common".equals(tagName)) {
return false;
}
if (tags == null) {
prepare();
}
for (HywayTag tag : tags) {
if (tagName.equals(tag.tag)) {
return true;
}
}
return false;
}
private static HywayTag getBuiltInHywayTag(String tagName) {
if (tags == null) {
loadBuiltInRules();
}
for (HywayTag tag : tags) {
if (tag.tag.equals(tagName)) {
return tag;
}
}
return null;
}
public static HywayTag getCommonTag() {
if (tags == null) {
prepare();
}
for (HywayTag tag : tags) {
if (tag.tag.equals("common")) {
return tag;
}
}
return null;
}
public static HywayTag getHywayTag(String tagName) {
if (tags == null) {
prepare();
}
HywayTag common = null;
HywayTag target = null;
for (HywayTag tag : tags) {
if (tag.tag.equals(tagName)) {
target = tag;
}
if (tag.tag.equals("common")) {
common = tag;
}
}
if (common != null) {
if (target != null) {
return merge(target, common);
} else {
return common;
}
} else {
if (target != null) {
return target;
}
}
return null;
}
private static HywayTag merge(HywayTag target, HywayTag common) {
target.attrs.addAll(common.attrs);
return target;
}
}
package com.hyway.html.quickfix;
import com.hyway.html.utils.HywayFileUtil;
import com.hyway.html.utils.MessageUtil;
import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
/**
* @desc desc
* @author tangrufei
* @date 2023/2/20 15:57
* @param null
* @return null
*/
/**
* 基本操作
*/
public class QuickFixAction extends BaseIntentionAction {
private String name = null;
private String type = null;
private static Map<String, String> defValues = new HashMap<String, String>();
static {
defValues.put("var", "null");
defValues.put("boolean", "false");
defValues.put("number", "0");
defValues.put("object", "{}");
defValues.put("array", "[]");
defValues.put("string", "''");
}
public QuickFixAction(String name, String type) {
this.name = name;
this.type = type;
}
@NotNull
@Override
public String getText() {
String t = "function".equals(type) ? "function " : "variable ";
return "Create " + t + "'" + name + "'";
}
@Nls
@NotNull
@Override
public String getFamilyName() {
return "Hyway properties";
}
@Override
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile psiFile) {
return true;
}
@Override
public void invoke(@NotNull Project project, Editor editor, PsiFile psiFile) throws IncorrectOperationException {
if (!"function".equals(type)) {
int offset = HywayFileUtil.getExportsEndOffset(psiFile, "data");
if (offset < 0) {
MessageUtil.showTopic(project, "QuickFix failed", "module.exports.data not found", NotificationType.WARNING);
return;
}
String template = name + ": " + getDefaultValue(type) + ",\n";
if (!hasComma(offset, editor)) {
template = "," + template;
}
editor.getDocument().insertString(offset, template);
PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument());
CodeStyleManager.getInstance(project).reformat(psiFile);
} else {
int offset = HywayFileUtil.getExportsEndOffset(psiFile, "methods");
if (offset < 0) {
MessageUtil.showTopic(project, "QuickFix failed", "module.exports.methods not found", NotificationType.WARNING);
return;
}
String template = name + ": " + "function () {\n\n}\n";
if (!hasComma(offset, editor)) {
template = "," + template;
}
editor.getDocument().insertString(offset, template);
PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument());
CodeStyleManager.getInstance(project).reformat(psiFile);
}
}
private boolean hasComma(int start, Editor editor) {
Document document = editor.getDocument();
TextRange range = new TextRange(start - 1, start);
while (true) {
String s = document.getText(range);
if (range.getStartOffset() <= 0) {
return false;
}
if (s.equals(",") || s.equals("{")) {
return true;
} else if (Pattern.compile("\\s+").matcher(s).matches()) {
range = new TextRange(range.getStartOffset() - 1, range.getStartOffset());
} else {
return false;
}
}
}
private String getDefaultValue(String name) {
return defValues.get(name);
}
}
package com.hyway.html.refrences;
import com.hyway.html.utils.HywayFileUtil;
import com.intellij.lang.html.HTMLLanguage;
import com.intellij.openapi.util.TextRange;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.patterns.XmlPatterns;
import com.intellij.psi.*;
import com.intellij.psi.xml.XmlAttributeValue;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.ProcessingContext;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/** create by tangrufei by 2023/2/21 */
/**
*主要作用是注册用于处理 HTML 文件中文本变量
*/
/**
具体来说,它通过调用 registerReferenceProvider 方法注册了两个参考提供者。第一个参考提供者使用 HtmlLanguage 实例
和 XmlAttributeValue 类作为参考元素,将其绑定到 HywayReferenceProvider 实例,以处理 HTML 文件中属性值
(即以双引号或单引号括起来的字符串)中的文本变量。第二个参考提供者使用 HtmlLanguage 实例和 XmlTag 类作为参考元素,
将其绑定到一个匿名的 PsiReferenceProvider 实例,以处理 HTML 文件中标签中的文本变量,但不包括 script 和 style 标签。
对于第二个参考提供者,当它找到一个标签时,它首先检查标签是否包含子标签,如果没有子标签,则它会获取标签内的文本,并使用
HywayFileUtil.containsMustacheValue 方法检查文本是否包含文本变量。如果包含,则它会使用 HywayFileUtil.getVars方法
获取文本变量,并使用 HywayFileUtil.getAllVarNames 方法检查变量名是否在当前标签及其祖先标签中定义过,如果是,
则会为该变量创建一个 DataReference 实例,并将其添加到返回的参考数组中。
*/
public class HywayReferenceContributor extends PsiReferenceContributor {
@Override
public void registerReferenceProviders(@NotNull PsiReferenceRegistrar psiReferenceRegistrar) {
psiReferenceRegistrar.registerReferenceProvider(
PlatformPatterns.psiElement(XmlAttributeValue.class).withLanguage(HTMLLanguage.INSTANCE), new HywayReferenceProvider());
psiReferenceRegistrar.registerReferenceProvider(
XmlPatterns.xmlTag().withLanguage(HTMLLanguage.INSTANCE)
.andNot(XmlPatterns.xmlTag().withLocalName("script"))
.andNot(XmlPatterns.xmlTag().withLocalName("style")),
new PsiReferenceProvider() {
@NotNull
@Override
public PsiReference[] getReferencesByElement(@NotNull PsiElement psiElement, @NotNull ProcessingContext processingContext) {
if (psiElement instanceof XmlTag) {
if (((XmlTag) psiElement).getSubTags().length == 0) {
String text = ((XmlTag) psiElement).getValue().getText();
if (HywayFileUtil.containsMustacheValue(text)) {
List<PsiReference> references = new ArrayList<PsiReference>();
Map<String, TextRange> vars = HywayFileUtil.getVars(text);
for (Map.Entry<String, TextRange> entry : vars.entrySet()) {
if (HywayFileUtil.getAllVarNames(psiElement).keySet().contains(entry.getKey())) {
references.add(new DataReference((XmlTag) psiElement, entry.getValue(), entry.getKey()));
}
}
return references.toArray(new PsiReference[references.size()]);
}
}
}
return new PsiReference[0];
}
}
);
}
}
package com.hyway.html.refrences;
import com.hyway.html.lint.DirectiveLint;
import com.hyway.html.lint.HywayTag;
import com.hyway.html.utils.HywayFileUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.PsiReferenceProvider;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.ProcessingContext;
import org.jetbrains.annotations.NotNull;
import java.util.regex.Pattern;
/** create by tangrufei by 2023/2/21 */
/** 胡须表达式处理器 */
/**
* getReferencesByElement 方法是必须实现的方法,它接受两个参数 PsiElement 和 ProcessingContext,并返回一个
*
* PsiReference 数组。当 Hyway 文件中出现一个插值表达式 {{...}} 时,这个方法将会被调用。首先,
*
* 使用 HywayFileUtil.isOnHywayFile 方法检查当前的 PsiElement 是否在一个 Hyway 文件中。如果不是,
*
* 那么将会直接返回一个空的 PsiReference 数组。replaceAll 方法将 text 中所有的双引号 " 都替换为空格,
*
* 这样可以获取到插值表达式的内容。接着使用正则表达式 \\{\\{.*\\}\\} 来匹配这个内容是否符合插值表达式的形式,
*
* 也就是是否以两个花括号开始和结束。如果匹配上了,并且内容长度大于 4,那么就执行接下来的逻辑。
*
* 使用 psiElement 的父节点来确定这个插值表达式所在的标签以及它的属性类型,根据属性类型的不同,生成一个 MustacheVarReference 引用对象,并将其作为数组返回。如果匹配不上,那么将会返回一个空的 PsiReference 数组。
*/
public class HywayReferenceProvider extends PsiReferenceProvider {
@NotNull
@Override
public PsiReference[] getReferencesByElement(@NotNull PsiElement psiElement, @NotNull ProcessingContext processingContext) {
if (!HywayFileUtil.isOnHywayFile(psiElement)) {
return new PsiReference[0];
}
String text = psiElement.getText().replaceAll("\"+", "");
if (Pattern.compile("\\{\\{.*\\}\\}").matcher(text).matches() && text.length() > 4) {
String valueType = "var";
if (psiElement.getParent() != null && psiElement.getParent().getParent() != null) {
PsiElement tag = psiElement.getParent().getParent();
String attr = null;
if (psiElement.getContext() != null) {
attr = ((XmlAttribute) psiElement.getContext()).getName();
}
if (attr != null && tag instanceof XmlTag) {
String tagName = ((XmlTag) tag).getName();
HywayTag weexTag = DirectiveLint.getHywayTag(tagName);
if (weexTag != null && weexTag.getAttribute(attr) != null) {
valueType = weexTag.getAttribute(attr).valueType;
}
}
}
return new PsiReference[]{new MustacheVarReference(psiElement, valueType.toLowerCase())};
}
return new PsiReference[0];
}
}
package com.hyway.html.tags;
import com.hyway.html.lint.Attribute;
import com.hyway.html.lint.DirectiveLint;
import com.hyway.html.lint.HywayTag;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* 上下文标记解析程序
*/
public class ContextTagResolver {
public static List<HywayTag> resolve(PsiElement any) {
List<HywayTag> result = new ArrayList<HywayTag>();
PsiFile file = any.getContainingFile();
PsiDirectory directory = file.getOriginalFile().getContainingDirectory();
if (directory == null) {
directory = file.getOriginalFile().getParent();
}
if (directory == null) {
return result;
}
PsiFile[] files = directory.getFiles();
for (PsiFile p : files) {
if (!p.getName().equals(any.getContainingFile().getName())) {
HywayTag common = DirectiveLint.getCommonTag();
if (common != null) {
HywayTag fake = new HywayTag();
fake.tag = p.getName().replace(".hyway", "");
fake.attrs = new CopyOnWriteArrayList<Attribute>(common.attrs);
fake.declare = p;
result.add(fake);
}
}
}
return result;
}
}
package com.hyway.html.tags;
import com.intellij.openapi.util.Condition;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.impl.source.html.dtd.HtmlNSDescriptorImpl;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.xml.XmlAttributeDescriptor;
import com.intellij.xml.XmlElementDescriptor;
import com.intellij.xml.XmlElementsGroup;
import com.intellij.xml.XmlNSDescriptor;
import com.intellij.xml.impl.schema.AnyXmlAttributeDescriptor;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.Nullable;
/** create by tangrufei by 2023/2/21 */
/** HywayTagDescriptor 主要是为 Hyway 标签提供 XML 元素描述信息,其中包含了标签的名称、默认名称、属性列表等。
*
* getQualifiedName() 方法返回标签的全限定名。
*
* getAttributesDescriptors() 方法返回标签的所有属性列表,包括 HTML 的公共属性以及自定义属性
*
* getAttributeDescriptor() 方法返回一个指定属性的描述器,用于描述属性的名称、类型和值等信息。
*
* 其他方法用于返回标签的各种描述信息,例如默认名称、声明、依赖关系等。
*
* 该类的主要作用是为了让 IDE 在编辑 Hyway 模板时,能够提供正确的代码补全、语法高亮等功能。
*
*/
public class HywayTagDescriptor implements XmlElementDescriptor{
protected final String name;
private final PsiFile file;
public HywayTagDescriptor(String name, PsiFile file) {
this.name = name;
this.file = file;
}
/** 获取全限定名 */
@Override
public String getQualifiedName() {
return name;
}
@Override
public String getDefaultName() {
return name;
}
@Override
public XmlElementDescriptor[] getElementsDescriptors(XmlTag context) {
return EMPTY_ARRAY;
}
/** getAttributeDescriptor() 方法返回一个指定属性的描述器,用于描述属性的名称、类型和值等信息 */
@Override
public XmlElementDescriptor getElementDescriptor(XmlTag childTag, XmlTag contextTag) {
return null;
}
/** getAttributesDescriptors() 方法返回标签的所有属性列表,包括 HTML 的公共属性以及自定义属性 */
@Override
public XmlAttributeDescriptor[] getAttributesDescriptors(@Nullable XmlTag context) {
XmlAttributeDescriptor[] attributeDescriptors = HtmlNSDescriptorImpl.getCommonAttributeDescriptors(context);
XmlAttributeDescriptor[] customAttributes = new XmlAttributeDescriptor[1];
customAttributes[0] = new AnyXmlAttributeDescriptor("hyway");
return ArrayUtil.mergeArrays(attributeDescriptors, customAttributes);
}
@Nullable
@Override
public XmlAttributeDescriptor getAttributeDescriptor(XmlAttribute attribute) {
return getAttributeDescriptor(attribute.getName(), attribute.getParent());
}
@Nullable
@Override
public XmlAttributeDescriptor getAttributeDescriptor(@NonNls final String attributeName, @Nullable XmlTag context) {
final XmlAttributeDescriptor descriptor = ContainerUtil.find(getAttributesDescriptors(context), new Condition<XmlAttributeDescriptor>() {
@Override
public boolean value(XmlAttributeDescriptor descriptor) {
return attributeName.equals(descriptor.getName());
}
});
if (descriptor != null) return descriptor;
return null;
}
@Override
public XmlNSDescriptor getNSDescriptor() {
return null;
}
@Nullable
@Override
public XmlElementsGroup getTopGroup() {
return null;
}
@Override
public int getContentType() {
return CONTENT_TYPE_ANY;
}
@Nullable
@Override
public String getDefaultValue() {
return null;
}
@Override
public PsiFile getDeclaration() {
return file;
}
@Override
public String getName(PsiElement context) {
return getName();
}
@Override
public String getName() {
return name;
}
@Override
public void init(PsiElement element) {
}
@Override
public Object[] getDependences() {
return ArrayUtil.EMPTY_OBJECT_ARRAY;
}
}
package com.hyway.html.tags;
import com.hyway.html.HywayIcons;
import com.hyway.html.lint.DirectiveLint;
import com.hyway.html.lint.HywayTag;
import com.hyway.html.utils.ArchiveUtil;
import com.hyway.html.utils.HywayFileUtil;
import com.intellij.codeInsight.completion.XmlTagInsertHandler;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.impl.source.xml.XmlElementDescriptorProvider;
import com.intellij.psi.xml.XmlTag;
import com.intellij.xml.XmlElementDescriptor;
import com.intellij.xml.XmlTagNameProvider;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Set;
/**
* @desc desc
* @author tangrufei
* @date 2023/2/20 16:03
* @param null
* @return null
*/
/**
* 标签名称提供程序
*/
public class HywayTagNameProvider implements XmlTagNameProvider, XmlElementDescriptorProvider {
@Override
public void addTagNameVariants(List<LookupElement> list, @NotNull XmlTag xmlTag, String prefix) {
if (!HywayFileUtil.isOnHywayFile(xmlTag)) {
return;
}
//if (!(xmlTag instanceof HtmlTag)) {
// return;
//}
Set<String> tags = DirectiveLint.getHywayTagNames();
for (String s : tags) {
/** 查找元素生成器**/
LookupElement element = LookupElementBuilder
.create(s)
.withInsertHandler(XmlTagInsertHandler.INSTANCE)
.withBoldness(true)
.withIcon(HywayIcons.TYPE)
.withTypeText("hyway component");
list.add(element);
}
/*List<HywayTag> contextTags = ContextTagResolver.resolve(xmlTag);
for (HywayTag tag : contextTags) {
LookupElement element = LookupElementBuilder
.create(tag.tag)
.withInsertHandler(XmlTagInsertHandler.INSTANCE)
.withIcon(HywayIcons.TYPE)
.withTypeText("local component");
list.add(element);
}*/
}
@Nullable
@Override
public XmlElementDescriptor getDescriptor(XmlTag xmlTag) {
if (!HywayFileUtil.isOnHywayFile(xmlTag)) {
return null;
}
Set<String> tags = DirectiveLint.getHywayTagNames();
List<String> htmlTags = DirectiveLint.getHtmlTags();
if (tags.contains(xmlTag.getName()) && !htmlTags.contains(xmlTag.getName())) {
PsiFile declare = null;
HywayTag tag = DirectiveLint.getHywayTag(xmlTag.getName());
if (tag != null) {
declare = tag.declare;
}
if (declare == null) {
VirtualFile virtualFile = ArchiveUtil.getFileFromArchive("constants/hyway-built-in-components.xml");
if (virtualFile != null) {
declare = PsiManager.getInstance(xmlTag.getProject()).findFile(virtualFile);
}
}
if (declare == null) {
declare = xmlTag.getContainingFile();
}
return new HywayTagDescriptor(xmlTag.getName(), declare);
}
List<HywayTag> contextTags = ContextTagResolver.resolve(xmlTag);
for (HywayTag tag : contextTags) {
if (xmlTag.getName().equals(tag.tag)) {
return new HywayTagDescriptor(tag.tag, tag.declare);
}
}
return new HywayTagDescriptor(xmlTag.getName(), xmlTag.getContainingFile());
}
}
package com.hyway.html.utils;
import com.intellij.openapi.vfs.JarFileSystem;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import java.net.URL;
import java.net.URLDecoder;
/** create by tangrufei by 2023/2/28 */
public class ArchiveUtil {
public static VirtualFile getFileFromArchive(String name) {
String[] subPath = name.split("/");
try {
URL def = ArchiveUtil.class.getClassLoader().getResource("/");
if (def != null) {
String path = URLDecoder.decode(def.getPath(), "utf-8").replace("file:", "");
String[] temp = path.split("!");
if (temp.length > 1 && path.toLowerCase().contains(".jar")) {
path = temp[0];
}
VirtualFile root = JarFileSystem.getInstance().findLocalVirtualFileByPath(path);
if (root == null) {
root = LocalFileSystem.getInstance().refreshAndFindFileByPath(path);
}
VirtualFile target = root;
for (String s : subPath) {
if (target != null) {
target = target.findChild(s);
}
}
return target;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
package com.hyway.html.utils;
import com.hyway.html.custom.Settings;
import com.hyway.html.lint.Attribute;
import com.hyway.html.lint.HywayTag;
import com.intellij.json.psi.JsonFile;
import com.intellij.json.psi.JsonObject;
import com.intellij.json.psi.JsonProperty;
import com.intellij.json.psi.JsonStringLiteral;
import com.intellij.lang.javascript.psi.*;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.impl.file.PsiDirectoryFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
/** create by tangrufei by 2023/2/27 */
public class ExtraModulesUtil {
public static List<HywayTag> getTagsFromNodeModules() {
List<HywayTag> list = new ArrayList<HywayTag>();
Project project = ProjectUtil.guessCurrentProject(null);
Logger.debug(project.toString());
VirtualFile vf = project.getBaseDir();
PsiDirectory[] localModules = new PsiDirectory[0];
if (vf != null && vf.isDirectory()) {
Logger.debug("Project root dir: " + vf.toString());
PsiDirectory dir = PsiDirectoryFactory.getInstance(project).createDirectory(vf);
localModules = getNodeModules(dir);
for (PsiDirectory directory : localModules) {
List<PsiFile> comps = getComponents(directory, getMain(directory));
String homePage = getHomePage(directory);
for (PsiFile comp : comps) {
HywayTag tag = parseToTag(comp);
tag.document = homePage;
list.add(tag);
}
}
} else {
Logger.debug("Project base dir is null");
}
for (PsiDirectory dir : getGlobalModules(localModules)) {
List<PsiFile> comps = getComponents(dir, getMain(dir));
String homePage = getHomePage(dir);
Logger.debug(comps.toString());
for (PsiFile comp : comps) {
HywayTag tag = parseToTag(comp);
tag.document = homePage;
list.add(tag);
}
}
return list;
}
private static String getHomePage(PsiDirectory directory) {
PsiFile pkg = directory.findFile("package.json");
if (pkg != null && pkg instanceof JsonFile) {
if (((JsonFile) pkg).getTopLevelValue() instanceof JsonObject) {
JsonObject object = (JsonObject) ((JsonFile) pkg).getTopLevelValue();
if (object != null) {
JsonProperty homePage = object.findProperty("homepage");
if (homePage != null && homePage.getValue() != null && homePage.getValue() instanceof JsonStringLiteral) {
JsonStringLiteral propValue = (JsonStringLiteral) homePage.getValue();
return propValue.getValue();
}
}
}
}
return null;
}
private static List<PsiDirectory> getGlobalModules(PsiDirectory[] localModules) {
if (localModules == null) {
localModules = new PsiDirectory[0];
}
List<PsiDirectory> result = new ArrayList<PsiDirectory>();
List<String> globals = Settings.getGlobalModules();
List<String> locals = new ArrayList<String>();
for (PsiDirectory dir : localModules) {
if (dir != null) {
locals.add(dir.getVirtualFile().getCanonicalPath());
}
}
for (String global : globals) {
if (!locals.contains(global)) {
VirtualFile vf = LocalFileSystem.getInstance().refreshAndFindFileByPath(global);
if (vf != null && vf.isDirectory()) {
PsiDirectory dir = PsiDirectoryFactory.getInstance(ProjectUtil.guessCurrentProject(null)).createDirectory(vf);
if (dir != null) {
result.add(dir);
}
}
} else {
Logger.info("Module " + global + " already exists locally, skip it.");
}
}
Logger.debug(result.toString());
return result;
}
private static HywayTag parseToTag(PsiFile comp) {
HywayTag weexTag = new HywayTag();
weexTag.tag = comp.getContainingFile().getName().replace(".hyway", "");
weexTag.attrs = new CopyOnWriteArrayList<Attribute>();
weexTag.declare = comp;
Map<String, String> vars = HywayFileUtil.getAllVarNames(comp);
for (Map.Entry<String, String> entry : vars.entrySet()) {
Attribute attribute = new Attribute();
attribute.name = convertAttrName(entry.getKey());
attribute.valueType = getType(entry.getValue());
weexTag.attrs.add(attribute);
}
return weexTag;
}
private static String convertAttrName(String name) {
char[] chars = name.toCharArray();
StringBuilder sb = new StringBuilder();
for (char c: chars) {
if (Character.isUpperCase(c)) {
if (sb.length() == 0) {
sb.append(Character.toLowerCase(c));
} else {
sb.append('-').append(Character.toLowerCase(c));
}
} else {
sb.append(c);
}
}
return sb.toString();
}
private static String getType(String realType) {
realType = realType.toLowerCase();
if ("number".equals(realType) || "boolean".equals(realType) || "array".equals(realType)) {
return realType.toLowerCase();
}
return "var";
}
private static PsiDirectory[] getNodeModules(PsiDirectory root) {
PsiDirectory node_modules = root.findSubdirectory("node_modules");
if (node_modules != null) {
return node_modules.getSubdirectories();
}
return new PsiDirectory[0];
}
private static PsiFile getMain(PsiDirectory moduleRoot) {
PsiFile pkg = moduleRoot.findFile("package.json");
if (pkg != null && pkg instanceof JsonFile) {
if (((JsonFile) pkg).getTopLevelValue() instanceof JsonObject) {
JsonObject object = (JsonObject) ((JsonFile) pkg).getTopLevelValue();
if (object != null) {
JsonProperty property = object.findProperty("main");
if (property != null && property.getValue() != null && property.getValue() instanceof JsonStringLiteral) {
JsonStringLiteral propValue = (JsonStringLiteral) property.getValue();
String value = propValue.getValue();
PsiFile psiFile = moduleRoot.findFile(value.replace("./", ""));
return psiFile;
}
}
}
}
return null;
}
private static List<PsiFile> getComponents(PsiDirectory root, PsiFile file) {
List<PsiFile> results = new ArrayList<PsiFile>();
if (file != null && file instanceof JSFile) {
for (PsiElement element : file.getChildren()) {
if (element instanceof JSExpressionStatement) {
JSExpression expression = ((JSExpressionStatement) element).getExpression();
if (expression instanceof JSCallExpression
&& ((JSCallExpression) expression).getArguments().length == 1
&& ((JSCallExpression) expression).getArguments()[0] instanceof JSLiteralExpression) {
JSLiteralExpression expression1 = (JSLiteralExpression) ((JSCallExpression) expression).getArguments()[0];
Object val = expression1.getValue();
if (val != null) {
String[] temp = val.toString().replace("./", "").split("/");
if (temp != null && temp.length > 0) {
String fileName = temp[temp.length - 1];
if (fileName.toLowerCase().endsWith(".hyway")) {
PsiDirectory start = root;
for (int i = 0; i < temp.length - 1; i++) {
PsiDirectory dir = start.findSubdirectory(temp[i]);
if (dir != null) {
start = dir;
}
}
PsiFile weexScript = start.findFile(temp[temp.length - 1]);
if (weexScript != null) {
results.add(weexScript);
}
}
}
}
}
}
}
}
return results;
}
public static boolean isNodeModule(PsiDirectory dir) {
PsiFile pkg = dir.findFile("package.json");
return pkg != null && pkg instanceof JsonFile;
}
public static String getModuleName(PsiDirectory dir) {
PsiFile pkg = dir.findFile("package.json");
String name = dir.getName();
if (pkg != null && pkg instanceof JsonFile) {
if (((JsonFile) pkg).getTopLevelValue() instanceof JsonObject) {
JsonObject object = (JsonObject) ((JsonFile) pkg).getTopLevelValue();
if (object != null) {
JsonProperty property = object.findProperty("name");
JsonProperty property1 = object.findProperty("version");
if (property != null && property.getValue() != null && property.getValue() instanceof JsonStringLiteral) {
JsonStringLiteral propValue = (JsonStringLiteral) property.getValue();
name = propValue.getValue();
if (property1 != null && property1.getValue() != null && property1.getValue() instanceof JsonStringLiteral) {
JsonStringLiteral propValue1 = (JsonStringLiteral) property1.getValue();
name = name + ":" + propValue1.getValue();
}
}
}
}
}
return name;
}
public static void main(String[] args) {
System.out.println(convertAttrName("Foo"));
System.out.println(convertAttrName("HowAreYou"));
System.out.println(convertAttrName("areYouOK"));
}
}
package com.hyway.html.utils;
import com.hyway.base.util.CodeUtil;
import com.hyway.html.lint.Attribute;
import com.hyway.html.lint.DirectiveLint;
import com.hyway.html.lint.HywayTag;
import com.intellij.lang.javascript.psi.*;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiInvalidElementAccessException;
import com.intellij.psi.xml.*;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** create by tangrufei by 2023/2/27 */
public class HywayFileUtil {
/**当前文件名*/
private static String currentFileName = "";
/**缓存的导出语句*/
private static JSObjectLiteralExpression cachedExportsStatement;
private static Map<String, String> vars = new ConcurrentHashMap<String, String>();
/** 所有方法函数 **/
private static Set<String> functions = new HashSet<String>();
/**判断当前编辑器是否处于.hyway结尾文件中*/
public static boolean isOnHywayFile(PsiElement element) {
if (element.getContainingFile() != null) {
return element.getContainingFile().getName().toLowerCase().endsWith(".hyway");
} else {
return false;
}
}
public static String getValueType(XmlAttributeValue value) {
if (value.getContext() != null && value.getContext() instanceof XmlAttribute) {
String attrName = ((XmlAttribute) value.getContext()).getName();
if (value.getContext().getContext() != null && value.getContext().getContext() instanceof XmlTag) {
String tagName = ((XmlTag) value.getContext().getContext()).getName();
HywayTag tag = DirectiveLint.getHywayTag(tagName);
if (tag != null) {
Attribute attribute = tag.getAttribute(attrName);
if (attribute != null) {
return attribute.valueType;
}
}
}
}
return "var";
}
public static String getJSPropertyType(JSProperty jsProperty) {
JSType t = jsProperty.getJSType();
String typeString = "var";
if (t == null) {
if (jsProperty.getValue() instanceof JSObjectLiteralExpression) {
typeString = "Object";
}
} else {
typeString = t.getResolvedTypeText();
}
return typeString;
}
public static boolean hasSameType(XmlAttributeValue value, JSProperty property) {
String valueType = getValueType(value);
String JsType = getJSPropertyType(property);
return hasSameType(valueType, JsType);
}
public static boolean hasSameType(String value, String property) {
if (value.toLowerCase().equals(property.toLowerCase())) {
return true;
}
if (value.equals("var")) {
return true;
}
return false;
}
private static void ensureFile(PsiElement element) {
String path = String.valueOf(System.currentTimeMillis());
if (element != null
&& element.getContainingFile() != null
&& element.getContainingFile().getVirtualFile() != null) {
path = element.getContainingFile().getVirtualFile().getPath();
}
if (!currentFileName.equals(path)) {
cachedExportsStatement = null;
vars.clear();
functions.clear();
currentFileName = path;
}
}
public static JSObjectLiteralExpression getExportsStatement(PsiElement anyElementOnHywayScript) {
ensureFile(anyElementOnHywayScript);
if (isValid(cachedExportsStatement)) {
return cachedExportsStatement;
}
//WELCOME TO HELL!!!
PsiFile file = anyElementOnHywayScript.getContainingFile();
if (file instanceof XmlFile) {
XmlDocument document = ((XmlFile) file).getDocument();
if (document != null) {
for (PsiElement e : document.getChildren()) {
if (e instanceof XmlTag) {
if ("script".equals(((XmlTag) e).getName())) {
for (PsiElement e1 : e.getChildren()) {
if (e1 instanceof JSEmbeddedContent) {
for (PsiElement e2 : e1.getChildren()) {
if (e2 instanceof JSExpressionStatement) {
for (PsiElement e3 : e2.getChildren()) {
if (e3 instanceof JSAssignmentExpression) {
PsiElement[] children = e3.getChildren();
if (children.length == 2) {
if (children[0].getText().equals("module.exports")) {
if (children[1] instanceof JSObjectLiteralExpression) {
cachedExportsStatement = (JSObjectLiteralExpression) children[1];
}
}
}
}
}
}
}
}
}
}
}
}
}
}
return cachedExportsStatement;
}
private static boolean isValid(JSObjectLiteralExpression expression) {
if (expression == null) {
return false;
}
try {
PsiFile file = expression.getContainingFile();
if (file == null) {
return false;
}
} catch (PsiInvalidElementAccessException e) {
return false;
}
JSProperty data = expression.findProperty("data");
if (data == null || data.getValue() == null) {
return false;
}
return true;
}
public static Map<String, String> getAllVarNames(PsiElement any) {
ensureFile(any);
getVarDeclaration(any, String.valueOf(System.currentTimeMillis()));
return vars;
}
public static Set<String> getAllFunctionNames(PsiElement any) {
ensureFile(any);
getFunctionDeclaration(any, String.valueOf(System.currentTimeMillis()));
return functions;
}
public static JSProperty getVarDeclaration(PsiElement anyElementOnHywayScript, String valueName) {
valueName = CodeUtil.getVarNameFromMustache(valueName);
JSObjectLiteralExpression exports = getExportsStatement(anyElementOnHywayScript);
vars.clear();
JSProperty ret = null;
if (exports != null) {
try {
PsiFile file = exports.getContainingFile();
if (file == null) {
return null;
}
} catch (PsiInvalidElementAccessException e) {
return null;
}
JSProperty data = exports.findProperty("data");
if (data == null || data.getValue() == null) {
return null;
}
for (PsiElement pe : data.getValue().getChildren()) {
if (pe instanceof JSProperty) {
String varName = ((JSProperty) pe).getName();
String varValue = getJSPropertyType((JSProperty) pe);
if (varName != null && varValue != null) {
vars.put(varName, varValue);
}
if (valueName.equals(varName)) {
ret = (JSProperty) pe;
}
}
}
}
return ret;
}
public static JSFunctionExpression getFunctionDeclaration(PsiElement anyElementOnHywayScript, String valueName) {
valueName = CodeUtil.getFunctionNameFromMustache(valueName);
JSObjectLiteralExpression exports = getExportsStatement(anyElementOnHywayScript);
functions.clear();
JSFunctionExpression ret = null;
if (exports != null) {
try {
PsiFile file = exports.getContainingFile();
if (file == null) {
return null;
}
} catch (PsiInvalidElementAccessException e) {
return null;
}
JSProperty data = exports.findProperty("methods");
if (data != null && data.getValue() != null) {
for (PsiElement e : data.getValue().getChildren()) {
if (e instanceof JSProperty) {
for (PsiElement e1 : e.getChildren()) {
if (e1 instanceof JSFunctionExpression) {
functions.add(((JSFunctionExpression) e1).getName());
if (valueName.equals(((JSFunctionExpression) e1).getName())) {
ret = (JSFunctionExpression) e1;
}
}
}
}
}
}
}
return ret;
}
public static int getExportsEndOffset(PsiElement anyElementOnHywayScript, String name) {
JSObjectLiteralExpression exports = getExportsStatement(anyElementOnHywayScript);
if (exports != null) {
try {
PsiFile file = exports.getContainingFile();
if (file == null) {
return -1;
}
} catch (PsiInvalidElementAccessException e) {
return -1;
}
JSProperty data = exports.findProperty(name);
if (data == null || data.getValue() == null) {
return -1;
}
return data.getValue().getTextRange().getEndOffset() - 1;
}
return -1;
}
public static boolean isMustacheValue(String value) {
if (value == null) {
return false;
}
return Pattern.compile("\\{\\{.+?\\}\\}").matcher(value).matches();
}
public static boolean containsMustacheValue(String value) {
if (value == null) {
return false;
}
return Pattern.compile(".*\\{\\{.+?\\}\\}.*").matcher(value).matches();
}
public static Map<String, TextRange> getVars(String src) {
Map<String, TextRange> results = new IdentityHashMap<String, TextRange>();
Pattern p = Pattern.compile("\\{\\{.+?\\}\\}");
Matcher m = p.matcher(src);
while (m.find()) {
String g = m.group().replaceAll("\\{+", "").replaceAll("\\}+", "").trim();
TextRange textRange = new TextRange(m.start(), m.end());
results.put(g, textRange);
}
return results;
}
}