blob: 5f4784a6bdd1606fc5641e957fd8bc249c6c2cf9 [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.apache.royale.linter;
import java.io.StringReader;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.IOUtils;
import org.apache.royale.compiler.common.PrefixMap;
import org.apache.royale.compiler.internal.filespecs.StringFileSpecification;
import org.apache.royale.compiler.internal.mxml.MXMLData;
import org.apache.royale.compiler.internal.parsing.mxml.MXMLToken;
import org.apache.royale.compiler.internal.parsing.mxml.MXMLTokenizer;
import org.apache.royale.compiler.mxml.IMXMLLanguageConstants;
import org.apache.royale.compiler.mxml.IMXMLTagData;
import org.apache.royale.compiler.mxml.IMXMLUnitData;
import org.apache.royale.compiler.parsing.IMXMLToken;
import org.apache.royale.compiler.parsing.IMXMLToken.MXMLTokenKind;
import org.apache.royale.compiler.problems.ICompilerProblem;
import org.apache.royale.linter.internal.BaseLinter;
public class MXMLLinter extends BaseLinter {
private static final String LINTER_TAG_OFF = "@linter:off";
private static final String LINTER_TAG_ON = "@linter:on";
public MXMLLinter(LinterSettings settings) {
super(settings);
}
public void lint(String filePath, String text, Collection<ICompilerProblem> allProblems) {
if (allProblems == null) {
allProblems = new ArrayList<ICompilerProblem>();
}
List<ICompilerProblem> fileProblems = new ArrayList<ICompilerProblem>();
try {
StringReader textReader = new StringReader(text);
MXMLTokenizer mxmlTokenizer = new MXMLTokenizer();
List<MXMLToken> originalTokens = null;
try {
originalTokens = mxmlTokenizer.parseTokens(textReader);
} finally {
IOUtils.closeQuietly(textReader);
IOUtils.closeQuietly(mxmlTokenizer);
}
if (mxmlTokenizer.hasTokenizationProblems()) {
fileProblems.addAll(mxmlTokenizer.getTokenizationProblems());
}
if (!settings.ignoreProblems && hasErrors(fileProblems)) {
return;
}
boolean skipLinting = false;
IMXMLToken[] allTokens = originalTokens.toArray(new IMXMLToken[0]);
MXMLTokenQuery tokenQuery = new MXMLTokenQuery(allTokens);
for (LinterRule rule : settings.rules) {
Map<MXMLTokenKind, MXMLTokenVisitor> tokenHandlers = rule.getMXMLTokenVisitors();
if (tokenHandlers != null) {
for (IMXMLToken token : originalTokens) {
MXMLTokenKind tokenKind = token.getMXMLTokenKind();
if (tokenKind == MXMLTokenKind.COMMENT) {
String tokenText = token.getText();
boolean isASDoc = tokenText.startsWith("<!---") && tokenText.length() > 7;
String trimmed = tokenText.substring(isASDoc ? 5 : 4, tokenText.length() - 3).trim();
if (!skipLinting && LINTER_TAG_OFF.equals(trimmed)) {
skipLinting = true;
} else if (skipLinting && LINTER_TAG_ON.equals(trimmed)) {
skipLinting = false;
continue;
}
}
if (skipLinting) {
continue;
}
if (tokenHandlers.containsKey(tokenKind)) {
tokenHandlers.get(tokenKind).visit(token, tokenQuery, fileProblems);
}
}
}
}
PrefixMap prefixMap = mxmlTokenizer.getPrefixMap();
MXMLData mxmlData = new MXMLData(originalTokens, prefixMap, new StringFileSpecification(filePath, text));
IMXMLTagData rootTag = mxmlData.getRootTag();
visitTag(rootTag, tokenQuery, fileProblems);
IMXMLUnitData current = rootTag;
int offset = 1;
String className = Paths.get(filePath).getFileName().toString();
int extensionIndex = className.indexOf('.');
if (extensionIndex != -1) {
className = className.substring(0, extensionIndex);
}
String componentName = mxmlData.getRootTag().getShortName();
StringBuilder builder = new StringBuilder();
builder.append("/* ");
builder.append(LINTER_TAG_OFF);
builder.append(" */");
builder.append("package{public class ");
builder.append(className);
builder.append(" extends ");
builder.append(componentName);
builder.append("{");
while (current != null) {
if (current instanceof IMXMLTagData) {
IMXMLTagData tag = (IMXMLTagData) current;
if ("Script".equals(tag.getShortName())) {
String prefix = tag.getPrefix();
String ns = prefixMap.getNamespaceForPrefix(prefix);
if (ns != null
&& (IMXMLLanguageConstants.NAMESPACE_MXML_2006.equals(ns)
|| IMXMLLanguageConstants.NAMESPACE_MXML_2009.equals(ns)
|| IMXMLLanguageConstants.NAMESPACE_MXML_2012.equals(ns))) {
if (tag.isOpenTag()) {
int line = tag.getLine();
String scriptText = tag.getCompilableText();
// wrap the script inside a class so that rules don't
// get confused. adds new lines to ensure the line
// numbers match the original file
for (int i = offset; i < line; i++) {
builder.append("\n");
}
builder.append("/* ");
builder.append(LINTER_TAG_ON);
builder.append(" */\n");
builder.append(scriptText);
builder.append("/* ");
builder.append(LINTER_TAG_OFF);
builder.append(" */");
} else {
offset = tag.getLine() + 1;
}
}
}
}
current = current.getNext();
}
builder.append("}}\n");
ASLinter asLinter = new ASLinter(settings);
asLinter.lint(filePath, builder.toString(), fileProblems);
} finally {
allProblems.addAll(fileProblems);
}
}
private void visitTag(IMXMLTagData tag, MXMLTokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
for (LinterRule rule : settings.rules) {
List<MXMLTagVisitor> tagVisitors = rule.getMXMLTagVisitors();
if (tagVisitors != null) {
for (int i = 0; i < tagVisitors.size(); i++) {
tagVisitors.get(i).visit(tag, tokenQuery, problems);
}
}
}
IMXMLTagData current = tag.getFirstChild(true);
while (current != null) {
visitTag(current, tokenQuery, problems);
current = current.getNextSibling(true);
}
}
}