blob: ecf2e7a54fa0bc559fa2b055d8ce8593974a96e4 [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.html.angular;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import org.netbeans.api.editor.mimelookup.MimeRegistration;
import org.netbeans.api.html.lexer.HTMLTokenId;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.modules.html.angular.index.AngularJsController;
import org.netbeans.modules.html.angular.index.AngularJsIndex;
import org.netbeans.modules.html.angular.index.AngularJsIndexer;
import org.netbeans.modules.html.angular.model.AngularModel;
import org.netbeans.modules.html.angular.model.Directive;
import org.netbeans.modules.html.editor.api.gsf.HtmlParserResult;
import org.netbeans.modules.html.editor.spi.embedding.JsEmbeddingProviderPlugin;
import org.netbeans.modules.parsing.api.Embedding;
import org.netbeans.modules.parsing.api.Snapshot;
import org.netbeans.modules.web.common.api.LexerUtils;
import org.netbeans.modules.web.common.api.WebUtils;
import org.openide.filesystems.FileObject;
import org.openide.util.Exceptions;
/**
*
* @author Petr Pisl, mfukala@netbeans.org
* @author Roman Svitanic
*/
@MimeRegistration(mimeType = "text/html", service = JsEmbeddingProviderPlugin.class)
public class AngularJsEmbeddingProviderPlugin extends JsEmbeddingProviderPlugin {
private static class StackItem {
final String tag;
String finishText;
public StackItem(String tag) {
this.tag = tag;
this.finishText = ""; //NOI18N
}
public void addFinishText(String text) {
finishText = text + finishText;
}
}
private final LinkedList<StackItem> stack;
private TokenSequence<HTMLTokenId> tokenSequence;
private Snapshot snapshot;
private List<Embedding> embeddings;
private int processedTemplate;
private Directive interestedAttr;
/** keeps mapping from simple property name to the object fqn
*/
private HashMap<String, String> propertyToFqn;
public AngularJsEmbeddingProviderPlugin() {
this.stack = new LinkedList<>();
this.propertyToFqn = new HashMap<>();
}
@Override
public boolean startProcessing(HtmlParserResult parserResult, Snapshot snapshot, TokenSequence<HTMLTokenId> tokenSequence, List<Embedding> embeddings) {
if (AngularJsIndexer.Factory.isScannerThread()) {
return false;
}
this.snapshot = snapshot;
this.tokenSequence = tokenSequence;
this.embeddings = embeddings;
this.stack.clear();
this.processedTemplate = -1;
AngularModel model = AngularModel.getModel(parserResult);
if(!model.isAngularPage()) {
return false;
}
FileObject file = snapshot.getSource().getFileObject();
if (file == null) {
return false;
}
return true;
}
@Override
public void endProcessing() {
super.endProcessing(); //To change body of generated methods, choose Tools | Templates.
if (processedTemplate > 0) {
for (int i = 0; i < processedTemplate; i++) {
embeddings.add(snapshot.create("}\n});\n", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
}
}
}
@Override
public boolean processToken() {
boolean processed = false;
CharSequence tokenText = tokenSequence.token().text();
switch (tokenSequence.token().id()) {
case TAG_OPEN:
stack.push(new StackItem(tokenText.toString()));
break;
case TAG_CLOSE:
if (!stack.isEmpty()) {
StackItem top = stack.pop();
while (!stack.isEmpty() && !LexerUtils.equals(top.tag, tokenText, false, false)) {
if (!top.finishText.isEmpty()) {
embeddings.add(snapshot.create(top.finishText, Constants.JAVASCRIPT_MIMETYPE));
}
top = stack.pop();
}
if (!top.finishText.isEmpty()) {
embeddings.add(snapshot.create(top.finishText, Constants.JAVASCRIPT_MIMETYPE));
}
}
break;
case ARGUMENT:
Directive ajsDirective = Directive.getDirective(tokenText.toString().trim().toLowerCase());
if(ajsDirective != null) {
interestedAttr = ajsDirective;
} else {
interestedAttr = null;
}
break;
case VALUE:
if (interestedAttr != null) {
String value = WebUtils.unquotedValue(tokenText);
switch (interestedAttr) {
case controller:
processed = processController(value);
stack.peek().addFinishText("}\n});\n"); //NOI18N
break;
case model:
case disabled:
case click:
processed = processModel(value);
break;
case repeat:
case repeatStart:
processed = processRepeat(value);
stack.peek().addFinishText("}\n"); //NOI18N
break;
case modelOptions:
case link:
processed = processObject(value);
break;
case options:
processed = processComprehensionExpression(value);
stack.peek().addFinishText("}\n"); //NOI18N
break;
default:
processed = processExpression(value);
}
}
break;
case EL_OPEN_DELIMITER:
if (tokenSequence.moveNext()) {
if (tokenSequence.token().id() == HTMLTokenId.EL_CONTENT) {
String value = tokenSequence.token().text().toString();
int indexStart = 0;
boolean parenRemoved = false;
// check if one-time binding "{{::expr}}" is present
int oneTimeBindingShift = 0;
if (value.trim().startsWith("::")) { //NOI18N
// remove double colon
int doubleColonIndex = value.indexOf("::");
value = value.substring(doubleColonIndex + 2);
oneTimeBindingShift = doubleColonIndex + 2;
}
String name = value.trim();
if (value.startsWith("(")) {
name = value.substring(1);
parenRemoved = true;
indexStart = 1;
}
int parenIndex = name.indexOf('('); //NOI18N
if (parenIndex > -1) {
name = name.substring(0, parenIndex);
}
processTemplate();
if (propertyToFqn.containsKey(name)) {
embeddings.add(snapshot.create(propertyToFqn.get(name) + ".$scope.", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
embeddings.add(snapshot.create(tokenSequence.offset() + oneTimeBindingShift, value.length(), Constants.JAVASCRIPT_MIMETYPE));
embeddings.add(snapshot.create(";\n", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
processed = true;
} else if (!name.contains("|") && !name.contains(":")){ //NOI18N
embeddings.add(snapshot.create(tokenSequence.offset() + oneTimeBindingShift, value.length(), Constants.JAVASCRIPT_MIMETYPE));
embeddings.add(snapshot.create(";\n", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
processed = true;
} else if (name.contains("|")){
int indexEnd = name.indexOf('|');
name = name.substring(0, indexEnd);
if (parenRemoved) {
indexEnd = name.lastIndexOf(')');
if (indexEnd > -1) {
name = name.substring(0, indexEnd);
}
}
if (name.startsWith("-")) {
indexStart++;
name = name.substring(1);
}
if(propertyToFqn.containsKey(name)) {
embeddings.add(snapshot.create(propertyToFqn.get(name) + ".$scope.", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
embeddings.add(snapshot.create(tokenSequence.offset() + indexStart + oneTimeBindingShift, name.length(), Constants.JAVASCRIPT_MIMETYPE));
embeddings.add(snapshot.create(";\n", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
processed = true;
} else {
embeddings.add(snapshot.create(tokenSequence.offset() + indexStart + oneTimeBindingShift, name.length(), Constants.JAVASCRIPT_MIMETYPE));
embeddings.add(snapshot.create(";\n", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
processed = true;
}
} else {
tokenSequence.movePrevious();
}
} else if (tokenSequence.token().id() == HTMLTokenId.EL_CLOSE_DELIMITER) {
embeddings.add(snapshot.create(tokenSequence.offset(), 0, Constants.JAVASCRIPT_MIMETYPE));
processed = true;
} else {
tokenSequence.movePrevious();
}
}
break;
default:
}
return processed;
}
private boolean processController(String controllerName) {
processTemplate();
StringBuilder sb = new StringBuilder();
Project project = FileOwnerQuery.getOwner(snapshot.getSource().getFileObject());
AngularJsIndex index = null;
try {
index = AngularJsIndex.get(project);
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
if (controllerName.contains(" as ")) { //NOI18N
// we have controllerAs expression
String[] parts = controllerName.trim().split(" as "); //NOI18N
if (parts.length < 2) {
return false;
}
sb.append("(function () {\nvar "); //NOI18N
String fqn = parts[0].trim();
if (index != null) {
Collection<AngularJsController> controllers = index.getControllers(parts[0], true);
for (AngularJsController controller : controllers) {
if (controller.getName().equals(parts[0])) {
fqn = controller.getFqn();
break;
}
}
}
embeddings.add(snapshot.create(sb.toString(), Constants.JAVASCRIPT_MIMETYPE));
sb = new StringBuilder();
if (!fqn.isEmpty()) {
int propNameOffset = controllerName.indexOf(parts[1].trim());
embeddings.add(snapshot.create(tokenSequence.offset() + 1 + propNameOffset, parts[1].trim().length(), Constants.JAVASCRIPT_MIMETYPE));
sb.append(" = new "); //NOI18N
sb.append(fqn);
sb.append(";\n"); //NOI18N
}
embeddings.add(snapshot.create(sb.toString(), Constants.JAVASCRIPT_MIMETYPE));
sb = new StringBuilder();
if (!parts[0].isEmpty()) {
embeddings.add(snapshot.create(tokenSequence.offset() + 1, parts[0].length(), Constants.JAVASCRIPT_MIMETYPE));
sb.append(";");
} else {
embeddings.add(snapshot.create(tokenSequence.offset() + 1, 0, Constants.JAVASCRIPT_MIMETYPE));
}
sb.append("\n{ \n"); //NOI18N
embeddings.add(snapshot.create(sb.toString(), Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
return true;
} else {
// classic controller with $scope
sb.append("(function () {\nvar $scope = "); //NOI18N
String fqn = controllerName.trim();
if (index != null) {
Collection<AngularJsController> controllers = index.getControllers(controllerName, true);
for (AngularJsController controller : controllers) {
if (controller.getName().equals(controllerName)) {
fqn = controller.getFqn();
break;
}
}
}
if (!fqn.isEmpty()) {
sb.append(fqn);
sb.append(".");
}
sb.append("$scope;\n"); //NOI18N
embeddings.add(snapshot.create(sb.toString(), Constants.JAVASCRIPT_MIMETYPE));
sb = new StringBuilder();
final int embeddingEndOffset = tokenSequence.offset() + 1 + controllerName.length();
if (!controllerName.isEmpty() && embeddingEndOffset <= snapshot.getText().length()) {
embeddings.add(snapshot.create(tokenSequence.offset() + 1, controllerName.length(), Constants.JAVASCRIPT_MIMETYPE));
sb.append(";");
} else {
embeddings.add(snapshot.create(tokenSequence.offset() + 1, 0, Constants.JAVASCRIPT_MIMETYPE));
}
sb.append("\nwith ($scope) { \n");
embeddings.add(snapshot.create(sb.toString(), Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
return true;
}
}
private boolean processModel(String value) {
processTemplate();
if (value.isEmpty()) {
embeddings.add(snapshot.create("( function () {", Constants.JAVASCRIPT_MIMETYPE));
embeddings.add(snapshot.create(tokenSequence.offset() + 1, 0, Constants.JAVASCRIPT_MIMETYPE));
embeddings.add(snapshot.create(";})();\n", Constants.JAVASCRIPT_MIMETYPE));
} else {
embeddings.add(snapshot.create(tokenSequence.offset() + 1,
value.length() - (tokenSequence.index() == tokenSequence.tokenCount() - 1 ? 1 : 0) , Constants.JAVASCRIPT_MIMETYPE));
embeddings.add(snapshot.create(";\n", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
// int parenStart = value.indexOf('('); //NOI18N
// String name = value;
// int nameStart = 0;
// int lenght = name.length();
// if (parenStart > -1) {
// name = name.substring(0, parenStart).trim();
// }
// if (name.indexOf('=') > -1) {
// name = name.substring(0, name.indexOf('=')).trim();
// }
// if (name.charAt(0) == '!') {
// name = name.substring(1);
// nameStart = 1;
// }
// if (propertyToFqn.containsKey(name)) {
// embeddings.add(snapshot.create(propertyToFqn.get(name) + ".$scope.", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
//
// if(parenStart > -1) {
// int parenEnd = parenStart;
// int balance = 1;
// while (balance > 0 && parenEnd < value.length()) {
// char ch = value.charAt(parenEnd);
// if (ch == '(') {
// balance++;
// } else if (ch == ')') {
// balance--;
// }
// if (balance > 0) {
// parenEnd++;
// }
// }
// embeddings.add(snapshot.create(tokenSequence.offset() + 1, parenEnd, Constants.JAVASCRIPT_MIMETYPE));
// } else {
// embeddings.add(snapshot.create(tokenSequence.offset() + 1, lenght, Constants.JAVASCRIPT_MIMETYPE));
// }
// embeddings.add(snapshot.create(";\n", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
// } else {
// // need to create local variable
// if (value.indexOf(' ') == -1 && parenStart == -1 && value.indexOf('.') == -1) {
// embeddings.add(snapshot.create("var ", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
// }
// embeddings.add(snapshot.create(tokenSequence.offset() + 1 + nameStart, value.length() - nameStart, Constants.JAVASCRIPT_MIMETYPE));
// embeddings.add(snapshot.create(";\n", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
// }
}
return true;
}
private boolean processObject(String value) {
processTemplate();
embeddings.add(snapshot.create("( function () {\n", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
if (value.isEmpty()) {
embeddings.add(snapshot.create(tokenSequence.offset() + 1, 0, Constants.JAVASCRIPT_MIMETYPE));
} else {
embeddings.add(snapshot.create("var value = ", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
embeddings.add(snapshot.create(tokenSequence.offset() + 1, value.length(), Constants.JAVASCRIPT_MIMETYPE));
}
embeddings.add(snapshot.create(";\n})();\n", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
return true;
}
private boolean processRepeat(String expression) {
processTemplate();
boolean processed = false;
String repeatExpression = expression;
// split the expression with "track by"
// if present, it should be the last thing in expression
String[] trackByParts = expression.split(" track by "); //NOI18N
if (trackByParts.length == 2) {
repeatExpression = trackByParts[0];
}
// split the expression with "as"
// if present, it should be now the last thing in expression (after we have taken care of "track by")
String[] asParts = repeatExpression.split(" as "); //NOI18N
if (asParts.length == 2) {
repeatExpression = asParts[0];
}
processed = processRepeatLoop(repeatExpression, 0);
// now insert the embeddings for "as" alias expression and/or "track by" tracking expression
if (asParts.length == 2) {
String asPropName = asParts[1].trim();
int asPropNameOffset = expression.indexOf(asPropName);
embeddings.add(snapshot.create(tokenSequence.offset() + 1 + asPropNameOffset, asPropName.length(), Constants.JAVASCRIPT_MIMETYPE));
embeddings.add(snapshot.create(" = [];\n", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
}
if (trackByParts.length == 2) {
String trackingExpr = trackByParts[1].trim();
int trackingExprOffset = expression.indexOf(trackingExpr);
embeddings.add(snapshot.create(tokenSequence.offset() + 1 + trackingExprOffset, trackingExpr.length(), Constants.JAVASCRIPT_MIMETYPE));
embeddings.add(snapshot.create(";\n", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
}
return processed;
}
/**
* Processes the main part (loop itself) of the repeat expression and
* filters. E.g. "item in items" or "item in items | filter:searchText".
*
* @param expression loop expression to process, stripped of all leading
* "group by", "as" or "disable when" expressions. It can contain filters
* @param initialOffset offset shift from the original expression in
* directive's value. For "ng-repeat" it should be always 0. This means that
* no parts were stripped from the beginning of the original value
* @return {@code true} if cycle has been processed, {@code false} otherwise
*/
private boolean processRepeatLoop(String expression, int initialOffset) {
boolean processedCycle = false;
// split the expression with |
// we expect that the first part is the for cycle and the rest are conditions
// and attributes like orderby, filter etc.
String[] parts = expression.split("\\|"); //NOI18N
if (parts.length > 0) {
// try to create the for cycle in virtual source
if (parts[0].contains(" in ")) {
// we understand only "in" now
String[] forParts = parts[0].trim().split(" in "); // NOI18N
if (forParts.length < 2) {
return false;
}
embeddings.add(snapshot.create("for (var ", Constants.JAVASCRIPT_MIMETYPE));
// one-time binding in repeat expression (e.g., ng-repeat="item in ::items")
boolean oneTimeBinding = false;
int oneTimeBindingShift = 0;
if (forParts[1].trim().startsWith("::")) { //NOI18N
// one-time binding was found in repeat expression, remove double colon from collection name
int doubleColonIndex = forParts[1].indexOf("::"); //NOI18N
forParts[1] = forParts[1].substring(doubleColonIndex + 2);
oneTimeBindingShift = doubleColonIndex + 2;
oneTimeBinding = true;
}
// forParts keeps value, collection
// now need to check, whether the value is simple or (key, value) - issue #230223
if (!forParts[0].contains(",")) {
// create virtual source for simple case: value in collection
if (forParts.length == 2 && propertyToFqn.containsKey(forParts[1])) {
// if we know the collection from a controller ....
int lastPartPos = expression.indexOf(forParts[1]); // the start position of the collection name
embeddings.add(snapshot.create(tokenSequence.offset() + 1 + initialOffset, lastPartPos - oneTimeBindingShift, Constants.JAVASCRIPT_MIMETYPE));
embeddings.add(snapshot.create(propertyToFqn.get(forParts[1]) + ".$scope.", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
embeddings.add(snapshot.create(tokenSequence.offset() + 1 + lastPartPos + initialOffset, forParts[1].length(), Constants.JAVASCRIPT_MIMETYPE));
} else {
if (oneTimeBinding) {
// we don't know the collection from controller and we have one-time binding present
int lastPartPos = expression.indexOf(forParts[1]); // the start position of the collection name
embeddings.add(snapshot.create(tokenSequence.offset() + 1 + initialOffset, lastPartPos - oneTimeBindingShift, Constants.JAVASCRIPT_MIMETYPE));
embeddings.add(snapshot.create(tokenSequence.offset() + 1 + lastPartPos + initialOffset, forParts[1].length(), Constants.JAVASCRIPT_MIMETYPE));
} else {
// if we don't know the collection from a controller, put it to the virtual source as it is
embeddings.add(snapshot.create(tokenSequence.offset() + 1 + initialOffset, parts[0].length(), Constants.JAVASCRIPT_MIMETYPE));
}
}
embeddings.add(snapshot.create(") {\n", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
} else {
// expect that thre is expression like: (key, value) in collection
// such expression should be translated to: for (var key in collectoin) { var value = collection[key];
String valueExp = forParts[0].trim();
if (valueExp.startsWith("(")) { // NOI18N
valueExp = valueExp.substring(1);
}
if (valueExp.endsWith(")")) { // NOI18N
valueExp = valueExp.substring(0, valueExp.length() - 1);
}
valueExp = valueExp.trim();
String[] keyValue = valueExp.split(","); // NOI18N
int lastPartPos = expression.indexOf(forParts[1]); // the start position of the collection name
int keyPos = expression.indexOf(keyValue[0]);
// map the key name
embeddings.add(snapshot.create(tokenSequence.offset() + 1 + keyPos + initialOffset, keyValue[0].length(), Constants.JAVASCRIPT_MIMETYPE));
if (keyValue.length == 1) {
// add a comma after variable name to trigger the error if an identifier for value is missing
// e.g., ng-repeat="(key, ) in expression" instead of ng-repeat="(key, value) in expression"
embeddings.add(snapshot.create(",", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
}
// map " in "
embeddings.add(snapshot.create(" in ", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
if (forParts.length == 2 && propertyToFqn.containsKey(forParts[1])) {
// if we know the collection from a controller ....
// map the collection
embeddings.add(snapshot.create(propertyToFqn.get(forParts[1]) + ".$scope.", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
embeddings.add(snapshot.create(tokenSequence.offset() + 1 + lastPartPos + initialOffset, forParts[1].length(), Constants.JAVASCRIPT_MIMETYPE));
} else {
// map the collection
embeddings.add(snapshot.create(tokenSequence.offset() + 1 + lastPartPos + initialOffset, forParts[1].length(), Constants.JAVASCRIPT_MIMETYPE));
}
if (keyValue.length == 2) {
// both identfiers, for key and value are present: ng-repeat="(key, value) in expression"
embeddings.add(snapshot.create(") {\nvar ", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
int valuePos = expression.indexOf(keyValue[1]);
embeddings.add(snapshot.create(tokenSequence.offset() + 1 + valuePos + initialOffset, keyValue[1].length(), Constants.JAVASCRIPT_MIMETYPE));
embeddings.add(snapshot.create(";\n", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
} else {
embeddings.add(snapshot.create(") {\n", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
}
}
// the for cycle should be closed in appropriate CLOSE_TAG token
processedCycle = true;
}
int partIndex = 1;
int lastPartPos = parts[0].length() + 1;
while (partIndex < parts.length) { // are there any condition / attributes of the cycle?
if (parts[partIndex].contains(":")) {
String[] conditionParts = parts[partIndex].trim().split(":");
if (conditionParts.length > 1) {
String propName = conditionParts[1].trim();
int indexInName = 0;
char ch = propName.charAt(indexInName);
while ((indexInName < (propName.length() - 1)) && (Character.isWhitespace(ch) || ch == '{')) {
ch = propName.charAt(++indexInName);
}
if (indexInName > 0) {
propName = propName.substring(indexInName);
}
int position = lastPartPos + parts[partIndex].indexOf(propName) + 1;
indexInName = propName.length() - 1;
ch = propName.charAt(indexInName);
while (indexInName > 0 && (Character.isWhitespace(ch) || ch == '{' || ch == '}')) {
ch = propName.charAt(--indexInName);
}
if (indexInName < (propName.length() - 1)) {
propName = propName.substring(0, indexInName - 1);
}
if (propertyToFqn.containsKey(propName)) {
embeddings.add(snapshot.create(propertyToFqn.get(propName) + ".$scope.", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
}
embeddings.add(snapshot.create(tokenSequence.offset() + position + initialOffset, propName.length(), Constants.JAVASCRIPT_MIMETYPE));
embeddings.add(snapshot.create(";\n", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
}
}
lastPartPos = lastPartPos + parts[partIndex].length() + 1;
partIndex++;
}
}
return processedCycle;
}
private boolean processExpression(String value) {
processTemplate();
boolean processed = false;
if (value.isEmpty()) {
embeddings.add(snapshot.create("( function () {", Constants.JAVASCRIPT_MIMETYPE));
embeddings.add(snapshot.create(tokenSequence.offset() + 1, 0, Constants.JAVASCRIPT_MIMETYPE));
embeddings.add(snapshot.create(";})();\n", Constants.JAVASCRIPT_MIMETYPE));
processed = true;
} else {
int lastPartPos = 0;
int valueTrimPos = 0;
int oneTimeBindingShift = 0;
boolean oneTimeBinding = false;
if (value.trim().startsWith("::")) { //NOI18N
// one-time binding "::expr" was found, remove double colon from expression
int doubleColonIndex = value.indexOf("::");
value = value.substring(doubleColonIndex + 2);
oneTimeBindingShift = doubleColonIndex + 2;
oneTimeBinding = true;
}
if (value.startsWith("{")) {
value = value.substring(1);
lastPartPos = 1;
}
String valueTrim = value.trim();
if (valueTrim.endsWith("}")) {
value = valueTrim.substring(0, valueTrim.length() - 1);
valueTrim = "";
} else if (valueTrim.contains("}")) {
valueTrimPos = valueTrim.indexOf('}');
value = valueTrim.substring(0, valueTrimPos);
valueTrim = valueTrim.substring(valueTrimPos + 1);
} else {
valueTrim = "";
}
int index = value.indexOf(':'); // are there pairs like name: expr?
if (index > -1) {
String[] parts = value.split(","); // example: ng-class="{completed: todo.completed, editing: todo == editedTodo}"
for (String part : parts) {
index = value.indexOf(':');
if (index > 0) {
String[] conditionParts = part.trim().split(":");
if (conditionParts.length > 1 && !conditionParts[0].contains("?")) { //ignore if ternary operator is present (issue #251057)
String propName = conditionParts[1].trim();
if (!propName.isEmpty() && !propName.startsWith("//")) {
int position = lastPartPos + part.indexOf(propName) + oneTimeBindingShift + 1;
if (propName.charAt(0) == '"' || propName.charAt(0) == '\'') {
propName = propName.substring(1);
position++;
if (propName.endsWith("\"") || propName.endsWith("'")) {
propName = propName.substring(0, propName.length() - 1);
}
}
if (propertyToFqn.containsKey(propName)) {
embeddings.add(snapshot.create(propertyToFqn.get(propName) + ".$scope.", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
}
embeddings.add(snapshot.create(tokenSequence.offset() + position, propName.length(), Constants.JAVASCRIPT_MIMETYPE));
embeddings.add(snapshot.create(";\n", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
processed = true;
}
}
}
lastPartPos = lastPartPos + part.length() + 1;
}
}
if (!valueTrim.isEmpty()) {
embeddings.add(snapshot.create(tokenSequence.offset() + lastPartPos + oneTimeBindingShift + 1, valueTrim.length(), Constants.JAVASCRIPT_MIMETYPE));
embeddings.add(snapshot.create(";\n", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
}
if (oneTimeBinding && !processed) {
// we have one-time binding expression "::expr" which hasn't been processed yet
embeddings.add(snapshot.create(tokenSequence.offset() + oneTimeBindingShift + 1, value.length(), Constants.JAVASCRIPT_MIMETYPE));
embeddings.add(snapshot.create(";\n", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
processed = true;
}
}
return processed;
}
private boolean processComprehensionExpression(String expression) {
processTemplate();
boolean processed = false;
String comprehensionExpression = expression;
// split the expression with "track by"
// if present, it should be the last thing in expression
String[] trackByParts = expression.split(" track by "); //NOI18N
if (trackByParts.length == 2) {
comprehensionExpression = trackByParts[0];
}
String[] forKeywordParts = comprehensionExpression.split(" for "); //NOI18N
if (forKeywordParts.length == 2) {
comprehensionExpression = forKeywordParts[1];
int initialOffset = forKeywordParts[0].length() + 5; // 5 is length of " for "
processed = processRepeatLoop(comprehensionExpression, initialOffset);
String tmpStr = forKeywordParts[0].trim();
if (tmpStr.contains(" disable when ")) { //NOI18N
String[] disableWhenParts = tmpStr.split(" disable when "); //NOI18N
tmpStr = disableWhenParts[0].trim();
int disableWhenOffset = expression.indexOf(disableWhenParts[1]);
embeddings.add(snapshot.create(tokenSequence.offset() + 1 + disableWhenOffset, disableWhenParts[1].trim().length(), Constants.JAVASCRIPT_MIMETYPE));
embeddings.add(snapshot.create(";\n", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
}
if (tmpStr.contains(" group by ")) { //NOI18N
String[] groupByParts = tmpStr.split(" group by "); //NOI18N
tmpStr = groupByParts[0].trim();
int groupByOffset = expression.indexOf(groupByParts[1]);
embeddings.add(snapshot.create(tokenSequence.offset() + 1 + groupByOffset, groupByParts[1].trim().length(), Constants.JAVASCRIPT_MIMETYPE));
embeddings.add(snapshot.create(";\n", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
}
if (tmpStr.contains(" as ")) { //NOI18N
String[] asParts = tmpStr.split(" as "); //NOI18N
tmpStr = asParts[0].trim();
int asOffset = expression.indexOf(asParts[1]);
embeddings.add(snapshot.create(tokenSequence.offset() + 1 + asOffset, asParts[1].trim().length(), Constants.JAVASCRIPT_MIMETYPE));
embeddings.add(snapshot.create(";\n", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
}
int firstItemOffset = expression.indexOf(tmpStr);
embeddings.add(snapshot.create(tokenSequence.offset() + 1 + firstItemOffset, tmpStr.trim().length(), Constants.JAVASCRIPT_MIMETYPE));
embeddings.add(snapshot.create(";\n", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
if (trackByParts.length == 2) {
String trackingExpr = trackByParts[1].trim();
int trackingExprOffset = expression.indexOf(trackingExpr);
embeddings.add(snapshot.create(tokenSequence.offset() + 1 + trackingExprOffset, trackingExpr.length(), Constants.JAVASCRIPT_MIMETYPE));
embeddings.add(snapshot.create(";\n", Constants.JAVASCRIPT_MIMETYPE)); //NOI18N
}
}
return processed;
}
private void processTemplate() {
if (processedTemplate > -1) {
// was already processed
return;
}
processedTemplate = 0;
FileObject fo = snapshot.getSource().getFileObject();
Project project = FileOwnerQuery.getOwner(fo);
AngularJsIndex index = null;
try {
index = AngularJsIndex.get(project);
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
if (index != null) {
Collection<AngularJsController.ModuleConfigRegistration> controllersForTemplate = index.getControllersForTemplate(fo.toURI());
for (AngularJsController.ModuleConfigRegistration controllerRegistration : controllersForTemplate) {
Collection<AngularJsController> controllers = index.getControllers(controllerRegistration.getControllerName(), true);
if (!controllers.isEmpty()) {
String fqn;
for (AngularJsController controller : controllers) {
if (controller.getName().equals(controllerRegistration.getControllerName())) {
fqn = controller.getFqn();
processedTemplate++;
StringBuilder sb = new StringBuilder();
if (controllerRegistration.getControllerAsName() == null) {
// classic Angular controller with $scope
sb.append("(function () {\nvar $scope = "); //NOI18N
if (!fqn.isEmpty()) {
sb.append(fqn);
} else {
sb.append(controllerRegistration.getControllerName());
}
sb.append("."); //NOI18N
sb.append("$scope;\n"); //NOI18N
sb.append("\nwith ($scope) { \n"); //NOI18N
} else {
// we have controllerAs present in configuration
sb.append("(function () {\nvar "); //NOI18N
sb.append(controllerRegistration.getControllerAsName()).append(" = new "); //NOI18N
if (!fqn.isEmpty()) {
sb.append(fqn);
sb.append(";\n"); //NOI18N
} else {
sb.append(controllerRegistration.getControllerName());
sb.append(";\n"); //NOI18N
}
sb.append("{ \n"); //NOI18N
}
embeddings.add(snapshot.create(sb.toString(), Constants.JAVASCRIPT_MIMETYPE));
break;
}
}
} else {
processedTemplate++;
StringBuilder sb = new StringBuilder();
if (controllerRegistration.getControllerAsName() == null) {
// classic Angular controller with $scope
sb.append("(function () {\nvar $scope = "); //NOI18N
sb.append(controllerRegistration.getControllerName());
sb.append("."); //NOI18N
sb.append("$scope;\n"); //NOI18N
sb.append("\nwith ($scope) { \n"); //NOI18N
} else {
// we have controllerAs present in configuration
sb.append("(function () {\nvar "); //NOI18N
sb.append(controllerRegistration.getControllerAsName());
sb.append(" = new "); //NOI18N
sb.append(controllerRegistration.getControllerName());
sb.append(";\n"); //NOI18N
sb.append("{ \n"); //NOI18N
}
embeddings.add(snapshot.create(sb.toString(), Constants.JAVASCRIPT_MIMETYPE));
}
}
}
}
}