blob: 9c7db70efebb161589c8c45f67520710b56cd4ad [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.javascript2.editor.hints;
import com.oracle.js.parser.Token;
import com.oracle.js.parser.TokenType;
import com.oracle.js.parser.ir.ClassNode;
import com.oracle.js.parser.ir.FunctionNode;
import com.oracle.js.parser.ir.IdentNode;
import com.oracle.js.parser.ir.LiteralNode;
import com.oracle.js.parser.ir.PropertyNode;
import com.oracle.js.parser.ir.UnaryNode;
import com.oracle.js.parser.ir.VarNode;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import javax.swing.text.BadLocationException;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.modules.csl.api.Hint;
import org.netbeans.modules.csl.api.HintsProvider;
import org.netbeans.modules.csl.api.OffsetRange;
import static org.netbeans.modules.javascript2.editor.JsVersion.ECMA6;
import static org.netbeans.modules.javascript2.editor.hints.EcmaLevelRule.ecmaEditionProjectBelow;
import org.netbeans.modules.javascript2.lexer.api.JsTokenId;
import org.netbeans.modules.javascript2.lexer.api.LexUtilities;
import org.netbeans.modules.javascript2.model.api.ModelUtils;
import org.netbeans.modules.javascript2.model.spi.PathNodeVisitor;
import org.netbeans.modules.parsing.api.Snapshot;
import org.openide.util.NbBundle;
public class Ecma6Rule extends EcmaLevelRule {
private static final List<JsTokenId> ECMA6LIST = Arrays.asList(
JsTokenId.TEMPLATE_BEGIN,
JsTokenId.TEMPLATE,
JsTokenId.TEMPLATE_END,
JsTokenId.TEMPLATE_EXP_BEGIN,
JsTokenId.TEMPLATE_EXP_END,
JsTokenId.KEYWORD_EXPORT,
JsTokenId.KEYWORD_IMPORT
);
@Override
void computeHints(JsHintsProvider.JsRuleContext context, List<Hint> hints, int offset, HintsProvider.HintsManager manager) throws BadLocationException {
if (ecmaEditionProjectBelow(context, ECMA6)) {
Snapshot snapshot = context.getJsParserResult().getSnapshot();
TokenSequence<? extends JsTokenId> ts = LexUtilities.getJsTokenSequence(snapshot, context.lexOffset);
OffsetRange returnOffsetRange;
if (ts != null) {
while (ts.moveNext()) {
org.netbeans.api.lexer.Token<? extends JsTokenId> token = LexUtilities.findNextIncluding(ts, ECMA6LIST);
if (token != null && token.length() >= 1 && ECMA6LIST.contains(token.id())) {
returnOffsetRange = new OffsetRange(ts.offset(), ts.offset() + token.length());
addHint(context, hints, returnOffsetRange);
}
}
}
Ecma6Visitor visitor = new Ecma6Visitor();
visitor.process(context, hints);
}
}
private void addHint(JsHintsProvider.JsRuleContext context, List<Hint> hints, OffsetRange range) {
addDocumenHint(context, hints, ModelUtils.documentOffsetRange(context.getJsParserResult(),
range.getStart(), range.getEnd()));
}
private void addDocumenHint(JsHintsProvider.JsRuleContext context, List<Hint> hints, OffsetRange range) {
hints.add(new Hint(this, Bundle.Ecma6Desc(),
context.getJsParserResult().getSnapshot().getSource().getFileObject(),
range, Collections.singletonList(
new SwitchToEcmaXFix(context.getJsParserResult().getSnapshot(), ECMA6)), 600));
}
@Override
public Set<?> getKinds() {
return Collections.singleton(JsAstRule.JS_OTHER_HINTS);
}
@Override
public String getId() {
return "ecma6.hint";
}
@NbBundle.Messages("Ecma6Desc=ECMA6 feature used in pre-ECMA6 source")
@Override
public String getDescription() {
return Bundle.Ecma6Desc();
}
@NbBundle.Messages("Ecma6DisplayName=ECMA6 feature used")
@Override
public String getDisplayName() {
return Bundle.Ecma6DisplayName();
}
private class Ecma6Visitor extends PathNodeVisitor {
private List<Hint> hints;
private JsHintsProvider.JsRuleContext context;
public void process(JsHintsProvider.JsRuleContext context, List<Hint> hints) {
this.hints = hints;
this.context = context;
FunctionNode root = context.getJsParserResult().getRoot();
if (root != null) {
context.getJsParserResult().getRoot().accept(this);
}
}
@Override
public boolean enterFunctionNode(FunctionNode functionNode) {
if (functionNode.getKind() == FunctionNode.Kind.ARROW) {
addHint(context, hints, new OffsetRange(Token.descPosition(functionNode.getFirstToken()), functionNode.getFinish()));
}
if (functionNode.getKind() == FunctionNode.Kind.NORMAL) {
for (IdentNode param : functionNode.getParameters()) {
if (param.isDefaultParameter() || param.isRestParameter()) {
addHint(context, hints, new OffsetRange(param.getStart(), param.getFinish()));
}
}
}
return super.enterFunctionNode(functionNode);
}
@Override
public boolean enterClassNode(ClassNode classNode) {
int headEnd = findToken(classNode.getStart(), JsTokenId.BRACKET_LEFT_CURLY);
addHint(context, hints, new OffsetRange(classNode.getStart(), headEnd));
return super.enterClassNode(classNode);
}
@Override
public boolean enterVarNode(VarNode varNode) {
long token = varNode.getToken();
TokenType type = Token.descType(token);
if (TokenType.LET == type || TokenType.CONST == type) {
addHint(context, hints, new OffsetRange(varNode.getStart(), varNode.getFinish()));
}
return super.enterVarNode(varNode);
}
@Override
public boolean enterLiteralNode(LiteralNode literalNode) {
if (literalNode instanceof LiteralNode.ArrayLiteralNode && ((LiteralNode.ArrayLiteralNode) literalNode).hasSpread()) {
addHint(context, hints, new OffsetRange(literalNode.getStart(), literalNode.getFinish()));
}
return super.enterLiteralNode(literalNode);
}
@Override
public boolean enterPropertyNode(PropertyNode propertyNode) {
if (propertyNode.isComputed()) {
addHint(context, hints, new OffsetRange(propertyNode.getStart(), propertyNode.getFinish()));
}
return super.enterPropertyNode(propertyNode);
}
@Override
public boolean enterUnaryNode(UnaryNode unaryNode) {
if (unaryNode.isTokenType(TokenType.AWAIT) || unaryNode.isTokenType(TokenType.YIELD)) {
long token = unaryNode.getToken();
int position = Token.descPosition(token);
addHint(context, hints, new OffsetRange(position, position + Token.descLength(token)));
}
return super.enterUnaryNode(unaryNode);
}
private int findToken(int offset, JsTokenId tokenId) {
int fileOffset = context.parserResult.getSnapshot().getOriginalOffset(offset);
if (fileOffset >= 0) {
TokenSequence<? extends JsTokenId> ts = LexUtilities.getPositionedSequence(
context.parserResult.getSnapshot(), offset, JsTokenId.javascriptLanguage());
if (ts != null) {
while (ts.moveNext()) {
org.netbeans.api.lexer.Token<? extends JsTokenId> next = LexUtilities.findNextNonWsNonComment(ts);
if (next != null && next.id() == tokenId) {
org.netbeans.api.lexer.Token<? extends JsTokenId> prev = LexUtilities.findPrevious(ts, Arrays.asList(JsTokenId.WHITESPACE, JsTokenId.EOL, JsTokenId.LINE_COMMENT, JsTokenId.BLOCK_COMMENT, JsTokenId.DOC_COMMENT, tokenId));
if (prev != null) {
return ts.offset() + prev.length();
}
}
}
}
}
return -1;
}
}
}