/******************************************************************************* * Copyright (c) 2009, 2016 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Zend Technologies *******************************************************************************/ package org.eclipse.php.internal.core.codeassist; import java.util.*; import java.util.regex.Pattern; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.dltk.ast.ASTNode; import org.eclipse.dltk.ast.declarations.Declaration; import org.eclipse.dltk.ast.declarations.MethodDeclaration; import org.eclipse.dltk.ast.declarations.ModuleDeclaration; import org.eclipse.dltk.ast.declarations.TypeDeclaration; import org.eclipse.dltk.ast.expressions.Expression; import org.eclipse.dltk.ast.references.SimpleReference; import org.eclipse.dltk.ast.references.TypeReference; import org.eclipse.dltk.ast.references.VariableReference; import org.eclipse.dltk.codeassist.ScriptSelectionEngine; import org.eclipse.dltk.compiler.env.IModuleSource; import org.eclipse.dltk.core.*; import org.eclipse.dltk.core.index2.search.ISearchEngine.MatchRule; import org.eclipse.dltk.core.search.IDLTKSearchScope; import org.eclipse.dltk.core.search.SearchEngine; import org.eclipse.dltk.internal.core.AbstractSourceModule; import org.eclipse.dltk.internal.core.ModelElement; import org.eclipse.dltk.internal.core.SourceRefElement; import org.eclipse.dltk.ti.IContext; import org.eclipse.dltk.ti.ISourceModuleContext; import org.eclipse.dltk.ti.types.IEvaluatedType; import org.eclipse.jface.text.BadLocationException; import org.eclipse.php.core.PHPVersion; import org.eclipse.php.core.compiler.PHPFlags; import org.eclipse.php.core.compiler.ast.nodes.*; import org.eclipse.php.core.compiler.ast.nodes.PHPDocTag.TagKind; import org.eclipse.php.core.project.ProjectOptions; import org.eclipse.php.internal.core.Logger; import org.eclipse.php.internal.core.PHPCorePlugin; import org.eclipse.php.internal.core.compiler.ast.parser.ASTUtils; import org.eclipse.php.internal.core.documentModel.parser.PHPRegionContext; import org.eclipse.php.internal.core.documentModel.parser.regions.IPHPScriptRegion; import org.eclipse.php.internal.core.documentModel.parser.regions.PHPRegionTypes; import org.eclipse.php.internal.core.documentModel.partitioner.PHPPartitionTypes; import org.eclipse.php.internal.core.model.PerFileModelAccessCache; import org.eclipse.php.internal.core.model.PHPModelAccess; import org.eclipse.php.internal.core.typeinference.IModelAccessCache; import org.eclipse.php.internal.core.typeinference.PHPClassType; import org.eclipse.php.internal.core.typeinference.PHPModelUtils; import org.eclipse.php.internal.core.typeinference.PHPTypeInferenceUtils; import org.eclipse.php.internal.core.typeinference.context.IModelCacheContext; import org.eclipse.php.internal.core.util.text.PHPTextSequenceUtilities; import org.eclipse.php.internal.core.util.text.TextSequence; import org.eclipse.wst.sse.core.StructuredModelManager; import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; import org.eclipse.wst.sse.core.internal.provisional.text.*; public class PHPSelectionEngine extends ScriptSelectionEngine { private static final String OPEN_BRACE = "("; //$NON-NLS-1$ private static final String PROTECTED = "protected"; //$NON-NLS-1$ private static final String PUBLIC = "public"; //$NON-NLS-1$ private static final String OBJECT_OPERATOR = "->"; //$NON-NLS-1$ private static final String PAAMAYIM_NEKUDOTAIM = "::"; //$NON-NLS-1$ private static final String CONST = "const"; //$NON-NLS-1$ private static final String THIS = "this"; //$NON-NLS-1$ private static final String STATIC = "static"; //$NON-NLS-1$ private static final String PRIVATE = "private"; //$NON-NLS-1$ private static final String VAR = "var"; //$NON-NLS-1$ private static final String IMPLEMENTS = "implements"; //$NON-NLS-1$ private static final String EXTENDS = "extends"; //$NON-NLS-1$ private static final String NEW = "new"; //$NON-NLS-1$ private static final String INTERFACE = "interface"; //$NON-NLS-1$ private static final String CLASS = "class"; //$NON-NLS-1$ private static final String FUNCTION = "function"; //$NON-NLS-1$ private static final String INSTEADOF = "insteadof"; //$NON-NLS-1$ private static final String AS = "as"; //$NON-NLS-1$ private static final IType[] EMPTY = {}; private PHPVersion phpVersion; public IModelElement[] select(IModuleSource sourceUnit, int offset, int end) { if (!PHPCorePlugin.toolkitInitialized) { return EMPTY; } if (end < offset) { end = offset + 1; } ISourceModule sourceModule = (ISourceModule) sourceUnit.getModelElement(); phpVersion = ProjectOptions.getPHPVersion(sourceModule.getScriptProject().getProject()); // First, try to resolve using AST (if we have parsed it well): IModelAccessCache cache = new PerFileModelAccessCache(sourceModule); try { IModelElement[] elements = internalASTResolve(sourceModule, cache, offset, end); if (elements != null) { Collection<IModelElement> filtered = PHPModelUtils.filterElements(sourceModule, Arrays.asList(elements), null, null); return (IModelElement[]) filtered.toArray(new IModelElement[filtered.size()]); } } catch (ModelException e) { if (!e.isDoesNotExist()) { PHPCorePlugin.log(e); } } // Use the old way by playing with document & buffer: IStructuredDocument document = null; IStructuredModel structuredModel = null; try { IFile file = (IFile) sourceUnit.getModelElement().getResource(); if (file != null) { if (file.exists()) { structuredModel = StructuredModelManager.getModelManager().getExistingModelForRead(file); if (structuredModel != null) { document = structuredModel.getStructuredDocument(); } else { document = StructuredModelManager.getModelManager().createStructuredDocumentFor(file); } } else { document = StructuredModelManager.getModelManager().createNewStructuredDocumentFor(file); document.set(sourceUnit.getSourceContents()); } } } catch (Exception e) { PHPCorePlugin.log(e); } finally { if (structuredModel != null) { structuredModel.releaseFromRead(); } } if (document == null) { return EMPTY; } IModelElement[] elements = null; try { elements = internalResolve(document, sourceModule, cache, offset, end); } catch (BadLocationException e1) { PHPCorePlugin.log(e1); } catch (ModelException e) { if (!e.isDoesNotExist()) { PHPCorePlugin.log(e); } } catch (CoreException e1) { PHPCorePlugin.log(e1); } if (elements == null) { return EMPTY; } Collection<IModelElement> filtered = PHPModelUtils.filterElements(sourceModule, Arrays.asList(elements), cache, null); if (filtered.size() == 0) { return EMPTY; } IStructuredDocumentRegion sRegion = document.getRegionAtCharacterOffset(offset); if (sRegion != null) { ITextRegion tRegion = sRegion.getRegionAtCharacterOffset(offset); ITextRegionCollection container = sRegion; if (tRegion instanceof ITextRegionContainer) { container = (ITextRegionContainer) tRegion; tRegion = container.getRegionAtCharacterOffset(offset); } if (tRegion != null && tRegion.getType() == PHPRegionContext.PHP_CONTENT) { IPHPScriptRegion phpScriptRegion = (IPHPScriptRegion) tRegion; try { tRegion = phpScriptRegion .getPHPToken(offset - container.getStartOffset() - phpScriptRegion.getStart()); } catch (BadLocationException e) { tRegion = null; } if (tRegion != null) { // Determine element name: int elementStart = container.getStartOffset() + phpScriptRegion.getStart() + tRegion.getStart(); TextSequence statement = PHPTextSequenceUtilities.getStatement(elementStart + tRegion.getLength(), sRegion, true); if (statement.length() != 0) { int endPosition = PHPTextSequenceUtilities.readBackwardSpaces(statement, statement.length()); int startPosition = PHPTextSequenceUtilities.readIdentifierStartIndex(phpVersion, statement, endPosition, true); String elementName = startPosition < 0 ? "" //$NON-NLS-1$ : statement.subSequence(startPosition, endPosition).toString(); elementName = PHPModelUtils.extractElementName(elementName); if (elementName != null && elementName.length() > 0) { List<IModelElement> result = new LinkedList<IModelElement>(); for (Iterator<IModelElement> iterator = filtered.iterator(); iterator.hasNext();) { IModelElement modelElement = (IModelElement) iterator.next(); if (modelElement instanceof AliasField) { AliasField aliasField = (AliasField) modelElement; if (aliasField.getAlias().equals(elementName)) { result.add(aliasField.getField()); } } else if (modelElement instanceof IField) { String fieldName = elementName; if (!fieldName.startsWith("$")) { //$NON-NLS-1$ fieldName = "$" + fieldName; //$NON-NLS-1$ } if (modelElement.getElementName().equals(fieldName) || modelElement.getElementName().equals(elementName)) { result.add(modelElement); } } else if (modelElement.getElementName().equals(elementName)) { result.add(modelElement); } } return (IModelElement[]) result.toArray(new IModelElement[result.size()]); } } } } } return filtered.toArray(new IModelElement[filtered.size()]); } private IModelElement[] internalASTResolve(ISourceModule sourceModule, IModelAccessCache cache, int offset, int end) throws ModelException { String source; try { source = sourceModule.getSource(); offset = PHPTextSequenceUtilities.readIdentifierStartIndex(source, offset, true); if (offset < 0) { return null; } } catch (IndexOutOfBoundsException ex) { // ISourceModule.getSource() may throw // ArrayIndexOutOfBoundsException and // PHPTextSequenceUtilities.readIdentifierStartIndex() may throw // StringIndexOutOfBoundExceptio in case when main thread // modifies editor content in parallel to this action in background // thread return null; } end = PHPTextSequenceUtilities.readIdentifierEndIndex(source, end, true); ModuleDeclaration parsedUnit = SourceParserUtil.getModuleDeclaration(sourceModule, null); // boolean inDocBlock=false; if (parsedUnit instanceof PHPModuleDeclaration) { PHPModuleDeclaration phpModuleDeclaration = (PHPModuleDeclaration) parsedUnit; List<PHPDocBlock> phpBlocks = phpModuleDeclaration.getPHPDocBlocks(); for (PHPDocBlock phpDocBlock : phpBlocks) { int realStart = phpDocBlock.sourceStart(); int realEnd = phpDocBlock.sourceEnd(); if (realStart <= offset && realEnd >= end) { // inDocBlock=true; PHPDocTag[] tags = phpDocBlock.getTags(); boolean isMethodOrFunction = PHPTextSequenceUtilities.getMethodEndIndex(source, end, false) != -1; return lookForMatchingElements(tags, sourceModule, parsedUnit, offset, end, isMethodOrFunction, cache); } } } int methodEnd = PHPTextSequenceUtilities.getMethodEndIndex(source, end, true); if (methodEnd != -1) { end = methodEnd; } ASTNode node = ASTUtils.findMinimalNode(parsedUnit, offset, end); if (node == null) { return null; } IContext context = ASTUtils.findContext(sourceModule, parsedUnit, node); if (context == null) { return null; } if (context instanceof IModelCacheContext) { ((IModelCacheContext) context).setCache(cache); } // Function call: if (node instanceof PHPCallExpression) { return processPHPCallExpression((PHPCallExpression) node, sourceModule, parsedUnit, offset, context, cache); } // Static field or constant access: else if (node instanceof StaticDispatch) { StaticDispatch dispatch = (StaticDispatch) node; String fieldName = null; if (dispatch instanceof StaticConstantAccess) { fieldName = ((StaticConstantAccess) dispatch).getConstant().getName(); } else if (dispatch instanceof StaticFieldAccess) { ASTNode field = ((StaticFieldAccess) dispatch).getField(); if (field instanceof VariableReference) { fieldName = ((VariableReference) field).getName(); } } if (fieldName != null && dispatch.getDispatcher() != null) { IEvaluatedType dispatcherType = PHPTypeInferenceUtils.resolveExpression(sourceModule, parsedUnit, context, dispatch.getDispatcher()); if (dispatcherType != null) { IType[] elements = PHPTypeInferenceUtils.getModelElements(dispatcherType, (ISourceModuleContext) context, offset); List<IModelElement> fields = new LinkedList<IModelElement>(); if (elements != null) { for (IModelElement element : elements) { IType type = (IType) element; try { fields.addAll(Arrays.asList(PHPModelUtils.getTypeHierarchyField(type, cache.getSuperTypeHierarchy(type, new NullProgressMonitor()), fieldName, true, new NullProgressMonitor()))); } catch (Exception e) { PHPCorePlugin.log(e); } } } return fields.toArray(new IModelElement[fields.size()]); } } } // Dynamic field access: else if (node instanceof FieldAccess) { FieldAccess fieldAccess = (FieldAccess) node; ASTNode field = fieldAccess.getField(); String fieldName = null; if (field instanceof SimpleReference) { fieldName = ((SimpleReference) field).getName(); } if (fieldName != null && fieldAccess.getDispatcher() != null) { IEvaluatedType dispatcherType = PHPTypeInferenceUtils.resolveExpression(sourceModule, parsedUnit, context, fieldAccess.getDispatcher()); if (dispatcherType != null) { IModelElement[] elements = PHPTypeInferenceUtils.getModelElements(dispatcherType, (ISourceModuleContext) context, offset); List<IModelElement> fields = new LinkedList<IModelElement>(); if (elements != null) { for (IModelElement element : elements) { if (element instanceof IType) { IType type = (IType) element; try { fields.addAll(Arrays.asList(PHPModelUtils.getTypeField(type, fieldName, true))); } catch (ModelException e) { PHPCorePlugin.log(e); } } } } return fields.toArray(new IModelElement[fields.size()]); } } } else if (node instanceof NamespaceReference) { String name = ((NamespaceReference) node).getName(); IType[] namespace = PHPModelUtils.getNamespaceOf(name + NamespaceReference.NAMESPACE_SEPARATOR, sourceModule, offset, cache, null); return namespace; } // Class/Interface reference: else if (node instanceof TypeReference) { TypeReference typeReference = (TypeReference) node; IEvaluatedType evaluatedType = PHPTypeInferenceUtils.resolveExpression(sourceModule, node); if (evaluatedType == null) { return EMPTY; } String name = evaluatedType.getTypeName(); IModelElement[] types = PHPModelUtils.getTypes(name, sourceModule, offset, cache, null); if (types.length == 0) { types = PHPModelUtils.getTraits(name, sourceModule, offset, cache, null); } if (types.length == 0) { // This can be a constant or namespace in PHP 5.3: if (name.length() > 0 && name.charAt(0) == NamespaceReference.NAMESPACE_SEPARATOR) { name = name.substring(1); } IDLTKSearchScope scope = SearchEngine.createSearchScope(sourceModule.getScriptProject()); types = PHPModelAccess.getDefault().findNamespaces(null, name, MatchRule.EXACT, 0, 0, scope, null); if (types.length == 0) { name = NamespaceReference.NAMESPACE_SEPARATOR + name; types = PHPModelUtils.getFields(name, sourceModule, offset, cache, null); } if (types.length == 0) { types = PHPModelUtils.getFunctions(name, sourceModule, offset, cache, null); } if (types.length == 0) { types = PHPModelUtils.getFunctions(typeReference.getName(), sourceModule, offset, cache, null); } } return types; } // 'new' statement else if (node instanceof ClassInstanceCreation) { ClassInstanceCreation newNode = (ClassInstanceCreation) node; Expression className = newNode.getClassName(); if ((className instanceof SimpleReference || className instanceof FullyQualifiedReference)) { IEvaluatedType evaluatedType = PHPTypeInferenceUtils.resolveExpression(sourceModule, node); if (evaluatedType != null) { return getConstructorsIfAny(extractClasses( PHPModelUtils.getTypes(evaluatedType.getTypeName(), sourceModule, offset, cache, null))); } } else if ((className instanceof StaticFieldAccess)) { StaticFieldAccess staticFieldAccess = (StaticFieldAccess) className; if ((offset >= staticFieldAccess.getDispatcher().sourceStart()) && (offset <= staticFieldAccess.getDispatcher().sourceEnd())) { className = staticFieldAccess.getDispatcher(); IEvaluatedType evaluatedType = PHPTypeInferenceUtils.resolveExpression(sourceModule, className); if (evaluatedType != null) { return extractClasses( PHPModelUtils.getTypes(evaluatedType.getTypeName(), sourceModule, offset, cache, null)); } } else if ((offset >= staticFieldAccess.getField().sourceStart()) && (offset <= staticFieldAccess.getField().sourceEnd())) { className = staticFieldAccess.getField(); String fieldName = null; ASTNode field = staticFieldAccess.getField(); if (field instanceof VariableReference) { fieldName = ((VariableReference) field).getName(); } if (fieldName != null && staticFieldAccess.getDispatcher() != null) { IEvaluatedType dispatcherType = PHPTypeInferenceUtils.resolveExpression(sourceModule, parsedUnit, context, staticFieldAccess.getDispatcher()); if (dispatcherType != null) { IModelElement[] elements = PHPTypeInferenceUtils.getModelElements(dispatcherType, (ISourceModuleContext) context, offset); List<IModelElement> fields = new LinkedList<IModelElement>(); if (elements != null) { for (IModelElement element : elements) { if (element instanceof IType) { IType type = (IType) element; try { fields.addAll( Arrays.asList(PHPModelUtils.getTypeField(type, fieldName, true))); } catch (ModelException e) { PHPCorePlugin.log(e); } } } } return fields.toArray(new IModelElement[fields.size()]); } } } } } // Class name in declaration else if ((node instanceof TypeDeclaration || node instanceof MethodDeclaration) && ((Declaration) node).getNameStart() <= offset && ((Declaration) node).getNameEnd() >= offset) { IModelElement element = sourceModule.getElementAt(node.sourceStart()); if (element != null) { return new IModelElement[] { element }; } } else if (node instanceof SimpleReference) { SimpleReference reference = (SimpleReference) node; node = ASTUtils.findMinimalNode(parsedUnit, offset, node.end() + 1); if (node instanceof TraitAliasStatement) { node = ASTUtils.findMinimalNode(parsedUnit, offset, node.end() + 1); if (node instanceof TraitUseStatement) { TraitUseStatement statement = (TraitUseStatement) node; List<IModelElement> methods = new LinkedList<IModelElement>(); for (TypeReference typeReference : statement.getTraitList()) { IType[] types = PHPModelUtils.getTypes(typeReference.getName(), sourceModule, offset, cache, null, false); for (IType t : types) { IModelElement[] children = t.getChildren(); for (IModelElement modelElement : children) { String name = modelElement.getElementName(); if (name.startsWith("$")) { //$NON-NLS-1$ name = name.substring(1); } if (name.equals(reference.getName())) { methods.add(modelElement); } } } } return methods.toArray(new IMethod[methods.size()]); } } } /* * else if (node instanceof Scalar) { Scalar scalar = (Scalar) node; if * (PHPModelUtils.isQuotesString(scalar.getValue())) { * * IEvaluatedType evaluatedType = PHPTypeInferenceUtils * .resolveExpression(sourceModule, node); if (evaluatedType != null) { * * IType[] types = PHPModelUtils.getTypes( evaluatedType.getTypeName(), * sourceModule, offset, null, null); return types; } } } */ return null; } private IModelElement[] lookForMatchingElements(PHPDocTag[] tags, ISourceModule sourceModule, ModuleDeclaration parsedUnit, int offset, int end, boolean isMethodOrFunction, IModelAccessCache cache) throws ModelException { if (tags == null) { return null; } for (PHPDocTag phpDocTag : tags) { if (phpDocTag.sourceStart() <= offset && phpDocTag.sourceEnd() >= end) { if (phpDocTag.getTagKind() == TagKind.INHERITDOC) { Declaration declaration = ASTUtils.findDeclarationAfterPHPdoc(parsedUnit, offset); if (declaration != null) { IModelElement element = sourceModule.getElementAt(declaration.sourceStart()); if (element != null) { try { if (element.getElementType() == IModelElement.METHOD) { IType type = (IType) element.getParent(); return PHPModelUtils.getSuperTypeHierarchyMethod(type, null, element.getElementName(), true, null); } else if (element.getElementType() == IModelElement.FIELD) { IType type = (IType) element.getParent(); return PHPModelUtils.getSuperTypeHierarchyField(type, null, element.getElementName(), true, null); } else if (element.getElementType() == IModelElement.TYPE) { return PHPModelUtils.getSuperClasses((IType) element, null); } } catch (CoreException e) { Logger.logException(e); } } } } else { for (TypeReference typeReference : phpDocTag.getTypeReferences()) { if (typeReference.sourceStart() <= offset && typeReference.sourceEnd() >= end) { boolean isNamespacePart = false; String name = typeReference.getName(); // remove additional end elements like ']', '[]' or // '()' if (typeReference.sourceEnd() > end) { isNamespacePart = name.charAt( end - typeReference.sourceStart()) == NamespaceReference.NAMESPACE_SEPARATOR; name = name.substring(0, end - typeReference.sourceStart()); // check if we're in an array int idx = name.lastIndexOf('[', offset - typeReference.sourceStart() - 1); if (idx != -1) { name = name.substring(idx + 1); } } String[] parts = name.split(Pattern.quote(PAAMAYIM_NEKUDOTAIM), 3); if (parts.length > 1) { if (parts.length == 2 && parts[0].length() > 0 && parts[1].length() > 0) { boolean isVariable = parts[1].charAt(0) == '$'; // to determine if it was the part before // "::" that was selected boolean isClassOrNamespacePartSelected = offset <= typeReference.sourceStart() + parts[0].length(); IType[] types = filterNS( PHPModelUtils.getTypes(parts[0], sourceModule, offset, cache, null)); if (isClassOrNamespacePartSelected) { // NB : no need to check for namespaces, // we cannot end here with variable // "isNamespacePart" set to true // and variable "name" containing "::" return types; } else { if (isMethodOrFunction && !isVariable) { // class method List<IMethod> methods = new LinkedList<IMethod>(); for (IType type : types) { methods.addAll(Arrays .asList(PHPModelUtils.getTypeMethod(type, parts[1], true))); } return methods.toArray(new IMethod[methods.size()]); } else { // class field or class constant List<IField> fields = new LinkedList<IField>(); for (IType type : types) { fields.addAll(Arrays .asList(PHPModelUtils.getTypeField(type, parts[1], true))); } return fields.toArray(new IField[fields.size()]); } } } } else if (name.length() > 0) { boolean isVariable = name.charAt(0) == '$'; if (isVariable) { return PHPModelUtils.getFields(name, sourceModule, offset, cache, null); } else if (isMethodOrFunction) { return PHPModelUtils.getFunctions(name, sourceModule, offset, cache, null); } else { // it's either a // class/interface/namespace... if (isNamespacePart) { return PHPModelUtils.getNamespaceOf( name + NamespaceReference.NAMESPACE_SEPARATOR, sourceModule, offset, cache, null); } IType[] types = filterNS( PHPModelUtils.getTypes(name, sourceModule, offset, cache, null)); if (types.length == 0) { // ... or a global constant return PHPModelUtils.getFields(name, sourceModule, offset, cache, null); } return types; } } } } } } } return null; } private IType[] filterNS(IType[] types) throws ModelException { if (types == null) { return EMPTY; } else { Set<? super IType> result = new HashSet<IType>(); for (IType type : types) { if (PHPFlags.isClass(type.getFlags()) || PHPFlags.isInterface(type.getFlags())) { result.add(type); } } return (IType[]) result.toArray(new IType[result.size()]); } } private IModelElement[] internalResolve(IStructuredDocument sDoc, ISourceModule sourceModule, IModelAccessCache cache, int offset, int end) throws BadLocationException, CoreException { IStructuredDocumentRegion sRegion = sDoc.getRegionAtCharacterOffset(offset); if (sRegion == null) { return EMPTY; } ITextRegion tRegion = sRegion.getRegionAtCharacterOffset(offset); ITextRegionCollection container = sRegion; if (tRegion instanceof ITextRegionContainer) { container = (ITextRegionContainer) tRegion; tRegion = container.getRegionAtCharacterOffset(offset); } if (tRegion != null && tRegion.getType() == PHPRegionContext.PHP_CONTENT) { IPHPScriptRegion phpScriptRegion = (IPHPScriptRegion) tRegion; tRegion = phpScriptRegion.getPHPToken(offset - container.getStartOffset() - phpScriptRegion.getStart()); // Determine element name: int elementStart = container.getStartOffset() + phpScriptRegion.getStart() + tRegion.getStart(); TextSequence statement = PHPTextSequenceUtilities.getStatement(elementStart + tRegion.getLength(), sRegion, true); if (statement.length() == 0) { return EMPTY; } int endPosition = PHPTextSequenceUtilities.readBackwardSpaces(statement, statement.length()); int startPosition = PHPTextSequenceUtilities.readIdentifierStartIndex(phpVersion, statement, endPosition, true); String elementName = startPosition < 0 ? "" //$NON-NLS-1$ : statement.subSequence(startPosition, endPosition).toString(); // Determine previous word: int prevWordEnd = PHPTextSequenceUtilities.readBackwardSpaces(statement, startPosition); int prevWordStart = PHPTextSequenceUtilities.readIdentifierStartIndex(phpVersion, statement, prevWordEnd, false); String prevWord = prevWordStart < 0 ? "" //$NON-NLS-1$ : statement.subSequence(prevWordStart, prevWordEnd).toString(); // Determine next word: ITextRegion nextRegion = tRegion; do { nextRegion = phpScriptRegion.getPHPToken(nextRegion.getEnd()); if (!PHPPartitionTypes.isPHPCommentState(nextRegion.getType()) && nextRegion.getType() != PHPRegionTypes.WHITESPACE) { break; } } while (nextRegion.getEnd() < phpScriptRegion.getLength()); String nextWord = sDoc.get(container.getStartOffset() + phpScriptRegion.getStart() + nextRegion.getStart(), nextRegion.getTextLength()); if (elementName.isEmpty()) { return EMPTY; } if (tRegion.getType() == PHPRegionTypes.PHP_ENCAPSED_VARIABLE) { // Handle the case of variables defined in back-quoted // strings, double-quoted strings or heredoc sections like // "${a}" or "${a[0]}" elementName = "$" + elementName; //$NON-NLS-1$ } IType containerType = PHPModelUtils.getCurrentType(sourceModule, offset); if (containerType == null) { containerType = PHPModelUtils.getCurrentNamespace(sourceModule, offset); } // If we are in function declaration: if (FUNCTION.equalsIgnoreCase(prevWord)) { if (containerType != null) { return PHPModelUtils.getTypeMethod(containerType, elementName, true); } return getFunction(sourceModule, elementName); } // If we are in class declaration: if (CLASS.equalsIgnoreCase(prevWord) || INTERFACE.equalsIgnoreCase(prevWord)) { if (containerType != null) { if (containerType.getElementName().equalsIgnoreCase(elementName)) { containerType = PHPModelUtils.getCurrentNamespace(sourceModule, offset); } if (containerType != null) { return PHPModelUtils.getTypeType(containerType, elementName, true); } } return getClass(sourceModule, elementName); } // Class instantiation: if (NEW.equalsIgnoreCase(prevWord)) { return getConstructorsIfAny( extractClasses(PHPModelUtils.getTypes(elementName, sourceModule, offset, cache, null))); } // Handle extends and implements: // Check that the statement suites the condition. If // class or interface keywords don't appear in the // beginning of the statement or they are alone there. boolean isClassDeclaration = false; if (statement.length() > 6 && (CLASS.equals(statement.subSequence(0, 5).toString()) && (isClassDeclaration = true) || statement.length() > 10 && INTERFACE.equals(statement.subSequence(0, 9).toString()))) { IModelElement[] generalizationTypes = getGeneralizationTypes(sourceModule, isClassDeclaration, prevWord, elementName, offset); if (generalizationTypes != null) { return generalizationTypes; } // Multiple extensions and implementations: int listStartPosition = PHPTextSequenceUtilities.readIdentifierListStartIndex(statement, endPosition); // Determine pre-list word: int preListWordEnd = PHPTextSequenceUtilities.readBackwardSpaces(statement, listStartPosition); int preListWordStart = PHPTextSequenceUtilities.readIdentifierStartIndex(statement, preListWordEnd, false); String preListWord = preListWordStart < 0 ? "" //$NON-NLS-1$ : statement.subSequence(preListWordStart, preListWordEnd).toString(); generalizationTypes = getGeneralizationTypes(sourceModule, isClassDeclaration, preListWord, elementName, offset); if (generalizationTypes != null) { return generalizationTypes; } } // Previous trigger: String trigger = null; if (startPosition > 2) { trigger = statement.subSequence(startPosition - 2, startPosition).toString(); } // https://bugs.eclipse.org/bugs/show_bug.cgi?id=471764 // use first word of statement, not "prevWord" int firstWordEnd = PHPTextSequenceUtilities.readForwardUntilSpaces(statement, 0); String firstWord = statement.subSequence(0, firstWordEnd).toString(); // If this is variable: if (elementName.charAt(0) == '$' && !PAAMAYIM_NEKUDOTAIM.equals(trigger)) { // Don't show escaped variables within PHP string: if (PHPPartitionTypes.isPHPQuotesState(tRegion.getType())) { try { char charBefore = sDoc.get(elementStart - 2, 1).charAt(0); if (charBefore == NamespaceReference.NAMESPACE_SEPARATOR) { return EMPTY; } } catch (BadLocationException e) { PHPCorePlugin.log(e); } } // If we are in var definition: if (containerType != null) { if (VAR.equalsIgnoreCase(firstWord) || PRIVATE.equalsIgnoreCase(firstWord) || STATIC.equalsIgnoreCase(firstWord) || PUBLIC.equalsIgnoreCase(firstWord) || PROTECTED.equalsIgnoreCase(firstWord)) { return PHPModelUtils.getTypeField(containerType, elementName, true); } if (THIS.equalsIgnoreCase(elementName)) { return new IModelElement[] { containerType }; } } return getGlobalOrMethodFields(sourceModule, offset, elementName); } // If we are at class constant definition: if (containerType != null) { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=509124 // Use "prevWord" since class constants can have visibility // keywords with PHP >= 7.1 if (CONST.equalsIgnoreCase(prevWord)) { return PHPModelUtils.getTypeField(containerType, elementName, true); } } // We are at class trigger: if (PAAMAYIM_NEKUDOTAIM.equals(nextWord)) { return PHPModelUtils.getTypes(elementName, sourceModule, offset, cache, null); } if (NamespaceReference.NAMESPACE_DELIMITER.equals(nextWord)) { IDLTKSearchScope scope = SearchEngine.createSearchScope(sourceModule.getScriptProject()); return PHPModelAccess.getDefault().findNamespaces(null, elementName, MatchRule.EXACT, 0, 0, scope, null); } IType[] types = CodeAssistUtils.getTypesFor(sourceModule, statement, startPosition, offset); // Is it function or method: if (OPEN_BRACE.equals(nextWord) || PHPPartitionTypes.isPHPDocState(tRegion.getType())) { if (types != null && types.length > 0) { List<IMethod> methods = new LinkedList<IMethod>(); for (IType t : types) { methods.addAll(Arrays.asList(PHPModelUtils.getTypeHierarchyMethod(t, cache.getSuperTypeHierarchy(t, null), elementName, true, null))); } return methods.toArray(new IMethod[methods.size()]); } return PHPModelUtils.getFunctions(elementName, sourceModule, offset, cache, null); } if ((INSTEADOF.equals(nextWord) || AS.equals(nextWord)) && (!PAAMAYIM_NEKUDOTAIM.equals(trigger) && !OBJECT_OPERATOR.equals(trigger))) { if (types != null && types.length > 0) { List<IMethod> methods = new LinkedList<IMethod>(); for (IType t : types) { methods.addAll(Arrays.asList(PHPModelUtils.getTypeHierarchyMethod(t, cache.getSuperTypeHierarchy(t, null), elementName, true, null))); } return methods.toArray(new IMethod[methods.size()]); } } if (types != null && types.length > 0) { // Check whether this is a class constant: if (startPosition > 0) { if (PAAMAYIM_NEKUDOTAIM.equals(trigger) && elementName.charAt(0) != '$') { List<IModelElement> fields = new LinkedList<IModelElement>(); for (IType t : types) { IField[] typeFields = PHPModelUtils.getTypeField(t, elementName, true); for (IField currentField : typeFields) { fields.add(currentField); } } return fields.toArray(new IModelElement[fields.size()]); } } // What can it be? Only class variables: // Set<IModelElement> fields = new // TreeSet<IModelElement>( // new SourceFieldComparator()); final List<IField> fields = new ArrayList<IField>(); for (IType t : types) { fields.addAll(Arrays.asList( getTypeHierarchyField(t, cache.getSuperTypeHierarchy(t, null), elementName, true, null))); } return fields.toArray(new IModelElement[fields.size()]); } // This can be only global constant, if we've reached // here: IField[] fields = PHPModelUtils.getFields(elementName, sourceModule, offset, cache, null); if (fields != null && fields.length > 0) { return fields; } ModuleDeclaration parsedUnit = SourceParserUtil.getModuleDeclaration(sourceModule, null); fields = findFieldAliases(elementName, sourceModule, parsedUnit, containerType, offset); if (fields != null && fields.length > 0) { return fields; } // Return class if nothing else found. return PHPModelUtils.getTypes(elementName, sourceModule, offset, cache, null); } return EMPTY; } private IModelElement[] processPHPCallExpression(PHPCallExpression callExpression, ISourceModule sourceModule, ModuleDeclaration parsedUnit, int offset, IContext context, IModelAccessCache cache) throws ModelException { IDLTKSearchScope scope = SearchEngine.createSearchScope(sourceModule.getScriptProject()); if (callExpression.getReceiver() != null) { IEvaluatedType receiverType = PHPTypeInferenceUtils.resolveExpression(sourceModule, parsedUnit, context, callExpression.getReceiver()); // (new class1())->avc2()[1][1]->avc1() if (receiverType != null) { IModelElement[] elements = null; if ((receiverType instanceof PHPClassType) && ((PHPClassType) receiverType).isGlobal()) { elements = PHPModelAccess.getDefault().findTypes(receiverType.getTypeName(), MatchRule.EXACT, 0, 0, scope, null); LinkedList<IModelElement> result = new LinkedList<IModelElement>(); for (IModelElement element : elements) { IModelElement parent = element.getParent(); while (parent.getParent() instanceof IType) { parent = parent.getParent(); } if ((parent instanceof IType) && PHPFlags.isNamespace(((IType) parent).getFlags())) { // Do nothing } else { result.add(element); } } elements = result.toArray(new IType[result.size()]); } else { elements = PHPTypeInferenceUtils.getModelElements(receiverType, (ISourceModuleContext) context, offset); } if (elements == null) { return EMPTY; } List<IModelElement> methods = new LinkedList<IModelElement>(); for (IModelElement element : elements) { if (element instanceof IType) { IType type = (IType) element; try { ITypeHierarchy hierarchy = cache.getSuperTypeHierarchy(type, null); IMethod[] method = PHPModelUtils.getFirstTypeHierarchyMethod(type, hierarchy, callExpression.getName(), true, null); methods.addAll(Arrays.asList(method)); } catch (CoreException e) { PHPCorePlugin.log(e); } } } return methods.toArray(new IModelElement[methods.size()]); } } else { SimpleReference callName = callExpression.getCallName(); String methodName = callName instanceof FullyQualifiedReference ? ((FullyQualifiedReference) callName).getFullyQualifiedName() : callName.getName(); IMember[] members = PHPModelUtils.getFunctions(methodName, sourceModule, offset, cache, null); if (members.length == 0) { final IType currentNamespace = PHPModelUtils.getCurrentNamespace(sourceModule, callExpression.sourceStart()); Map<String, UsePart> useParts = PHPModelUtils.getAliasToNSMap(methodName, parsedUnit, offset, currentNamespace, true); if (useParts.containsKey(methodName)) { String fullName = useParts.get(methodName).getNamespace().getFullyQualifiedName(); fullName = NamespaceReference.NAMESPACE_SEPARATOR + fullName; members = PHPModelUtils.getFunctions(fullName, sourceModule, offset, null, null); } } return members; } return EMPTY; } private IField[] findFieldAliases(String fieldName, ISourceModule sourceModule, ModuleDeclaration parsedUnit, IType currentNamespace, int offset) throws ModelException { IDLTKSearchScope scope = SearchEngine.createSearchScope(sourceModule.getScriptProject()); Map<String, UsePart> useParts = PHPModelUtils.getAliasToNSMap(fieldName, parsedUnit, offset, currentNamespace, true); if (useParts.containsKey(fieldName)) { String fullName = useParts.get(fieldName).getNamespace().getFullyQualifiedName(); IField[] elements = PHPModelAccess.getDefault().findFields(fullName, MatchRule.EXACT, 0, 0, scope, null); if (elements != null) { List<IField> result = new ArrayList<IField>(); for (IField field : elements) { result.add(new AliasField((ModelElement) field, field.getFullyQualifiedName(), fieldName)); } return result.toArray(new IField[0]); } } return null; } public static IField[] getTypeHierarchyField(IType type, ITypeHierarchy hierarchy, String prefix, boolean exactName, IProgressMonitor monitor) throws CoreException { if (prefix == null) { throw new NullPointerException(); } final List<IField> fields = new ArrayList<IField>(); fields.addAll(Arrays.asList(PHPModelUtils.getTypeField(type, prefix, exactName))); if (type.getSuperClasses() != null && type.getSuperClasses().length > 0) { Set<IModelElement> fieldSet = new TreeSet<IModelElement>(new SourceFieldComparator()); fieldSet.addAll(Arrays .asList(PHPModelUtils.getSuperTypeHierarchyField(type, hierarchy, prefix, exactName, monitor))); IField[] temp = fieldSet.toArray(new IField[fieldSet.size()]); for (IField field : temp) { fields.add(field); } } return fields.toArray(new IField[fields.size()]); } private static IModelElement[] getGeneralizationTypes(ISourceModule sourceModule, boolean isClassDeclaration, String generalization, String elementName, int offset) throws ModelException { if (EXTENDS.equalsIgnoreCase(generalization)) { if (isClassDeclaration) { return extractClasses(PHPModelUtils.getTypes(elementName, sourceModule, offset, null, null)); } return extractInterfaces(PHPModelUtils.getTypes(elementName, sourceModule, offset, null, null)); } if (IMPLEMENTS.equalsIgnoreCase(generalization)) { return extractInterfaces(PHPModelUtils.getTypes(elementName, sourceModule, offset, null, null)); } return null; } private static IModelElement[] getFunction(ISourceModule sourceModule, String elementName) throws ModelException { IMethod[] methods = ((AbstractSourceModule) sourceModule).getMethods(); for (IMethod method : methods) { if (method.getElementName().equalsIgnoreCase(elementName)) { return new IModelElement[] { method }; } } return EMPTY; } private static IModelElement[] getClass(ISourceModule sourceModule, String elementName) throws ModelException { IType[] types = sourceModule.getTypes(); for (IType type : types) { if (type.getElementName().equalsIgnoreCase(elementName)) { return new IModelElement[] { type }; } } return EMPTY; } private static IType[] extractInterfaces(IType[] types) { List<IType> result = new ArrayList<IType>(types.length); for (IType type : types) { try { if (PHPFlags.isInterface(type.getFlags())) { result.add(type); } } catch (ModelException e) { PHPCorePlugin.log(e); } } return (IType[]) result.toArray(new IType[result.size()]); } private static IType[] extractClasses(IType[] types) { List<IType> result = new ArrayList<IType>(types.length); for (IType type : types) { try { if (PHPFlags.isClass(type.getFlags())) { result.add(type); } } catch (ModelException e) { PHPCorePlugin.log(e); } } return (IType[]) result.toArray(new IType[result.size()]); } private static IModelElement[] getConstructorsIfAny(IType[] types) throws ModelException { List<IModelElement> result = new LinkedList<IModelElement>(); for (IType type : types) { boolean hasConstructor = false; for (IMethod method : type.getMethods()) { if (method.isConstructor()) { result.add(method); hasConstructor = true; break; } } if (!hasConstructor) { result.add(type); } } return (IModelElement[]) result.toArray(new IModelElement[result.size()]); } /** * Return workspace or method fields depending on current position: whether * we are inside method or in global scope. * * @param sourceModule * @param offset * @param prefix * @param mask * @return * @throws ModelException */ private static IModelElement[] getGlobalOrMethodFields(ISourceModule sourceModule, int offset, String prefix) throws ModelException { try { IModelElement enclosingElement = sourceModule.getElementAt(offset); if (enclosingElement instanceof IField) { enclosingElement = enclosingElement.getParent(); } if (enclosingElement instanceof IMethod) { IMethod method = (IMethod) enclosingElement; return PHPModelUtils.getMethodFields(method, prefix, true, null); } } catch (ModelException e) { PHPCorePlugin.log(e); } return PHPModelUtils.getFields(prefix, sourceModule, offset, null, null); } private static class SourceFieldComparator implements Comparator<IModelElement> { public int compare(IModelElement o1, IModelElement o2) { try { SourceRefElement e1 = (SourceRefElement) o1; SourceRefElement e2 = (SourceRefElement) o2; IType type1 = (IType) e1.getAncestor(IModelElement.TYPE); IType type2 = (IType) e2.getAncestor(IModelElement.TYPE); if (type1 != null && type2 != null && type1 != type2) { return -1; } if (e1.getSourceModule() == e2.getSourceModule()) { ISourceRange r1 = e1.getSourceRange(); ISourceRange r2 = e2.getSourceRange(); return (int) Math.signum(r1.getOffset() - r2.getOffset()); } else { return -1; } } catch (ModelException e) { PHPCorePlugin.log(e); } return 0; } } }