diff --git a/CHANGELOG.md b/CHANGELOG.md index 3409b833d..0e95ad093 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,12 +10,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - **API:** `TypeParameterNode::getTypeParameters` method. +- **API:** `InterfaceTypeNode::getGuidExpression` method. +- **API:** `AttributeNode::getExpression` method. + +### Changed + +- Issue locations no longer span the entire routine declaration in `RoutineName`. + +### Deprecated + +- **API:** `InterfaceGuidNode` node type. +- **API:** `InterfaceTypeNode::getGuid` method, use `getGuidExpression` instead. +- **API:** `DelphiTokenType.GUID`, as the associated `InterfaceGuidNode` is no longer parsed. ### Fixed - Name resolution failures on generic routine invocations where later type parameters are constrained by earlier type parameters. +- Type resolution failures on property attribute lists. +- Type resolution failures on implementation-local routine attribute lists. - Type resolution failures on `as` casts where the type is returned from a routine invocation. - Inaccurate type resolution when calling a constructor on a class reference type. +- Grammar ambiguity causing attributes to be misinterpreted as interface GUIDs. ## [1.16.0] - 2025-05-09 diff --git a/delphi-checks/src/main/java/au/com/integradev/delphi/checks/ForbiddenRoutineCheck.java b/delphi-checks/src/main/java/au/com/integradev/delphi/checks/ForbiddenRoutineCheck.java index d08aaebca..207800a19 100644 --- a/delphi-checks/src/main/java/au/com/integradev/delphi/checks/ForbiddenRoutineCheck.java +++ b/delphi-checks/src/main/java/au/com/integradev/delphi/checks/ForbiddenRoutineCheck.java @@ -20,6 +20,7 @@ import com.google.common.base.Splitter; import com.google.common.collect.ImmutableSortedSet; +import java.util.Objects; import java.util.Set; import org.sonar.check.Rule; import org.sonar.check.RuleProperty; @@ -76,7 +77,8 @@ public DelphiCheckContext visit(AttributeNode attribute, DelphiCheckContext cont NameDeclaration declaration = occurrence.getNameDeclaration(); if (declaration instanceof RoutineNameDeclaration && routinesSet.contains(((RoutineNameDeclaration) declaration).fullyQualifiedName())) { - reportIssue(context, attribute.getNameReference().getIdentifier(), message); + NameReferenceNode reference = Objects.requireNonNull(attribute.getNameReference()); + reportIssue(context, reference.getIdentifier(), message); } } return super.visit(attribute, context); diff --git a/delphi-checks/src/main/java/au/com/integradev/delphi/checks/InterfaceGuidCheck.java b/delphi-checks/src/main/java/au/com/integradev/delphi/checks/InterfaceGuidCheck.java index cec3d2e68..d58a80dcf 100644 --- a/delphi-checks/src/main/java/au/com/integradev/delphi/checks/InterfaceGuidCheck.java +++ b/delphi-checks/src/main/java/au/com/integradev/delphi/checks/InterfaceGuidCheck.java @@ -34,7 +34,7 @@ public class InterfaceGuidCheck extends DelphiCheck { public DelphiCheckContext visit(TypeDeclarationNode typeDeclaration, DelphiCheckContext context) { if (typeDeclaration.isInterface()) { InterfaceTypeNode interfaceType = (InterfaceTypeNode) typeDeclaration.getTypeNode(); - if (!interfaceType.isForwardDeclaration() && interfaceType.getGuid() == null) { + if (!interfaceType.isForwardDeclaration() && interfaceType.getGuidExpression() == null) { reportIssue(context, typeDeclaration.getTypeNameNode(), MESSAGE); } } diff --git a/delphi-checks/src/main/java/au/com/integradev/delphi/checks/MixedNamesCheck.java b/delphi-checks/src/main/java/au/com/integradev/delphi/checks/MixedNamesCheck.java index 89337a653..c5da20c1d 100644 --- a/delphi-checks/src/main/java/au/com/integradev/delphi/checks/MixedNamesCheck.java +++ b/delphi-checks/src/main/java/au/com/integradev/delphi/checks/MixedNamesCheck.java @@ -118,7 +118,12 @@ public DelphiCheckContext visit(UnitImportNode importNode, DelphiCheckContext co @Override public DelphiCheckContext visit(AttributeNode attributeNode, DelphiCheckContext context) { - List nameReferences = attributeNode.getNameReference().flatten(); + NameReferenceNode reference = attributeNode.getNameReference(); + if (reference == null) { + return super.visit(attributeNode, context); + } + + List nameReferences = reference.flatten(); for (int i = 0; i + 1 < nameReferences.size(); i++) { context = super.visit(nameReferences.get(i), context); diff --git a/delphi-checks/src/main/java/au/com/integradev/delphi/checks/RoutineNameCheck.java b/delphi-checks/src/main/java/au/com/integradev/delphi/checks/RoutineNameCheck.java index d26db68db..0c9e899df 100644 --- a/delphi-checks/src/main/java/au/com/integradev/delphi/checks/RoutineNameCheck.java +++ b/delphi-checks/src/main/java/au/com/integradev/delphi/checks/RoutineNameCheck.java @@ -38,7 +38,7 @@ public class RoutineNameCheck extends DelphiCheck { @Override public DelphiCheckContext visit(RoutineDeclarationNode routine, DelphiCheckContext context) { if (isViolation(routine) && !isExcluded(routine)) { - reportIssue(context, routine, MESSAGE); + reportIssue(context, routine.getRoutineNameNode(), MESSAGE); } return super.visit(routine, context); } diff --git a/delphi-frontend/src/main/antlr3/au/com/integradev/delphi/antlr/Delphi.g b/delphi-frontend/src/main/antlr3/au/com/integradev/delphi/antlr/Delphi.g index 40feca4bd..e7c411de0 100644 --- a/delphi-frontend/src/main/antlr3/au/com/integradev/delphi/antlr/Delphi.g +++ b/delphi-frontend/src/main/antlr3/au/com/integradev/delphi/antlr/Delphi.g @@ -11,6 +11,7 @@ tokens { // Deprecated tokens //---------------------------------------------------------------------------- AMPERSAND__deprecated; + TkGuid__deprecated; //---------------------------------------------------------------------------- // Imaginary tokens @@ -25,7 +26,6 @@ tokens { TkRecordVariantItem; TkRecordVariantTag; TkRecordExpressionItem; - TkGuid; TkClassParents; TkLocalDeclarations; TkCaseItem; @@ -708,9 +708,7 @@ fieldDecl : attributeList? nameDeclarationList ':' varType po ; classHelperType : CLASS^ HELPER classParent? FOR typeReference visibilitySection* END ; -interfaceType : (INTERFACE^ | DISPINTERFACE^) classParent? (interfaceGuid? interfaceItems? END)? - ; -interfaceGuid : lbrack expression rbrack -> ^(TkGuid expression) +interfaceType : (INTERFACE^ | DISPINTERFACE^) classParent? ((interfaceItems | attributeList?) END)? ; interfaceItems : interfaceItem+ -> ^(TkVisibilitySection interfaceItem+) ; @@ -913,8 +911,8 @@ attributeList : attributeGroup+ attributeGroup : lbrack (attribute ','?)+ rbrack -> ^(TkAttributeGroup attribute+) ; -attribute : (ASSEMBLY ':')? nameReference argumentList? (':' nameReference argumentList?)* - -> ^(TkAttribute ASSEMBLY? nameReference argumentList? (':' nameReference argumentList?)*) +attribute : (ASSEMBLY ':')? expression (':' expression)* + -> ^(TkAttribute ASSEMBLY? expression (':' expression)*) ; //---------------------------------------------------------------------------- diff --git a/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/node/AttributeGroupNodeImpl.java b/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/node/AttributeGroupNodeImpl.java index 0ea7025bc..ba808837d 100644 --- a/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/node/AttributeGroupNodeImpl.java +++ b/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/node/AttributeGroupNodeImpl.java @@ -47,7 +47,7 @@ public List getAttributes() { @Override public String getImage() { return getAttributes().stream() - .map(attribute -> attribute.getNameReference().fullyQualifiedName()) + .map(AttributeNode::getImage) .collect(Collectors.joining(", ", "[", "]")); } } diff --git a/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/node/AttributeListNodeImpl.java b/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/node/AttributeListNodeImpl.java index 4d6e4f3e5..811bddd4e 100644 --- a/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/node/AttributeListNodeImpl.java +++ b/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/node/AttributeListNodeImpl.java @@ -25,10 +25,8 @@ import org.sonar.plugins.communitydelphi.api.ast.AttributeGroupNode; import org.sonar.plugins.communitydelphi.api.ast.AttributeListNode; import org.sonar.plugins.communitydelphi.api.ast.AttributeNode; -import org.sonar.plugins.communitydelphi.api.symbol.declaration.NameDeclaration; -import org.sonar.plugins.communitydelphi.api.symbol.declaration.TypeNameDeclaration; +import org.sonar.plugins.communitydelphi.api.ast.ExpressionNode; import org.sonar.plugins.communitydelphi.api.type.Type; -import org.sonar.plugins.communitydelphi.api.type.TypeFactory; public final class AttributeListNodeImpl extends DelphiNodeImpl implements AttributeListNode { public AttributeListNodeImpl(Token token) { @@ -57,20 +55,9 @@ public List getAttributes() { @Override public List getAttributeTypes() { return getAttributes().stream() - .map(AttributeNode::getTypeNameOccurrence) - .map( - occurrence -> { - if (occurrence == null) { - return TypeFactory.unknownType(); - } - - NameDeclaration declaration = occurrence.getNameDeclaration(); - if (!(declaration instanceof TypeNameDeclaration)) { - return TypeFactory.unknownType(); - } - - return ((TypeNameDeclaration) declaration).getType(); - }) + .map(AttributeNode::getExpression) + .map(ExpressionNode::getType) + .filter(type -> type.isUnknown() || type.isDescendantOf("System.TCustomAttribute")) .collect(Collectors.toList()); } } diff --git a/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/node/AttributeNodeImpl.java b/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/node/AttributeNodeImpl.java index 820c0e8d9..e751265a8 100644 --- a/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/node/AttributeNodeImpl.java +++ b/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/node/AttributeNodeImpl.java @@ -20,15 +20,25 @@ import au.com.integradev.delphi.antlr.ast.visitors.DelphiParserVisitor; import au.com.integradev.delphi.symbol.occurrence.AttributeNameOccurrenceImpl; +import com.google.common.base.Suppliers; +import java.util.function.Supplier; +import javax.annotation.Nullable; import org.antlr.runtime.Token; import org.sonar.plugins.communitydelphi.api.ast.ArgumentListNode; import org.sonar.plugins.communitydelphi.api.ast.AttributeNode; import org.sonar.plugins.communitydelphi.api.ast.DelphiNode; +import org.sonar.plugins.communitydelphi.api.ast.ExpressionNode; import org.sonar.plugins.communitydelphi.api.ast.NameReferenceNode; +import org.sonar.plugins.communitydelphi.api.ast.PrimaryExpressionNode; import org.sonar.plugins.communitydelphi.api.symbol.NameOccurrence; import org.sonar.plugins.communitydelphi.api.token.DelphiTokenType; public final class AttributeNodeImpl extends DelphiNodeImpl implements AttributeNode { + private final Supplier nameReferenceSupplier = + Suppliers.memoize(this::findNameReference); + private final Supplier argumentListSupplier = + Suppliers.memoize(this::findArgumentList); + public AttributeNodeImpl(Token token) { super(token); } @@ -42,14 +52,32 @@ public boolean isAssembly() { return getChild(0).getTokenType() == DelphiTokenType.ASSEMBLY; } + @Override + public ExpressionNode getExpression() { + return (ExpressionNode) getChild(isAssembly() ? 1 : 0); + } + + @Nullable @Override public NameReferenceNode getNameReference() { - return (NameReferenceNode) getChild(isAssembly() ? 1 : 0); + return nameReferenceSupplier.get(); + } + + @Override + public ArgumentListNode getArgumentList() { + return argumentListSupplier.get(); } @Override public NameOccurrence getTypeNameOccurrence() { - return getNameReference().getLastName().getNameOccurrence(); + NameReferenceNode nameReference = getNameReference(); + if (nameReference != null) { + NameOccurrence occurrence = nameReference.getLastName().getNameOccurrence(); + if (occurrence instanceof AttributeNameOccurrenceImpl) { + return occurrence; + } + } + return null; } @Override @@ -62,21 +90,45 @@ public NameOccurrence getConstructorNameOccurrence() { } @Override - public ArgumentListNode getArgumentList() { - DelphiNode node = getChild(isAssembly() ? 2 : 1); - if (node instanceof ArgumentListNode) { - return (ArgumentListNode) node; + public String getImage() { + DelphiNode imageNode = getNameReference(); + if (imageNode == null) { + imageNode = getExpression(); } - return null; - } - @Override - public String getImage() { - return "[" + getNameReference().fullyQualifiedName() + "]"; + String image = imageNode.getImage(); + if (isAssembly()) { + image = "assembly " + image; + } + + return image; } @Override public T accept(DelphiParserVisitor visitor, T data) { return visitor.visit(this, data); } + + private NameReferenceNode findNameReference() { + ExpressionNode expression = getExpression(); + if (expression instanceof PrimaryExpressionNode) { + DelphiNode child = expression.getChild(0); + if (child instanceof NameReferenceNode) { + return (NameReferenceNode) child; + } + } + return null; + } + + private ArgumentListNode findArgumentList() { + NameReferenceNode nameReference = getNameReference(); + if (nameReference != null) { + int index = nameReference.getChildIndex() + 1; + DelphiNode next = nameReference.getParent().getChild(index); + if (next instanceof ArgumentListNode) { + return (ArgumentListNode) next; + } + } + return null; + } } diff --git a/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/node/InterfaceGuidNodeImpl.java b/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/node/InterfaceGuidNodeImpl.java index 0b334d21b..db14ee353 100644 --- a/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/node/InterfaceGuidNodeImpl.java +++ b/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/node/InterfaceGuidNodeImpl.java @@ -22,6 +22,7 @@ import org.antlr.runtime.Token; import org.sonar.plugins.communitydelphi.api.ast.InterfaceGuidNode; +@SuppressWarnings("removal") public final class InterfaceGuidNodeImpl extends DelphiNodeImpl implements InterfaceGuidNode { public InterfaceGuidNodeImpl(Token token) { super(token); diff --git a/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/node/InterfaceTypeNodeImpl.java b/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/node/InterfaceTypeNodeImpl.java index 30f35cdcb..a28beeb66 100644 --- a/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/node/InterfaceTypeNodeImpl.java +++ b/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/node/InterfaceTypeNodeImpl.java @@ -19,11 +19,25 @@ package au.com.integradev.delphi.antlr.ast.node; import au.com.integradev.delphi.antlr.ast.visitors.DelphiParserVisitor; +import com.google.common.base.Suppliers; +import com.google.common.collect.Iterables; +import java.util.function.Supplier; +import javax.annotation.Nullable; import org.antlr.runtime.Token; +import org.sonar.plugins.communitydelphi.api.ast.AttributeGroupNode; +import org.sonar.plugins.communitydelphi.api.ast.AttributeListNode; +import org.sonar.plugins.communitydelphi.api.ast.AttributeNode; +import org.sonar.plugins.communitydelphi.api.ast.DelphiNode; +import org.sonar.plugins.communitydelphi.api.ast.ExpressionNode; import org.sonar.plugins.communitydelphi.api.ast.InterfaceGuidNode; import org.sonar.plugins.communitydelphi.api.ast.InterfaceTypeNode; +import org.sonar.plugins.communitydelphi.api.ast.PropertyNode; +import org.sonar.plugins.communitydelphi.api.ast.RoutineDeclarationNode; +import org.sonar.plugins.communitydelphi.api.ast.VisibilitySectionNode; public final class InterfaceTypeNodeImpl extends StructTypeNodeImpl implements InterfaceTypeNode { + private final Supplier guid = Suppliers.memoize(this::findGuidExpression); + public InterfaceTypeNodeImpl(Token token) { super(token); } @@ -38,8 +52,54 @@ public boolean isForwardDeclaration() { return getChildren().isEmpty(); } + @SuppressWarnings("removal") @Override public InterfaceGuidNode getGuid() { - return getFirstChildOfType(InterfaceGuidNode.class); + return null; + } + + @Nullable + @Override + public ExpressionNode getGuidExpression() { + return guid.get(); + } + + private ExpressionNode findGuidExpression() { + AttributeListNode attributeList = findFirstAttributeList(); + if (attributeList != null) { + AttributeGroupNode attributeGroup = attributeList.getAttributeGroups().get(0); + AttributeNode attribute = Iterables.getLast(attributeGroup.getAttributes()); + ExpressionNode expression = attribute.getExpression(); + if (expression.getType().isString()) { + return expression; + } + } + return null; + } + + private AttributeListNode findFirstAttributeList() { + for (DelphiNode child : getChildren()) { + if (child instanceof AttributeListNode) { + return (AttributeListNode) child; + } + + if (child instanceof VisibilitySectionNode) { + return findFirstAttributeList((VisibilitySectionNode) child); + } + } + return null; + } + + private AttributeListNode findFirstAttributeList(VisibilitySectionNode visibilitySection) { + for (DelphiNode child : visibilitySection.getChildren()) { + if (child instanceof RoutineDeclarationNode) { + return ((RoutineDeclarationNode) child).getRoutineHeading().getAttributeList(); + } + + if (child instanceof PropertyNode) { + return ((PropertyNode) child).getAttributeList(); + } + } + return null; } } diff --git a/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/visitors/DelphiParserVisitor.java b/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/visitors/DelphiParserVisitor.java index 1193ef680..81ebb7af7 100644 --- a/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/visitors/DelphiParserVisitor.java +++ b/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/visitors/DelphiParserVisitor.java @@ -319,6 +319,7 @@ default T visit(InitializationSectionNode node, T data) { return visit((DelphiNode) node, data); } + @SuppressWarnings("removal") default T visit(InterfaceGuidNode node, T data) { return visit((DelphiNode) node, data); } diff --git a/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/visitors/SymbolTableVisitor.java b/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/visitors/SymbolTableVisitor.java index 74a4dcbbf..1cacad269 100644 --- a/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/visitors/SymbolTableVisitor.java +++ b/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/visitors/SymbolTableVisitor.java @@ -63,6 +63,7 @@ import javax.annotation.Nullable; import org.sonar.plugins.communitydelphi.api.ast.AnonymousMethodNode; import org.sonar.plugins.communitydelphi.api.ast.ArrayConstructorNode; +import org.sonar.plugins.communitydelphi.api.ast.AttributeListNode; import org.sonar.plugins.communitydelphi.api.ast.AttributeNode; import org.sonar.plugins.communitydelphi.api.ast.CaseItemStatementNode; import org.sonar.plugins.communitydelphi.api.ast.CaseStatementNode; @@ -111,6 +112,7 @@ import org.sonar.plugins.communitydelphi.api.ast.RepeatStatementNode; import org.sonar.plugins.communitydelphi.api.ast.RoutineBodyNode; import org.sonar.plugins.communitydelphi.api.ast.RoutineDeclarationNode; +import org.sonar.plugins.communitydelphi.api.ast.RoutineHeadingNode; import org.sonar.plugins.communitydelphi.api.ast.RoutineImplementationNode; import org.sonar.plugins.communitydelphi.api.ast.RoutineNameNode; import org.sonar.plugins.communitydelphi.api.ast.RoutineNode; @@ -825,6 +827,23 @@ public Data visit(PrimaryExpressionNode node, Data data) { return data; } + @Override + public Data visit(AttributeListNode node, Data data) { + DelphiNode parent = node.getParent(); + + if (parent instanceof PropertyNode) { + // Already resolved by the PropertyNode visit. + return data; + } + + if (parent instanceof RoutineHeadingNode) { + // Already resolved by the RoutineNode visit. + return data; + } + + return DelphiParserVisitor.super.visit(node, data); + } + @Override public Data visit(AttributeNode node, Data data) { data.nameResolutionHelper.resolve(node); diff --git a/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/ExpressionTypeResolver.java b/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/ExpressionTypeResolver.java index 717541c28..8284e4be2 100644 --- a/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/ExpressionTypeResolver.java +++ b/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/ExpressionTypeResolver.java @@ -21,6 +21,7 @@ import static org.sonar.plugins.communitydelphi.api.type.TypeFactory.unknownType; import au.com.integradev.delphi.operator.OperatorInvocableCollector; +import au.com.integradev.delphi.symbol.occurrence.AttributeNameOccurrenceImpl; import au.com.integradev.delphi.type.TypeUtils; import au.com.integradev.delphi.type.intrinsic.IntrinsicReturnType; import com.google.common.collect.Iterables; @@ -316,13 +317,15 @@ private Type handleNameOccurrence(NameOccurrence occurrence) { if (declaration instanceof Typed) { Type type = ((Typed) declaration).getType(); - if (declaration instanceof TypeNameDeclaration - || declaration instanceof TypeParameterNameDeclaration) { - type = typeFactory.classOf(null, type); - } + if (!(occurrence instanceof AttributeNameOccurrenceImpl)) { + if (declaration instanceof TypeNameDeclaration + || declaration instanceof TypeParameterNameDeclaration) { + type = typeFactory.classOf(null, type); + } - if (type.isProcedural() && occurrence.isExplicitInvocation()) { - type = ((ProceduralType) type).returnType(); + if (type.isProcedural() && occurrence.isExplicitInvocation()) { + type = ((ProceduralType) type).returnType(); + } } return type; diff --git a/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolutionHelper.java b/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolutionHelper.java index 6a10db830..7a6a3b4c1 100644 --- a/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolutionHelper.java +++ b/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolutionHelper.java @@ -167,10 +167,21 @@ public void resolve(NameReferenceNode reference) { resolver.addToSymbolTable(); } + private void resolve(@Nullable AttributeListNode attributeList) { + if (attributeList != null) { + attributeList.getAttributes().forEach(this::resolve); + } + } + public void resolve(AttributeNode attribute) { NameResolver resolver = createNameResolver(); resolver.readAttribute(attribute); - resolver.addToSymbolTable(); + + if (resolver.getApproximateType().isDescendantOf("System.TCustomAttribute")) { + resolver.addToSymbolTable(); + } else { + resolve(attribute.getExpression()); + } } public void resolve(PrimaryExpressionNode expression) { @@ -250,6 +261,7 @@ public void resolve(MethodResolutionClauseNode resolutionClause) { } public void resolve(PropertyNode property) { + resolve(property.getAttributeList()); resolve(property.getParameterListNode()); TypeNode type = property.getTypeNode(); @@ -422,6 +434,7 @@ private static boolean isBareInterfaceRoutineReference( } private void resolveRoutine(RoutineNode routine) { + resolve(routine.getRoutineHeading().getAttributeList()); SearchMode previousSearchMode = searchMode; try { searchMode = SearchMode.ROUTINE_HEADING; diff --git a/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolver.java b/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolver.java index 87487698c..45818e7b5 100644 --- a/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolver.java +++ b/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolver.java @@ -564,10 +564,15 @@ private void handleTypeParameterForwardReferences( } void readAttribute(AttributeNode node) { - readNameReference(node.getNameReference(), true); + NameReferenceNode nameReference = node.getNameReference(); + if (nameReference == null) { + return; + } + + readNameReference(nameReference, true); addResolvedDeclaration(); - List references = node.getNameReference().flatten(); + List references = nameReference.flatten(); NameReferenceNode lastReference = references.get(references.size() - 1); var attributeNameOccurrence = (AttributeNameOccurrenceImpl) lastReference.getNameOccurrence(); diff --git a/delphi-frontend/src/main/java/org/sonar/plugins/communitydelphi/api/ast/AttributeNode.java b/delphi-frontend/src/main/java/org/sonar/plugins/communitydelphi/api/ast/AttributeNode.java index b7d3d2fb9..989738f95 100644 --- a/delphi-frontend/src/main/java/org/sonar/plugins/communitydelphi/api/ast/AttributeNode.java +++ b/delphi-frontend/src/main/java/org/sonar/plugins/communitydelphi/api/ast/AttributeNode.java @@ -32,14 +32,17 @@ public interface AttributeNode extends DelphiNode { */ boolean isAssembly(); + ExpressionNode getExpression(); + + @Nullable NameReferenceNode getNameReference(); @Nullable - NameOccurrence getTypeNameOccurrence(); + ArgumentListNode getArgumentList(); @Nullable - NameOccurrence getConstructorNameOccurrence(); + NameOccurrence getTypeNameOccurrence(); @Nullable - ArgumentListNode getArgumentList(); + NameOccurrence getConstructorNameOccurrence(); } diff --git a/delphi-frontend/src/main/java/org/sonar/plugins/communitydelphi/api/ast/InterfaceGuidNode.java b/delphi-frontend/src/main/java/org/sonar/plugins/communitydelphi/api/ast/InterfaceGuidNode.java index ac0ef9eb3..0c9b8dac3 100644 --- a/delphi-frontend/src/main/java/org/sonar/plugins/communitydelphi/api/ast/InterfaceGuidNode.java +++ b/delphi-frontend/src/main/java/org/sonar/plugins/communitydelphi/api/ast/InterfaceGuidNode.java @@ -18,4 +18,5 @@ */ package org.sonar.plugins.communitydelphi.api.ast; +@Deprecated(forRemoval = true) public interface InterfaceGuidNode extends DelphiNode {} diff --git a/delphi-frontend/src/main/java/org/sonar/plugins/communitydelphi/api/ast/InterfaceTypeNode.java b/delphi-frontend/src/main/java/org/sonar/plugins/communitydelphi/api/ast/InterfaceTypeNode.java index 11e86a8a9..e1ede2d1c 100644 --- a/delphi-frontend/src/main/java/org/sonar/plugins/communitydelphi/api/ast/InterfaceTypeNode.java +++ b/delphi-frontend/src/main/java/org/sonar/plugins/communitydelphi/api/ast/InterfaceTypeNode.java @@ -18,8 +18,18 @@ */ package org.sonar.plugins.communitydelphi.api.ast; +import javax.annotation.Nullable; + public interface InterfaceTypeNode extends StructTypeNode { boolean isForwardDeclaration(); + /** + * @deprecated Use {@link InterfaceTypeNode#getGuidExpression} instead. + */ + @SuppressWarnings("removal") + @Deprecated(forRemoval = true) InterfaceGuidNode getGuid(); + + @Nullable + ExpressionNode getGuidExpression(); } diff --git a/delphi-frontend/src/test/java/au/com/integradev/delphi/executor/DelphiSymbolTableExecutorTest.java b/delphi-frontend/src/test/java/au/com/integradev/delphi/executor/DelphiSymbolTableExecutorTest.java index 65eba3dca..dbfeabafa 100644 --- a/delphi-frontend/src/test/java/au/com/integradev/delphi/executor/DelphiSymbolTableExecutorTest.java +++ b/delphi-frontend/src/test/java/au/com/integradev/delphi/executor/DelphiSymbolTableExecutorTest.java @@ -416,6 +416,13 @@ void testLabels() { verifyUsages(29, 6, reference(31, 7), reference(32, 2)); } + @Test + void testGuid() { + execute("Guid.pas"); + verifyUsages(7, 2, reference(11, 5)); + verifyUsages(15, 2, reference(20, 16)); + } + @Test void testBestEffortArguments() { execute("BestEffortArguments.pas"); diff --git a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/Guid.pas b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/Guid.pas new file mode 100644 index 000000000..208817095 --- /dev/null +++ b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/Guid.pas @@ -0,0 +1,27 @@ +unit Guid; + +interface + + +const + Foo = '{4E89BDD0-B67C-4B4D-86E6-2C0D1DC7B237'; + +type + TBar = interface + [Foo] + end; + +type + FooAttribute = class(TCustomAttribute) + // ... + end; + + TBaz = interface + [assembly : Foo, '{4E89BDD0-B67C-4B4D-86E6-2C0D1DC7B237'] + procedure Flarp; + end; + + +implementation + +end. \ No newline at end of file