Skip to content

Commit ac758e0

Browse files
Add Location to FunctionSymbol parameters
1 parent ebbe2bd commit ac758e0

File tree

5 files changed

+72
-20
lines changed

5 files changed

+72
-20
lines changed

python-frontend/src/main/java/org/sonar/plugins/python/api/SubscriptionContext.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ public interface SubscriptionContext {
3030

3131
PythonCheck.PreciseIssue addIssue(Tree element, @Nullable String message);
3232

33+
PythonCheck.PreciseIssue addIssue(LocationInFile location, @Nullable String message);
34+
3335
PythonCheck.PreciseIssue addIssue(Token token, @Nullable String message);
3436

3537
PythonCheck.PreciseIssue addIssue(Token from, Token to, @Nullable String message);

python-frontend/src/main/java/org/sonar/plugins/python/api/symbols/FunctionSymbol.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,7 @@ interface Parameter {
4848
String name();
4949
boolean hasDefaultValue();
5050
boolean isKeywordOnly();
51+
@CheckForNull
52+
LocationInFile location();
5153
}
5254
}

python-frontend/src/main/java/org/sonar/python/SubscriptionVisitor.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import javax.annotation.CheckForNull;
3232
import javax.annotation.Nullable;
3333
import org.sonar.plugins.python.api.IssueLocation;
34+
import org.sonar.plugins.python.api.LocationInFile;
3435
import org.sonar.plugins.python.api.PythonCheck;
3536
import org.sonar.plugins.python.api.PythonFile;
3637
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
@@ -103,6 +104,11 @@ public PythonCheck.PreciseIssue addIssue(Tree element, @Nullable String message)
103104
return addIssue(IssueLocation.preciseLocation(element, message));
104105
}
105106

