blob: 8dfbf64fdb57085749bda0424727f1efecc3f5bc [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.php.editor.csl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import org.netbeans.modules.csl.api.ColoringAttributes;
import org.netbeans.modules.csl.api.OffsetRange;
import org.netbeans.modules.csl.api.SemanticAnalyzer;
import org.netbeans.modules.parsing.api.Snapshot;
import org.netbeans.modules.parsing.spi.Parser.Result;
import org.netbeans.modules.parsing.spi.Scheduler;
import org.netbeans.modules.parsing.spi.SchedulerEvent;
import org.netbeans.modules.php.editor.CodeUtils;
import org.netbeans.modules.php.editor.api.NameKind;
import org.netbeans.modules.php.editor.api.QualifiedName;
import org.netbeans.modules.php.editor.api.elements.ElementFilter;
import org.netbeans.modules.php.editor.api.elements.FieldElement;
import org.netbeans.modules.php.editor.api.elements.FunctionElement;
import org.netbeans.modules.php.editor.api.elements.MethodElement;
import org.netbeans.modules.php.editor.api.elements.TypeConstantElement;
import org.netbeans.modules.php.editor.api.elements.TypeElement;
import org.netbeans.modules.php.editor.model.Model;
import org.netbeans.modules.php.editor.model.VariableScope;
import org.netbeans.modules.php.editor.model.impl.VariousUtils;
import org.netbeans.modules.php.editor.parser.PHPParseResult;
import org.netbeans.modules.php.editor.parser.astnodes.ASTNode;
import org.netbeans.modules.php.editor.parser.astnodes.ArrayAccess;
import org.netbeans.modules.php.editor.parser.astnodes.Block;
import org.netbeans.modules.php.editor.parser.astnodes.BodyDeclaration.Modifier;
import org.netbeans.modules.php.editor.parser.astnodes.ClassDeclaration;
import org.netbeans.modules.php.editor.parser.astnodes.ClassInstanceCreation;
import org.netbeans.modules.php.editor.parser.astnodes.ConstantDeclaration;
import org.netbeans.modules.php.editor.parser.astnodes.Expression;
import org.netbeans.modules.php.editor.parser.astnodes.FieldAccess;
import org.netbeans.modules.php.editor.parser.astnodes.FieldsDeclaration;
import org.netbeans.modules.php.editor.parser.astnodes.FunctionDeclaration;
import org.netbeans.modules.php.editor.parser.astnodes.FunctionName;
import org.netbeans.modules.php.editor.parser.astnodes.GroupUseStatementPart;
import org.netbeans.modules.php.editor.parser.astnodes.Identifier;
import org.netbeans.modules.php.editor.parser.astnodes.InterfaceDeclaration;
import org.netbeans.modules.php.editor.parser.astnodes.MethodDeclaration;
import org.netbeans.modules.php.editor.parser.astnodes.MethodInvocation;
import org.netbeans.modules.php.editor.parser.astnodes.NamespaceName;
import org.netbeans.modules.php.editor.parser.astnodes.PHPDocTypeNode;
import org.netbeans.modules.php.editor.parser.astnodes.PHPVarComment;
import org.netbeans.modules.php.editor.parser.astnodes.Program;
import org.netbeans.modules.php.editor.parser.astnodes.StaticConstantAccess;
import org.netbeans.modules.php.editor.parser.astnodes.StaticFieldAccess;
import org.netbeans.modules.php.editor.parser.astnodes.StaticMethodInvocation;
import org.netbeans.modules.php.editor.parser.astnodes.TraitDeclaration;
import org.netbeans.modules.php.editor.parser.astnodes.TraitMethodAliasDeclaration;
import org.netbeans.modules.php.editor.parser.astnodes.TypeDeclaration;
import org.netbeans.modules.php.editor.parser.astnodes.UseStatement;
import org.netbeans.modules.php.editor.parser.astnodes.SingleUseStatementPart;
import org.netbeans.modules.php.editor.parser.astnodes.UseStatementPart;
import org.netbeans.modules.php.editor.parser.astnodes.Variable;
import org.netbeans.modules.php.editor.parser.astnodes.visitors.DefaultTreePathVisitor;
import org.netbeans.modules.php.editor.parser.astnodes.visitors.DefaultVisitor;
import org.netbeans.modules.php.project.api.PhpAnnotations;
/**
*
* @author Petr Pisl
*/
public class SemanticAnalysis extends SemanticAnalyzer {
public static final EnumSet<ColoringAttributes> UNUSED_FIELD_SET = EnumSet.of(ColoringAttributes.UNUSED, ColoringAttributes.FIELD);
public static final EnumSet<ColoringAttributes> DEPRECATED_UNUSED_FIELD_SET = EnumSet.of(ColoringAttributes.DEPRECATED, ColoringAttributes.UNUSED, ColoringAttributes.FIELD);
public static final EnumSet<ColoringAttributes> DEPRECATED_FIELD_SET = EnumSet.of(ColoringAttributes.DEPRECATED, ColoringAttributes.FIELD);
public static final EnumSet<ColoringAttributes> UNUSED_STATIC_FIELD_SET = EnumSet.of(ColoringAttributes.UNUSED, ColoringAttributes.FIELD, ColoringAttributes.STATIC);
public static final EnumSet<ColoringAttributes> DEPRECATED_UNUSED_STATIC_FIELD_SET = EnumSet.of(
ColoringAttributes.DEPRECATED,
ColoringAttributes.UNUSED,
ColoringAttributes.FIELD,
ColoringAttributes.STATIC);
public static final EnumSet<ColoringAttributes> DEPRECATED_STATIC_FIELD_SET = EnumSet.of(ColoringAttributes.DEPRECATED, ColoringAttributes.FIELD, ColoringAttributes.STATIC);
public static final EnumSet<ColoringAttributes> UNUSED_METHOD_SET = EnumSet.of(ColoringAttributes.UNUSED, ColoringAttributes.METHOD);
public static final EnumSet<ColoringAttributes> DEPRECATED_UNUSED_METHOD_SET = EnumSet.of(ColoringAttributes.DEPRECATED, ColoringAttributes.UNUSED, ColoringAttributes.METHOD);
public static final EnumSet<ColoringAttributes> STATIC_METHOD_SET = EnumSet.of(ColoringAttributes.STATIC, ColoringAttributes.METHOD);
public static final EnumSet<ColoringAttributes> DEPRECATED_STATIC_METHOD_SET = EnumSet.of(ColoringAttributes.DEPRECATED, ColoringAttributes.STATIC, ColoringAttributes.METHOD);
public static final EnumSet<ColoringAttributes> DEPRECATED_METHOD_SET = EnumSet.of(ColoringAttributes.DEPRECATED, ColoringAttributes.METHOD);
public static final EnumSet<ColoringAttributes> UNUSED_STATIC_METHOD_SET = EnumSet.of(ColoringAttributes.STATIC, ColoringAttributes.METHOD, ColoringAttributes.UNUSED);
public static final EnumSet<ColoringAttributes> DEPRECATED_UNUSED_STATIC_METHOD_SET = EnumSet.of(
ColoringAttributes.DEPRECATED,
ColoringAttributes.STATIC,
ColoringAttributes.METHOD,
ColoringAttributes.UNUSED);
public static final EnumSet<ColoringAttributes> DEPRECATED_CLASS_SET = EnumSet.of(ColoringAttributes.DEPRECATED, ColoringAttributes.CLASS);
public static final EnumSet<ColoringAttributes> DEPRECATED_SET = EnumSet.of(ColoringAttributes.DEPRECATED);
public static final EnumSet<ColoringAttributes> DEPRECATED_STATIC_SET = EnumSet.of(ColoringAttributes.DEPRECATED, ColoringAttributes.STATIC);
public static final EnumSet<ColoringAttributes> ANNOTATION_TYPE_SET = EnumSet.of(ColoringAttributes.ANNOTATION_TYPE);
public static final EnumSet<ColoringAttributes> METHOD_INVOCATION_SET = EnumSet.of(ColoringAttributes.CUSTOM1);
public static final EnumSet<ColoringAttributes> STATIC_METHOD_INVOCATION_SET = EnumSet.of(ColoringAttributes.STATIC, ColoringAttributes.CUSTOM1);
private static final Logger LOGGER = Logger.getLogger(SemanticAnalysis.class.getName());
private static boolean isLogged = false;
private volatile boolean cancelled;
private boolean checkIfResolveDeprecatedElements = true;
private boolean isResolveDeprecatedElements = false;
private Map<OffsetRange, Set<ColoringAttributes>> semanticHighlights;
public SemanticAnalysis() {
semanticHighlights = null;
}
private static void setIsLogged(boolean isLogged) {
SemanticAnalysis.isLogged = isLogged;
}
private static boolean isLogged() {
return isLogged;
}
@Override
public Map<OffsetRange, Set<ColoringAttributes>> getHighlights() {
return semanticHighlights;
}
@Override
public void cancel() {
cancelled = true;
}
@Override
public void run(Result r, SchedulerEvent event) {
checkIfResolveDeprecatedElements = true;
if (isResolveDeprecatedElements()) {
if (!isLogged()) {
LOGGER.info("Resolving of deprecated elements in Semantic analysis - IDE will be possibly slow!");
setIsLogged(true);
}
}
resume();
if (isCancelled()) {
return;
}
process(r);
}
void process(Result r) {
PHPParseResult result = (PHPParseResult) r;
Map<OffsetRange, Set<ColoringAttributes>> highlights = new HashMap<>(100);
if (result.getProgram() != null) {
SemanticHighlightVisitor semanticHighlightVisitor = new SemanticHighlightVisitor(highlights, result.getSnapshot(), result.getModel());
result.getProgram().accept(semanticHighlightVisitor);
if (highlights.size() > 0) {
semanticHighlights = highlights;
} else {
semanticHighlights = null;
}
}
}
protected final boolean isCancelled() {
return cancelled;
}
protected final void resume() {
cancelled = false;
}
protected boolean isResolveDeprecatedElements() {
if (checkIfResolveDeprecatedElements) {
isResolveDeprecatedElements = PhpAnnotations.getDefault().isResolveDeprecatedElements();
checkIfResolveDeprecatedElements = false;
}
return isResolveDeprecatedElements;
}
@Override
public int getPriority() {
return 0;
}
@Override
public Class<? extends Scheduler> getSchedulerClass() {
return Scheduler.EDITOR_SENSITIVE_TASK_SCHEDULER;
}
private class SemanticHighlightVisitor extends DefaultTreePathVisitor {
private class ASTNodeColoring {
public ASTNode identifier;
public Set<ColoringAttributes> coloring;
public ASTNodeColoring(ASTNode identifier, Set<ColoringAttributes> coloring) {
this.identifier = identifier;
this.coloring = coloring;
}
}
Map<OffsetRange, Set<ColoringAttributes>> highlights;
// for unused private fields: name, varible
// if isused, then it's deleted from the list and marked as the field
private final Map<UnusedIdentifier, ASTNodeColoring> privateFieldsUnused;
// for unsed private method: name, identifier
private final Map<UnusedIdentifier, ASTNodeColoring> privateUnusedMethods;
// this is holder of blocks, which has to be scanned for usages in the class.
private List<Block> needToScan = new ArrayList<>();
private final Snapshot snapshot;
private final Model model;
private Set<TypeElement> deprecatedTypes;
private Set<MethodElement> deprecatedMethods;
private Set<FieldElement> deprecatedFields;
private Set<TypeConstantElement> deprecatedConstants;
private Set<FunctionElement> deprecatedFunctions;
// last visited type declaration
private TypeInfo typeInfo;
public SemanticHighlightVisitor(Map<OffsetRange, Set<ColoringAttributes>> highlights, Snapshot snapshot, Model model) {
this.highlights = highlights;
privateFieldsUnused = new HashMap<>();
privateUnusedMethods = new HashMap<>();
this.snapshot = snapshot;
this.model = model;
}
private Set<TypeElement> getDeprecatedTypes() {
if (isCancelled()) {
return Collections.EMPTY_SET;
}
if (deprecatedTypes == null) {
deprecatedTypes = ElementFilter.forDeprecated(true).filter(model.getIndexScope().getIndex().getTypes(NameKind.empty()));
}
return deprecatedTypes;
}
private Set<MethodElement> getDeprecatedMethods() {
if (isCancelled()) {
return Collections.EMPTY_SET;
}
if (deprecatedMethods == null) {
deprecatedMethods = ElementFilter.forDeprecated(true).filter(model.getIndexScope().getIndex().getMethods(NameKind.empty()));
}
return deprecatedMethods;
}
private Set<FunctionElement> getDeprecatedFunctions() {
if (isCancelled()) {
return Collections.EMPTY_SET;
}
if (deprecatedFunctions == null) {
deprecatedFunctions = ElementFilter.forDeprecated(true).filter(model.getIndexScope().getIndex().getFunctions(NameKind.empty()));
}
return deprecatedFunctions;
}
private Set<FieldElement> getDeprecatedFields() {
if (isCancelled()) {
return Collections.EMPTY_SET;
}
if (deprecatedFields == null) {
deprecatedFields = ElementFilter.forDeprecated(true).filter(model.getIndexScope().getIndex().getFields(NameKind.empty()));
}
return deprecatedFields;
}
private Set<TypeConstantElement> getDeprecatedConstants() {
if (isCancelled()) {
return Collections.EMPTY_SET;
}
if (deprecatedConstants == null) {
deprecatedConstants = ElementFilter.forDeprecated(true).filter(model.getIndexScope().getIndex().getTypeConstants(NameKind.empty()));
}
return deprecatedConstants;
}
private void addColoringForNode(ASTNode node, Set<ColoringAttributes> coloring) {
int start = snapshot.getOriginalOffset(node.getStartOffset());
if (start > -1) {
int end = start + node.getEndOffset() - node.getStartOffset();
assert coloring != null : snapshot.getText().toString();
highlights.put(new OffsetRange(start, end), coloring);
}
}
private void addColoringForUnusedPrivateFields() {
// are there unused private fields?
for (ASTNodeColoring item : privateFieldsUnused.values()) {
if (item.coloring.contains(ColoringAttributes.STATIC)) {
if (item.coloring.contains(ColoringAttributes.DEPRECATED)) {
addColoringForNode(item.identifier, DEPRECATED_UNUSED_STATIC_FIELD_SET);
} else {
addColoringForNode(item.identifier, UNUSED_STATIC_FIELD_SET);
}
} else {
if (item.coloring.contains(ColoringAttributes.DEPRECATED)) {
addColoringForNode(item.identifier, DEPRECATED_UNUSED_FIELD_SET);
} else {
addColoringForNode(item.identifier, UNUSED_FIELD_SET);
}
}
}
}
@Override
public void scan(ASTNode node) {
if (!isCancelled()) {
super.scan(node);
}
}
@Override
public void visit(Program program) {
if (isCancelled()) {
return;
}
scan(program.getStatements());
scan(program.getComments());
// are there unused private methods?
for (ASTNodeColoring item : privateUnusedMethods.values()) {
if (item.coloring.contains(ColoringAttributes.STATIC)) {
if (item.coloring.contains(ColoringAttributes.DEPRECATED)) {
addColoringForNode(item.identifier, DEPRECATED_UNUSED_STATIC_METHOD_SET);
} else {
addColoringForNode(item.identifier, UNUSED_STATIC_METHOD_SET);
}
} else {
if (item.coloring.contains(ColoringAttributes.DEPRECATED)) {
addColoringForNode(item.identifier, DEPRECATED_UNUSED_METHOD_SET);
} else {
addColoringForNode(item.identifier, UNUSED_METHOD_SET);
}
}
}
}
@Override
public void visit(ClassDeclaration cldec) {
if (isCancelled()) {
return;
}
addToPath(cldec);
typeInfo = new TypeDeclarationTypeInfo(cldec);
scan(cldec.getSuperClass());
scan(cldec.getInterfaes());
Identifier name = cldec.getName();
addColoringForNode(name, createTypeNameColoring(name));
needToScan = new ArrayList<>();
if (cldec.getBody() != null) {
cldec.getBody().accept(this);
// find all usages in the method bodies
while (!needToScan.isEmpty()) {
Block block = needToScan.remove(0);
block.accept(this);
}
addColoringForUnusedPrivateFields();
}
removeFromPath();
}
private Set<ColoringAttributes> createTypeNameColoring(Identifier typeName) {
if (isCancelled()) {
return Collections.EMPTY_SET;
}
Set<ColoringAttributes> result;
if (isDeprecatedTypeDeclaration(typeName)) {
result = DEPRECATED_CLASS_SET;
} else {
result = ColoringAttributes.CLASS_SET;
}
return result;
}
private boolean isDeprecatedTypeDeclaration(Identifier typeName) {
boolean isDeprecated = false;
if (!isCancelled() && isResolveDeprecatedElements()) {
VariableScope variableScope = model.getVariableScope(typeName.getStartOffset());
QualifiedName fullyQualifiedName = VariousUtils.getFullyQualifiedName(QualifiedName.create(typeName), typeName.getStartOffset(), variableScope);
for (TypeElement typeElement : getDeprecatedTypes()) {
if (typeElement.getFullyQualifiedName().equals(fullyQualifiedName)) {
isDeprecated = true;
break;
}
}
}
return isDeprecated;
}
@Override
public void visit(FunctionDeclaration node) {
if (isCancelled()) {
return;
}
Identifier functionName = node.getFunctionName();
if (isDeprecatedFunctionDeclaration(functionName)) {
addColoringForNode(functionName, DEPRECATED_SET);
}
super.visit(node);
}
private boolean isDeprecatedFunctionDeclaration(Identifier functionName) {
boolean isDeprecated = false;
if (!isCancelled() && isResolveDeprecatedElements()) {
for (FunctionElement functionElement : getDeprecatedFunctions()) {
if (functionElement.getName().equals(functionName.getName())) {
isDeprecated = true;
break;
}
}
}
return isDeprecated;
}
@Override
public void visit(MethodDeclaration md) {
if (isCancelled()) {
return;
}
scan(md.getFunction().getFormalParameters());
boolean isPrivate = Modifier.isPrivate(md.getModifier());
Identifier identifier = md.getFunction().getFunctionName();
String name = identifier.getName().toLowerCase();
Set<ColoringAttributes> coloring = createMethodDeclarationColoring(md);
// don't color private magic private method. methods which start __
// in case of trait, just ignore it because it may be used in other classes
if (isPrivate
&& !typeInfo.isTrait()
&& name != null
&& !name.startsWith("__")) { // NOI18N
privateUnusedMethods.put(new UnusedIdentifier(name, typeInfo), new ASTNodeColoring(identifier, coloring));
} else {
// color now only non private method and all trait methods
addColoringForNode(identifier, coloring);
}
if (!Modifier.isAbstract(md.getModifier())) {
// don't scan the body now. It should be scanned after all declarations
// are known
Block body = md.getFunction().getBody();
if (body != null) {
needToScan.add(body);
}
}
}
private Set<ColoringAttributes> createMethodDeclarationColoring(MethodDeclaration methodDeclaration) {
if (isCancelled()) {
return Collections.EMPTY_SET;
}
boolean isDeprecated = isDeprecatedMethodDeclaration(methodDeclaration.getFunction().getFunctionName());
Set<ColoringAttributes> coloring = isDeprecated ? DEPRECATED_METHOD_SET : ColoringAttributes.METHOD_SET;
if (Modifier.isStatic(methodDeclaration.getModifier())) {
coloring = isDeprecated ? DEPRECATED_STATIC_METHOD_SET : STATIC_METHOD_SET;
}
return coloring;
}
private boolean isDeprecatedMethodDeclaration(Identifier methodName) {
boolean isDeprecated = false;
if (!isCancelled() && isResolveDeprecatedElements()) {
VariableScope variableScope = model.getVariableScope(methodName.getStartOffset());
QualifiedName typeFullyQualifiedName = VariousUtils.getFullyQualifiedName(
QualifiedName.create(typeInfo.getName()),
methodName.getStartOffset(),
variableScope);
for (MethodElement methodElement : getDeprecatedMethods()) {
if (methodElement.getName().equals(methodName.getName()) && methodElement.getType().getFullyQualifiedName().equals(typeFullyQualifiedName)) {
isDeprecated = true;
break;
}
}
}
return isDeprecated;
}
@Override
public void visit(TraitMethodAliasDeclaration node) {
if (isCancelled()) {
return;
}
if (node.getNewMethodName() != null) {
addColoringForNode(node.getNewMethodName(), ColoringAttributes.METHOD_SET);
}
}
@Override
public void visit(MethodInvocation node) {
if (isCancelled()) {
return;
}
Identifier identifier = null;
if (node.getMethod().getFunctionName().getName() instanceof Variable) {
Variable variable = (Variable) node.getMethod().getFunctionName().getName();
if (variable.getName() instanceof Identifier) {
identifier = (Identifier) variable.getName();
}
} else if (node.getMethod().getFunctionName().getName() instanceof Identifier) {
identifier = (Identifier) node.getMethod().getFunctionName().getName();
}
if (identifier != null) {
ASTNodeColoring item = privateUnusedMethods.remove(new UnusedIdentifier(identifier.getName().toLowerCase(), typeInfo));
if (item != null) {
addColoringForNode(item.identifier, item.coloring);
}
addColoringForNode(identifier, METHOD_INVOCATION_SET);
}
super.visit(node);
}
@Override
public void visit(ClassInstanceCreation node) {
if (isCancelled()) {
return;
}
if (node.isAnonymous()) {
addToPath(node);
typeInfo = new ClassInstanceCreationTypeInfo(node);
scan(node.getSuperClass());
scan(node.getInterfaces());
needToScan = new ArrayList<>();
Block body = node.getBody();
if (body != null) {
body.accept(this);
// find all usages in the method bodies
while (!needToScan.isEmpty()) {
Block block = needToScan.remove(0);
block.accept(this);
}
addColoringForUnusedPrivateFields();
}
removeFromPath();
} else {
super.visit(node);
}
}
@Override
public void visit(InterfaceDeclaration node) {
if (isCancelled()) {
return;
}
typeInfo = new TypeDeclarationTypeInfo(node);
Identifier name = node.getName();
addColoringForNode(name, createTypeNameColoring(name));
super.visit(node);
}
@Override
public void visit(TraitDeclaration node) {
if (isCancelled()) {
return;
}
typeInfo = new TypeDeclarationTypeInfo(node);
Identifier name = node.getName();
addColoringForNode(name, createTypeNameColoring(name));
needToScan = new ArrayList<>();
if (node.getBody() != null) {
node.getBody().accept(this);
for (Block block : needToScan) {
block.accept(this);
}
addColoringForUnusedPrivateFields();
}
}
@Override
public void visit(FieldsDeclaration node) {
if (isCancelled()) {
return;
}
boolean isPrivate = Modifier.isPrivate(node.getModifier());
boolean isStatic = Modifier.isStatic(node.getModifier());
Variable[] variables = node.getVariableNames();
for (int i = 0; i < variables.length; i++) {
Variable variable = variables[i];
Set<ColoringAttributes> coloring = createFieldDeclarationColoring(variable, isStatic);
// in case of trait, just ignore it because it may be used in other classes
if (!isPrivate
|| typeInfo.isTrait()) {
addColoringForNode(variable.getName(), coloring);
} else {
if (variable.getName() instanceof Identifier) {
Identifier identifier = (Identifier) variable.getName();
privateFieldsUnused.put(new UnusedIdentifier(identifier.getName(), typeInfo), new ASTNodeColoring(identifier, coloring));
}
}
}
super.visit(node);
}
private Set<ColoringAttributes> createFieldDeclarationColoring(Variable variable, boolean isStatic) {
if (isCancelled()) {
return Collections.EMPTY_SET;
}
boolean isDeprecated = isDeprecatedFieldDeclaration(variable);
Set<ColoringAttributes> coloring = isDeprecated ? DEPRECATED_FIELD_SET : ColoringAttributes.FIELD_SET;
if (isStatic) {
coloring = isDeprecated ? DEPRECATED_STATIC_FIELD_SET : ColoringAttributes.STATIC_FIELD_SET;
}
return coloring;
}
private boolean isDeprecatedFieldDeclaration(Variable variable) {
boolean isDeprecated = false;
if (!isCancelled() && isResolveDeprecatedElements()) {
String variableName = CodeUtils.extractVariableName(variable);
VariableScope variableScope = model.getVariableScope(variable.getStartOffset());
QualifiedName typeFullyQualifiedName = VariousUtils.getFullyQualifiedName(
QualifiedName.create(typeInfo.getName()),
variable.getStartOffset(),
variableScope);
for (FieldElement fieldElement : getDeprecatedFields()) {
if (fieldElement.getName().equals(variableName) && fieldElement.getType().getFullyQualifiedName().equals(typeFullyQualifiedName)) {
isDeprecated = true;
break;
}
}
}
return isDeprecated;
}
@Override
public void visit(FieldAccess node) {
if (isCancelled()) {
return;
}
if (!node.getField().isDollared()) {
new FieldAccessVisitor(ColoringAttributes.FIELD_SET).scan(node.getField().getName());
}
scan(node.getField());
super.scan(node.getDispatcher());
}
@Override
public void visit(StaticMethodInvocation node) {
if (isCancelled()) {
return;
}
FunctionName fnName = node.getMethod().getFunctionName();
if (fnName.getName() instanceof Identifier) {
Identifier identifier = (Identifier) fnName.getName();
String name = identifier.getName().toLowerCase();
ASTNodeColoring item = privateUnusedMethods.remove(new UnusedIdentifier(name, typeInfo));
if (item != null) {
addColoringForNode(item.identifier, item.coloring);
}
} else if (fnName.getName() instanceof Variable) {
// e.g. $test->instance::$staticField();
// $test->instance::$staticField[0]();
Variable variable = (Variable) fnName.getName();
Expression expr = variable;
if (variable instanceof ArrayAccess) {
ArrayAccess arrayAccess = (ArrayAccess) variable;
expr = arrayAccess.getName();
}
if (variable.isDollared() || variable instanceof ArrayAccess) {
new FieldAccessVisitor(ColoringAttributes.STATIC_FIELD_SET).scan(expr);
super.visit(node);
return;
}
}
addColoringForNode(fnName, STATIC_METHOD_INVOCATION_SET);
super.visit(node);
}
@Override
public void visit(PHPVarComment node) {
if (isCancelled()) {
return;
}
int start = node.getVariable().getStartOffset();
int end = start + 4;
int startTranslated = snapshot.getOriginalOffset(start);
if (startTranslated > -1) {
int endTranslated = startTranslated + end - start;
highlights.put(new OffsetRange(startTranslated, endTranslated), ANNOTATION_TYPE_SET);
}
}
@Override
public void visit(StaticFieldAccess node) {
if (isCancelled()) {
return;
}
Expression expr = node.getField().getName();
if (expr instanceof ArrayAccess) {
ArrayAccess arrayAccess = (ArrayAccess) expr;
expr = arrayAccess.getName();
}
new FieldAccessVisitor(ColoringAttributes.STATIC_FIELD_SET).scan(expr);
super.visit(node);
}
@Override
public void visit(ConstantDeclaration node) {
if (isCancelled()) {
return;
}
ASTNode parentNode = null;
List<ASTNode> path = getPath();
if (path != null && path.size() > 1) {
parentNode = path.get(1);
}
if (parentNode instanceof ClassDeclaration || parentNode instanceof InterfaceDeclaration
|| parentNode instanceof TraitDeclaration) {
List<Identifier> names = node.getNames();
if (!names.isEmpty()) {
for (Identifier identifier : names) {
addColoringForNode(identifier, createConstantDeclarationColoring(identifier));
}
}
}
super.visit(node);
}
private Set<ColoringAttributes> createConstantDeclarationColoring(Identifier constantName) {
if (isCancelled()) {
return Collections.EMPTY_SET;
}
return isDeprecatedConstantDeclaration(constantName) ? DEPRECATED_STATIC_FIELD_SET : ColoringAttributes.STATIC_FIELD_SET;
}
private boolean isDeprecatedConstantDeclaration(Identifier constantName) {
boolean isDeprecated = false;
if (!isCancelled() && isResolveDeprecatedElements()) {
VariableScope variableScope = model.getVariableScope(constantName.getStartOffset());
QualifiedName typeFullyQualifiedName = VariousUtils.getFullyQualifiedName(
QualifiedName.create(typeInfo.getName()),
constantName.getStartOffset(),
variableScope);
for (TypeConstantElement constantElement : getDeprecatedConstants()) {
if (constantElement.getName().equals(constantName.getName()) && constantElement.getType().getFullyQualifiedName().equals(typeFullyQualifiedName)) {
isDeprecated = true;
break;
}
}
}
return isDeprecated;
}
@Override
public void visit(StaticConstantAccess node) {
if (isCancelled()) {
return;
}
Identifier constant = node.getConstantName();
if (constant != null) {
addColoringForNode(constant, ColoringAttributes.STATIC_FIELD_SET);
}
super.visit(node);
}
@Override
public void visit(PHPDocTypeNode node) {
if (isCancelled()) {
return;
}
if (isDeprecatedTypeNode(node)) {
addColoringForNode(node, DEPRECATED_SET);
}
}
private boolean isDeprecatedTypeNode(PHPDocTypeNode node) {
return isDeprecatedType(QualifiedName.create(node.getValue()), node.getStartOffset());
}
@Override
public void visit(NamespaceName node) {
if (isCancelled()) {
return;
}
if (isDeprecatedNamespaceName(node)) {
addColoringForNode(node, DEPRECATED_SET);
}
}
private boolean isDeprecatedNamespaceName(NamespaceName node) {
return isDeprecatedType(QualifiedName.create(node), node.getStartOffset());
}
private boolean isDeprecatedType(QualifiedName qualifiedName, int offset) {
boolean isDeprecated = false;
if (!isCancelled() && isResolveDeprecatedElements()) {
VariableScope variableScope = model.getVariableScope(offset);
QualifiedName fullyQualifiedName = VariousUtils.getFullyQualifiedName(qualifiedName, offset, variableScope);
for (TypeElement typeElement : getDeprecatedTypes()) {
if (typeElement.getFullyQualifiedName().equals(fullyQualifiedName)) {
isDeprecated = true;
break;
}
}
}
return isDeprecated;
}
@Override
public void visit(UseStatement node) {
if (isCancelled()) {
return;
}
List<UseStatementPart> parts = node.getParts();
for (int i = 0; i < parts.size(); i++) {
UseStatementPart useStatementPart = parts.get(i);
if (useStatementPart instanceof SingleUseStatementPart) {
SingleUseStatementPart singleUseStatementPart = (SingleUseStatementPart) useStatementPart;
if (isDeprecatedNamespaceName(singleUseStatementPart.getName())) {
addColoringForNode(singleUseStatementPart.getName(), DEPRECATED_SET);
}
} else if (useStatementPart instanceof GroupUseStatementPart) {
GroupUseStatementPart groupUseStatementPart = (GroupUseStatementPart) useStatementPart;
for (SingleUseStatementPart item : groupUseStatementPart.getItems()) {
if (isDeprecatedNamespaceName(CodeUtils.compoundName(groupUseStatementPart, item, true))) {
addColoringForNode(item.getName(), DEPRECATED_SET);
}
}
} else {
assert false : "Unexpected class type: " + useStatementPart.getClass().getName(); // NOI18N
}
}
}
private class FieldAccessVisitor extends DefaultVisitor {
private final Set<ColoringAttributes> coloring;
public FieldAccessVisitor(Set<ColoringAttributes> coloring) {
this.coloring = coloring;
}
@Override
public void visit(ArrayAccess node) {
super.scan(node.getName());
// don't scan(scan(node.getDimension()); issue #194535
}
@Override
public void visit(Identifier identifier) {
//remove the field, because is used
ASTNodeColoring removed = privateFieldsUnused.remove(new UnusedIdentifier(identifier.getName(), typeInfo));
if (removed != null) {
// if it was removed, marked as normal field
addColoringForNode(removed.identifier, removed.coloring);
}
addColoringForNode(identifier, coloring);
}
}
private class UnusedIdentifier {
private final String name;
private final TypeInfo typeInfo;
UnusedIdentifier(final String name, final TypeInfo typeInfo) {
this.name = name;
this.typeInfo = typeInfo;
}
@Override
public int hashCode() {
int hash = 5;
hash = 29 * hash + (this.name != null ? this.name.hashCode() : 0);
hash = 29 * hash + (this.typeInfo != null ? this.typeInfo.hashCode() : 0);
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final UnusedIdentifier other = (UnusedIdentifier) obj;
if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) {
return false;
}
if (this.typeInfo != other.typeInfo && (this.typeInfo == null || !this.typeInfo.equals(other.typeInfo))) {
return false;
}
return true;
}
}
}
private interface TypeInfo {
Expression getName();
boolean isTrait();
}
private static final class TypeDeclarationTypeInfo implements TypeInfo {
private final TypeDeclaration typeDeclaration;
private final boolean isTrait;
TypeDeclarationTypeInfo(TypeDeclaration typeDeclaration) {
assert typeDeclaration != null;
this.typeDeclaration = typeDeclaration;
this.isTrait = typeDeclaration instanceof TraitDeclaration;
}
@Override
public Expression getName() {
return typeDeclaration.getName();
}
@Override
public boolean isTrait() {
return isTrait;
}
}
private static final class ClassInstanceCreationTypeInfo implements TypeInfo {
private final ClassInstanceCreation classInstanceCreation;
ClassInstanceCreationTypeInfo(ClassInstanceCreation classInstanceCreation) {
assert classInstanceCreation != null;
this.classInstanceCreation = classInstanceCreation;
}
@Override
public Expression getName() {
return classInstanceCreation.getClassName().getName();
}
@Override
public boolean isTrait() {
return false;
}
}
}