107+
@Override
108+
public PythonCheck.PreciseIssue addIssue(LocationInFile location, @Nullable String message) {
109+
return addIssue(IssueLocation.preciseLocation(location, message));
110+
}
111+
106112
@Override
107113
public PythonCheck.PreciseIssue addIssue(Token token, @Nullable String message) {
108114
return addIssue(IssueLocation.preciseLocation(token, message));

python-frontend/src/main/java/org/sonar/python/semantic/FunctionSymbolImpl.java

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -55,18 +55,16 @@ public class FunctionSymbolImpl extends SymbolImpl implements FunctionSymbol {
5555
setKind(Kind.FUNCTION);
5656
isInstanceMethod = isInstanceMethod(functionDef);
5757
hasDecorators = !functionDef.decorators().isEmpty();
58+
String fileId = null;
59+
if (!SymbolUtils.isTypeShedFile(pythonFile)) {
60+
Path path = pathOf(pythonFile);
61+
fileId = path != null ? path.toString() : pythonFile.toString();
62+
}
5863
ParameterList parametersList = functionDef.parameters();
5964
if (parametersList != null) {
60-
createParameterNames(parametersList.all());
61-
}
62-
if (SymbolUtils.isTypeShedFile(pythonFile)) {
63-
functionDefinitionLocation = null;
64-
} else {
65-
TokenLocation functionName = new TokenLocation(functionDef.name().firstToken());
66-
Path path = pathOf(pythonFile);
67-
String fileId = path != null ? path.toString() : pythonFile.toString();
68-
functionDefinitionLocation = new LocationInFile(fileId, functionName.startLine(), functionName.startLineOffset(), functionName.endLine(), functionName.endLineOffset());
65+
createParameterNames(parametersList.all(), fileId);
6966
}
67+
functionDefinitionLocation = locationInFile(functionDef.name(), fileId);
7068
}
7169

7270
FunctionSymbolImpl(String name, FunctionSymbol functionSymbol) {
@@ -93,6 +91,16 @@ public FunctionSymbolImpl(String name, @Nullable String fullyQualifiedName, bool
9391
this.isStub = true;
9492
}
9593

94+
@CheckForNull
95+
private static LocationInFile locationInFile(Tree tree, @Nullable String fileId) {
96+
if (fileId == null) {
97+
return null;
98+
}
99+
TokenLocation firstToken = new TokenLocation(tree.firstToken());
100+
TokenLocation lastToken = new TokenLocation(tree.lastToken());
101+
return new LocationInFile(fileId, firstToken.startLine(), firstToken.startLineOffset(), lastToken.endLine(), lastToken.endLineOffset());
102+
}
103+
96104
@Override
97105
FunctionSymbolImpl copyWithoutUsages() {
98106
FunctionSymbolImpl copy = new FunctionSymbolImpl(name(), this);
@@ -109,23 +117,23 @@ private static boolean isInstanceMethod(FunctionDef functionDef) {
109117
.noneMatch(decorator -> decorator.equals("staticmethod") || decorator.equals("classmethod"));
110118
}
111119

112-
private void createParameterNames(List<AnyParameter> parameterTrees) {
120+
private void createParameterNames(List<AnyParameter> parameterTrees, @Nullable String fileId) {
113121
boolean keywordOnly = false;
114122
for (AnyParameter anyParameter : parameterTrees) {
115123
if (anyParameter.is(Tree.Kind.PARAMETER)) {
116124
org.sonar.plugins.python.api.tree.Parameter parameter = (org.sonar.plugins.python.api.tree.Parameter) anyParameter;
117125
Name parameterName = parameter.name();
118126
Token starToken = parameter.starToken();
119127
if (parameterName != null) {
120-
this.parameters.add(new ParameterImpl(parameterName.name(), parameter.defaultValue() != null, keywordOnly));
128+
this.parameters.add(new ParameterImpl(parameterName.name(), parameter.defaultValue() != null, keywordOnly, locationInFile(anyParameter, fileId)));
121129
if (starToken != null) {
122130
hasVariadicParameter = true;
123131
}
124132
} else if (starToken != null && "*".equals(starToken.value())) {
125133
keywordOnly = true;
126134
}
127135
} else {
128-
parameters.add(new ParameterImpl(null, false, false));
136+
parameters.add(new ParameterImpl(null, false, false, locationInFile(anyParameter, fileId)));
129137
}
130138
}
131139
}
@@ -181,11 +189,13 @@ private static class ParameterImpl implements Parameter {
181189
private final String name;
182190
private final boolean hasDefaultValue;
183191
private final boolean isKeywordOnly;
192+
private final LocationInFile location;
184193

185-
ParameterImpl(@Nullable String name, boolean hasDefaultValue, boolean isKeywordOnly) {
194+
ParameterImpl(@Nullable String name, boolean hasDefaultValue, boolean isKeywordOnly, @Nullable LocationInFile location) {
186195
this.name = name;
187196
this.hasDefaultValue = hasDefaultValue;
188197
this.isKeywordOnly = isKeywordOnly;
198+
this.location = location;
189199
}
190200

191201
@Override
@@ -203,5 +213,11 @@ public boolean hasDefaultValue() {
203213
public boolean isKeywordOnly() {
204214
return isKeywordOnly;
205215
}
216+
217+
@CheckForNull
218+
@Override
219+
public LocationInFile location() {
220+
return location;
221+
}
206222
}
207223
}

python-frontend/src/test/java/org/sonar/python/semantic/FunctionSymbolTest.java

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@
1919
*/
2020
package org.sonar.python.semantic;
2121

22-
import java.util.List;
2322
import org.junit.Test;
23+
import org.sonar.plugins.python.api.LocationInFile;
24+
import org.sonar.plugins.python.api.PythonFile;
2425
import org.sonar.plugins.python.api.symbols.ClassSymbol;
2526
import org.sonar.plugins.python.api.symbols.FunctionSymbol;
2627
import org.sonar.plugins.python.api.symbols.Symbol;
28+
import org.sonar.plugins.python.api.tree.CallExpression;
2729
import org.sonar.plugins.python.api.tree.ClassDef;
2830
import org.sonar.plugins.python.api.tree.FileInput;
2931
import org.sonar.plugins.python.api.tree.FunctionDef;
@@ -33,6 +35,7 @@
3335

3436
import static org.assertj.core.api.Assertions.assertThat;
3537
import static org.sonar.python.PythonTestUtils.parse;
38+
import static org.sonar.python.semantic.SymbolUtils.pathOf;
3639

3740
public class FunctionSymbolTest {
3841

@@ -171,12 +174,35 @@ public void instance_method() {
171174
assertThat(newMethod.isInstanceMethod()).isFalse();
172175
}
173176

177+
@Test
178+
public void locations() {
179+
PythonFile foo = PythonTestUtils.pythonFile("foo");
180+
181+
FunctionSymbol functionSymbol = functionSymbol(foo, "def foo(param1, param2): ...");
182+
assertThat(functionSymbol.parameters().get(0).location()).isEqualToComparingFieldByField(new LocationInFile(pathOf(foo).toString(), 1, 8, 1, 14));
183+
assertThat(functionSymbol.parameters().get(1).location()).isEqualToComparingFieldByField(new LocationInFile(pathOf(foo).toString(), 1, 16, 1, 22));
184+
assertThat(functionSymbol.definitionLocation()).isEqualToComparingFieldByField(new LocationInFile(pathOf(foo).toString(), 1, 4, 1, 7));
185+
186+
functionSymbol = functionSymbol(foo, "def foo(*param1): ...");
187+
assertThat(functionSymbol.parameters().get(0).location()).isEqualToComparingFieldByField(new LocationInFile(pathOf(foo).toString(), 1, 8, 1, 15));
188+
189+
functionSymbol = functionSymbol(foo, "def foo((a, b)): ...");
190+
assertThat(functionSymbol.parameters().get(0).location()).isEqualToComparingFieldByField(new LocationInFile(pathOf(foo).toString(), 1, 8, 1, 14));
191+
192+
FileInput fileInput = parse(new SymbolTableBuilder(foo), "all([1,2,3])");
193+
CallExpression callExpression = (CallExpression) PythonTestUtils.getAllDescendant(fileInput, t -> t.is(Tree.Kind.CALL_EXPR)).get(0);
194+
FunctionSymbol builtinFunctionSymbol = (FunctionSymbol) callExpression.calleeSymbol();
195+
assertThat(builtinFunctionSymbol.definitionLocation()).isNull();
196+
assertThat(builtinFunctionSymbol.parameters().get(0).location()).isNull();
197+
}
198+
199+
private FunctionSymbol functionSymbol(PythonFile pythonFile, String... code) {
200+
FileInput fileInput = parse(new SymbolTableBuilder(pythonFile), code);
201+
FunctionDef functionDef = (FunctionDef) PythonTestUtils.getAllDescendant(fileInput, t -> t.is(Tree.Kind.FUNCDEF)).get(0);;
202+
return TreeUtils.getFunctionSymbolFromDef(functionDef);
203+
}
204+
174205
private FunctionSymbol functionSymbol(String... code) {
175-
FileInput tree = parse(code);
176-
FunctionDef functionDef = (FunctionDef) PythonTestUtils.getAllDescendant(tree, t -> t.is(Tree.Kind.FUNCDEF)).get(0);
177-
Symbol functionSymbol = functionDef.name().symbol();
178-
assertThat(functionSymbol.kind()).isEqualTo(Symbol.Kind.FUNCTION);
179-
List<FunctionSymbol.Parameter> parameters = ((FunctionSymbol) functionSymbol).parameters();
180-
return ((FunctionSymbol) functionSymbol);
206+
return functionSymbol(PythonTestUtils.pythonFile("foo"), code);
181207
}
182208
}

0 commit comments

Comments
 (0)