Skip to content

Commit 375575b

Browse files
Seppli11sonartech
authored andcommitted
SONARPY-3195 Refactor PythonVisitorContext Constructors to Builder (#400)
GitOrigin-RevId: 92a379e79fa41e75964a2a5303d866edd2e39a92
1 parent 3b1be31 commit 375575b

File tree

9 files changed

+150
-97
lines changed

9 files changed

+150
-97
lines changed

python-checks-testkit/src/main/java/org/sonar/python/checks/quickfix/PythonQuickFixVerifier.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,11 @@ private static PythonVisitorContext createVisitorContext(PythonParser parser, Py
159159
var astNode = parser.parse(pythonFile.content());
160160
var fileInput = treeMaker.fileInput(astNode);
161161

162-
return new PythonVisitorContext(fileInput,
163-
pythonFile, null, "",
164-
ProjectLevelSymbolTable.empty(), CacheContextImpl.dummyCache(), SonarProduct.SONARLINT);
162+
return new PythonVisitorContext.Builder(fileInput, pythonFile)
163+
.projectLevelSymbolTable(ProjectLevelSymbolTable.empty())
164+
.cacheContext(CacheContextImpl.dummyCache())
165+
.sonarProduct(SonarProduct.SONARLINT)
166+
.build();
165167
}
166168

167169
private static String applyQuickFix(String codeWithIssue, PythonQuickFix quickFix) {

python-checks/src/test/java/org/sonar/python/checks/ParsingErrorCheckTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.List;
2424
import org.junit.jupiter.api.Test;
2525
import org.sonar.plugins.python.api.PythonCheck.PreciseIssue;
26+
import org.sonar.api.SonarProduct;
2627
import org.sonar.plugins.python.api.PythonVisitorContext;
2728
import org.sonar.python.parser.PythonParser;
2829

@@ -41,7 +42,7 @@ void test() throws Exception {
4142
parser.parse(fileContent);
4243
throw new IllegalStateException("Expected RecognitionException");
4344
} catch (RecognitionException e) {
44-
context = new PythonVisitorContext(null, e);
45+
context = new PythonVisitorContext(null, e, SonarProduct.SONARQUBE);
4546
}
4647

4748
ParsingErrorCheck check = new ParsingErrorCheck();

python-commons/src/main/java/org/sonar/plugins/python/PythonScanner.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -150,13 +150,13 @@ private PythonVisitorContext createVisitorContext(PythonInputFile inputFile, Pyt
150150
AstNode astNode = parserSupplier.get().parse(inputFile.contents());
151151
PythonTreeMaker treeMaker = getTreeMaker(inputFile);
152152
FileInput parse = treeMaker.fileInput(astNode);
153-
visitorContext = new PythonVisitorContext(parse,
154-
pythonFile,
155-
getWorkingDirectory(context),
156-
indexer.packageName(inputFile),
157-
indexer.projectLevelSymbolTable(),
158-
indexer.cacheContext(),
159-
context.runtime().getProduct());
153+
visitorContext = new PythonVisitorContext.Builder(parse, pythonFile)
154+
.workingDirectory(getWorkingDirectory(context))
155+
.packageName(indexer.packageName(inputFile))
156+
.projectLevelSymbolTable(indexer.projectLevelSymbolTable())
157+
.cacheContext(indexer.cacheContext())
158+
.sonarProduct(context.runtime().getProduct())
159+
.build();
160160

161161
} catch (RecognitionException e) {
162162
visitorContext = new PythonVisitorContext(pythonFile, e, context.runtime().getProduct());

python-commons/src/test/java/org/sonar/plugins/python/IpynbNotebookParserScannerTest.java

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,10 @@ void trailing_whitespace() throws IOException {
3737
var inputFile = createInputFile(baseDir, "notebook_trailing_whitespace.ipynb", InputFile.Status.CHANGED, InputFile.Type.MAIN);
3838
var result = IpynbNotebookParser.parseNotebook(inputFile).get();
3939
var check = new TrailingWhitespaceCheck();
40-
var context = new PythonVisitorContext(
40+
var context = new PythonVisitorContext.Builder(
4141
TestPythonVisitorRunner.parseNotebookFile(result.locationMap(), result.contents()),
42-
SonarQubePythonFile.create(result),
43-
null,
44-
"");
42+
SonarQubePythonFile.create(result))
43+
.build();
4544
check.scanFile(context);
4645

4746
var issues = context.getIssues();
@@ -68,11 +67,10 @@ void trailing_whitespace_compressed() throws IOException {
6867
var inputFile = createInputFile(baseDir, "notebook_trailing_whitespace_compressed.ipynb", InputFile.Status.CHANGED, InputFile.Type.MAIN);
6968
var result = IpynbNotebookParser.parseNotebook(inputFile).get();
7069
var check = new TrailingWhitespaceCheck();
71-
var context = new PythonVisitorContext(
70+
var context = new PythonVisitorContext.Builder(
7271
TestPythonVisitorRunner.parseNotebookFile(result.locationMap(), result.contents()),
73-
SonarQubePythonFile.create(result),
74-
null,
75-
"");
72+
SonarQubePythonFile.create(result))
73+
.build();
7674
check.scanFile(context);
7775

7876
var issues = context.getIssues();

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

Lines changed: 70 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.io.File;
2222
import java.util.ArrayList;
2323
import java.util.List;
24+
import java.util.Optional;
2425
import javax.annotation.CheckForNull;
2526
import javax.annotation.Nullable;
2627
import org.sonar.api.SonarProduct;
@@ -46,78 +47,32 @@ public class PythonVisitorContext extends PythonInputFileContext {
4647
private final List<PreciseIssue> issues;
4748
private final ProjectConfiguration projectConfiguration;
4849

49-
public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable File workingDirectory, String packageName) {
50-
this(rootTree, pythonFile, workingDirectory, packageName, new ProjectConfiguration());
51-
}
52-
53-
public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable File workingDirectory, String packageName, ProjectConfiguration projectConfiguration) {
54-
super(pythonFile, workingDirectory, CacheContextImpl.dummyCache(), ProjectLevelSymbolTable.empty());
55-
buildSymbols(rootTree, pythonFile, packageName);
56-
var symbolTable = new SymbolTableBuilderV2(rootTree).build();
57-
var projectLevelTypeTable = new ProjectLevelTypeTable(ProjectLevelSymbolTable.empty());
58-
59-
this.rootTree = rootTree;
60-
this.parsingException = null;
61-
this.moduleType = new TypeInferenceV2(projectLevelTypeTable, pythonFile, symbolTable, packageName).inferModuleType(rootTree);
62-
this.typeChecker = new TypeChecker(projectLevelTypeTable);
63-
this.projectConfiguration = projectConfiguration;
64-
this.issues = new ArrayList<>();
65-
}
66-
67-
public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable File workingDirectory, String packageName,
68-
ProjectLevelSymbolTable projectLevelSymbolTable, CacheContext cacheContext) {
69-
this(rootTree, pythonFile, workingDirectory, packageName, projectLevelSymbolTable, cacheContext, new ProjectConfiguration());
70-
}
71-
72-
public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable File workingDirectory, String packageName,
73-
ProjectLevelSymbolTable projectLevelSymbolTable, CacheContext cacheContext, ProjectConfiguration projectConfiguration) {
74-
super(pythonFile, workingDirectory, cacheContext, projectLevelSymbolTable);
75-
76-
buildSymbols(rootTree, pythonFile, packageName, projectLevelSymbolTable);
77-
var symbolTable = new SymbolTableBuilderV2(rootTree).build();
78-
var projectLevelTypeTable = new ProjectLevelTypeTable(projectLevelSymbolTable);
79-
80-
this.rootTree = rootTree;
81-
this.parsingException = null;
82-
this.moduleType = new TypeInferenceV2(projectLevelTypeTable, pythonFile, symbolTable, packageName).inferModuleType(rootTree);
83-
this.typeChecker = new TypeChecker(projectLevelTypeTable);
84-
this.projectConfiguration = projectConfiguration;
85-
this.issues = new ArrayList<>();
86-
}
50+
private PythonVisitorContext(FileInput rootTree,
51+
PythonFile pythonFile,
52+
@Nullable File workingDirectory,
53+
String packageName,
54+
ProjectLevelSymbolTable projectLevelSymbolTable,
55+
CacheContext cacheContext,
56+
SonarProduct sonarProduct,
57+
ProjectConfiguration projectConfiguration) {
8758

88-
public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable File workingDirectory, String packageName,
89-
ProjectLevelSymbolTable projectLevelSymbolTable, CacheContext cacheContext, SonarProduct sonarProduct) {
9059
super(pythonFile, workingDirectory, cacheContext, sonarProduct, projectLevelSymbolTable);
9160
var symbolTableBuilderV2 = new SymbolTableBuilderV2(rootTree);
9261
var symbolTable = symbolTableBuilderV2.build();
9362
buildSymbols(rootTree, pythonFile, packageName, projectLevelSymbolTable);
9463
var projectLevelTypeTable = new ProjectLevelTypeTable(projectLevelSymbolTable);
9564
this.moduleType = new TypeInferenceV2(projectLevelTypeTable, pythonFile, symbolTable, packageName).inferModuleType(rootTree);
9665
this.typeChecker = new TypeChecker(projectLevelTypeTable);
97-
this.projectConfiguration = new ProjectConfiguration();
66+
this.projectConfiguration = projectConfiguration;
9867
this.rootTree = rootTree;
9968
this.parsingException = null;
10069
this.issues = new ArrayList<>();
10170
}
102-
103-
private static synchronized void buildSymbols(FileInput rootTree, PythonFile pythonFile, String packageName) {
104-
buildSymbols(rootTree, pythonFile, packageName, ProjectLevelSymbolTable.empty());
105-
}
106-
10771
private static synchronized void buildSymbols(FileInput rootTree, PythonFile pythonFile, String packageName, ProjectLevelSymbolTable projectLevelSymbolTable) {
10872
var symbolTableBuilder = new SymbolTableBuilder(packageName, pythonFile, projectLevelSymbolTable);
10973
symbolTableBuilder.visitFileInput(rootTree);
11074
}
11175

112-
public PythonVisitorContext(PythonFile pythonFile, RecognitionException parsingException) {
113-
super(pythonFile, null, CacheContextImpl.dummyCache(), ProjectLevelSymbolTable.empty());
114-
this.rootTree = null;
115-
this.parsingException = parsingException;
116-
this.typeChecker = new TypeChecker(new ProjectLevelTypeTable(ProjectLevelSymbolTable.empty()));
117-
this.projectConfiguration = new ProjectConfiguration();
118-
this.issues = new ArrayList<>();
119-
}
120-
12176
public PythonVisitorContext(PythonFile pythonFile, RecognitionException parsingException, SonarProduct sonarProduct) {
12277
super(pythonFile, null, CacheContextImpl.dummyCache(), sonarProduct, ProjectLevelSymbolTable.empty());
12378
this.rootTree = null;
@@ -156,4 +111,64 @@ public ModuleType moduleType() {
156111
public ProjectConfiguration projectConfiguration() {
157112
return projectConfiguration;
158113
}
114+
115+
public static class Builder {
116+
private final PythonFile pythonFile;
117+
private final FileInput rootTree;
118+
119+
private Optional<ProjectLevelSymbolTable> projectLevelSymbolTable = Optional.empty();
120+
private Optional<CacheContext> cacheContext = Optional.empty();
121+
private Optional<SonarProduct> sonarProduct = Optional.empty();
122+
private Optional<File> workingDirectory = Optional.empty();
123+
private Optional<ProjectConfiguration> projectConfiguration = Optional.empty();
124+
private Optional<String> packageName = Optional.empty();
125+
126+
public Builder(FileInput rootTree, PythonFile pythonFile) {
127+
this.rootTree = rootTree;
128+
this.pythonFile = pythonFile;
129+
}
130+
131+
public Builder workingDirectory(@Nullable File workingDirectory) {
132+
this.workingDirectory = Optional.ofNullable(workingDirectory);
133+
return this;
134+
}
135+
136+
public Builder packageName(String packageName) {
137+
this.packageName = Optional.ofNullable(packageName);
138+
return this;
139+
}
140+
141+
public Builder projectLevelSymbolTable(ProjectLevelSymbolTable projectLevelSymbolTable) {
142+
this.projectLevelSymbolTable = Optional.ofNullable(projectLevelSymbolTable);
143+
return this;
144+
}
145+
146+
public Builder cacheContext(CacheContext cacheContext) {
147+
this.cacheContext = Optional.ofNullable(cacheContext);
148+
return this;
149+
}
150+
151+
public Builder sonarProduct(SonarProduct sonarProduct) {
152+
this.sonarProduct = Optional.ofNullable(sonarProduct);
153+
return this;
154+
}
155+
156+
public Builder projectConfiguration(ProjectConfiguration projectConfiguration) {
157+
this.projectConfiguration = Optional.ofNullable(projectConfiguration);
158+
return this;
159+
}
160+
161+
public PythonVisitorContext build() {
162+
return new PythonVisitorContext(
163+
rootTree,
164+
pythonFile,
165+
workingDirectory.orElse(null),
166+
packageName.orElse(""),
167+
projectLevelSymbolTable.orElseGet(ProjectLevelSymbolTable::empty),
168+
cacheContext.orElseGet(CacheContextImpl::dummyCache),
169+
sonarProduct.orElse(SonarProduct.SONARQUBE),
170+
projectConfiguration.orElse(new ProjectConfiguration())
171+
);
172+
}
173+
}
159174
}

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

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,20 +80,35 @@ public static PythonVisitorContext createContext(File file, @Nullable File worki
8080
ProjectLevelSymbolTable projectLevelSymbolTable, CacheContext cacheContext, ProjectConfiguration projectConfiguration) {
8181
TestPythonFile pythonFile = new TestPythonFile(file);
8282
FileInput rootTree = parseFile(pythonFile);
83-
return new PythonVisitorContext(rootTree, pythonFile, workingDirectory, packageName, projectLevelSymbolTable, cacheContext, projectConfiguration);
83+
return new PythonVisitorContext.Builder(rootTree, pythonFile)
84+
.workingDirectory(workingDirectory)
85+
.packageName(packageName)
86+
.projectLevelSymbolTable(projectLevelSymbolTable)
87+
.cacheContext(cacheContext)
88+
.projectConfiguration(projectConfiguration)
89+
.build();
8490
}
8591

8692
public static PythonVisitorContext createContext(MockPythonFile file, @Nullable File workingDirectory, String packageName,
8793
ProjectLevelSymbolTable projectLevelSymbolTable, CacheContext cacheContext) {
8894
FileInput rootTree = parseFile(file);
89-
return new PythonVisitorContext(rootTree, file, workingDirectory, packageName, projectLevelSymbolTable, cacheContext);
95+
return new PythonVisitorContext.Builder(rootTree, file)
96+
.workingDirectory(workingDirectory)
97+
.packageName(packageName)
98+
.projectLevelSymbolTable(projectLevelSymbolTable)
99+
.cacheContext(cacheContext)
100+
.build();
90101
}
91102

92103
public static PythonVisitorContext createNotebookContext(File file, Map<Integer, IPythonLocation> locations, String content, String packageName,
93104
ProjectLevelSymbolTable projectLevelSymbolTable, CacheContext cacheContext) {
94105
TestPythonFile pythonFile = new TestPythonFile(file);
95106
FileInput rootTree = parseNotebookFile(locations, content);
96-
return new PythonVisitorContext(rootTree, pythonFile, null, packageName, projectLevelSymbolTable, cacheContext);
107+
return new PythonVisitorContext.Builder(rootTree, pythonFile)
108+
.packageName(packageName)
109+
.projectLevelSymbolTable(projectLevelSymbolTable)
110+
.cacheContext(cacheContext)
111+
.build();
97112
}
98113

99114
public static ProjectLevelSymbolTable globalSymbols(List<File> files, File baseDir) {

python-frontend/src/test/java/org/sonar/plugins/python/api/PythonVisitorContextTest.java

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package org.sonar.plugins.python.api;
1818

1919
import com.sonar.sslr.api.RecognitionException;
20-
import java.io.File;
2120
import java.util.Collections;
2221
import java.util.Map;
2322
import java.util.Set;
@@ -47,21 +46,21 @@ void fullyQualifiedModuleName() {
4746
FileInput fileInput = PythonTestUtils.parse("def foo(): pass");
4847

4948
PythonFile pythonFile = pythonFile("my_module.py");
50-
var ctx = new PythonVisitorContext(fileInput, pythonFile, null, "my_package");
49+
var ctx = new PythonVisitorContext.Builder(fileInput, pythonFile).packageName("my_package").build();
5150
FunctionDef functionDef = (FunctionDef) PythonTestUtils.getAllDescendant(fileInput, t -> t.is(Tree.Kind.FUNCDEF)).get(0);
5251
assertThat(functionDef.name().symbol().fullyQualifiedName()).isEqualTo("my_package.my_module.foo");
5352
assertThat(ctx.moduleType()).isNotNull()
5453
.hasFieldOrPropertyWithValue("fullyQualifiedName", "my_package.my_module");
5554

5655
// no package
57-
ctx = new PythonVisitorContext(fileInput, pythonFile, null, "");
56+
ctx = new PythonVisitorContext.Builder(fileInput, pythonFile).build();
5857
assertThat(functionDef.name().symbol().fullyQualifiedName()).isEqualTo("my_module.foo");
5958
assertThat(ctx.moduleType()).isNotNull()
6059
.hasFieldOrPropertyWithValue("fullyQualifiedName", "my_module");
6160

6261
// file without extension
6362
Mockito.when(pythonFile.fileName()).thenReturn("my_module");
64-
ctx = new PythonVisitorContext(fileInput, pythonFile, null, "my_package");
63+
ctx = new PythonVisitorContext.Builder(fileInput, pythonFile).packageName("my_package").build();
6564
functionDef = (FunctionDef) PythonTestUtils.getAllDescendant(fileInput, t -> t.is(Tree.Kind.FUNCDEF)).get(0);
6665
assertThat(functionDef.name().symbol().fullyQualifiedName()).isEqualTo("my_package.my_module.foo");
6766
assertThat(ctx.moduleType()).isNotNull()
@@ -72,12 +71,12 @@ void fullyQualifiedModuleName() {
7271
void initModuleFullyQualifiedName() {
7372
FileInput fileInput = PythonTestUtils.parse("def fn(): pass");
7473
PythonFile pythonFile = pythonFile("__init__.py");
75-
new PythonVisitorContext(fileInput, pythonFile, null, "foo.bar");
74+
new PythonVisitorContext.Builder(fileInput, pythonFile).packageName("foo.bar").build();
7675
FunctionDef functionDef = (FunctionDef) PythonTestUtils.getAllDescendant(fileInput, t -> t.is(Tree.Kind.FUNCDEF)).get(0);
7776
assertThat(functionDef.name().symbol().fullyQualifiedName()).isEqualTo("foo.bar.fn");
7877

7978
// no package
80-
new PythonVisitorContext(fileInput, pythonFile, null, "");
79+
new PythonVisitorContext.Builder(fileInput, pythonFile).build();
8180
assertThat(functionDef.name().symbol().fullyQualifiedName()).isEqualTo("fn");
8281
}
8382

@@ -90,7 +89,11 @@ void globalSymbols() {
9089
Set<Descriptor> descriptors = Set.of(new VariableDescriptor("a", "mod.a", null), new VariableDescriptor("b", "mod.b", null));
9190
Map<String, Set<Descriptor>> globalDescriptors = Collections.singletonMap("mod", descriptors);
9291

93-
new PythonVisitorContext(fileInput, pythonFile, null, "my_package", ProjectLevelSymbolTable.from(globalDescriptors), CacheContextImpl.dummyCache());
92+
new PythonVisitorContext.Builder(fileInput, pythonFile)
93+
.packageName("my_package")
94+
.projectLevelSymbolTable(ProjectLevelSymbolTable.from(globalDescriptors))
95+
.cacheContext(CacheContextImpl.dummyCache())
96+
.build();
9497
assertThat(fileInput.globalVariables()).extracting(Symbol::name).containsExactlyInAnyOrder("a", "b");
9598
}
9699

@@ -99,21 +102,34 @@ void sonar_product() {
99102
CacheContextImpl cacheContext = CacheContextImpl.dummyCache();
100103
ProjectLevelSymbolTable projectLevelSymbolTable = ProjectLevelSymbolTable.empty();
101104
String myPackage = "my_package";
102-
File workingDirectory = null;
103105
PythonFile pythonFile = pythonFile("my_module.py");
104106
FileInput fileInput = mock(FileInputImpl.class);
105107

106-
PythonVisitorContext pythonVisitorContext = new PythonVisitorContext(fileInput, pythonFile, workingDirectory, myPackage, projectLevelSymbolTable, cacheContext, SonarProduct.SONARLINT);
108+
PythonVisitorContext pythonVisitorContext = new PythonVisitorContext.Builder(fileInput, pythonFile)
109+
.packageName(myPackage)
110+
.projectLevelSymbolTable(projectLevelSymbolTable)
111+
.cacheContext(cacheContext)
112+
.sonarProduct(SonarProduct.SONARLINT)
113+
.build();
107114
assertThat(pythonVisitorContext.sonarProduct()).isEqualTo(SonarProduct.SONARLINT);
108115

109-
pythonVisitorContext = new PythonVisitorContext(fileInput, pythonFile, workingDirectory, myPackage, projectLevelSymbolTable, cacheContext, SonarProduct.SONARQUBE);
116+
pythonVisitorContext = new PythonVisitorContext.Builder(fileInput, pythonFile)
117+
.packageName(myPackage)
118+
.projectLevelSymbolTable(projectLevelSymbolTable)
119+
.cacheContext(cacheContext)
120+
.sonarProduct(SonarProduct.SONARQUBE)
121+
.build();
110122
assertThat(pythonVisitorContext.sonarProduct()).isEqualTo(SonarProduct.SONARQUBE);
111123

112-
pythonVisitorContext = new PythonVisitorContext(fileInput, pythonFile, workingDirectory, myPackage, projectLevelSymbolTable, cacheContext);
124+
pythonVisitorContext = new PythonVisitorContext.Builder(fileInput, pythonFile)
125+
.packageName(myPackage)
126+
.projectLevelSymbolTable(projectLevelSymbolTable)
127+
.cacheContext(cacheContext)
128+
.build();
113129
assertThat(pythonVisitorContext.sonarProduct()).isEqualTo(SonarProduct.SONARQUBE);
114130

115131
RecognitionException parsingException = mock(RecognitionException.class);
116-
pythonVisitorContext = new PythonVisitorContext(pythonFile, parsingException);
132+
pythonVisitorContext = new PythonVisitorContext(pythonFile, parsingException, SonarProduct.SONARQUBE);
117133
assertThat(pythonVisitorContext.sonarProduct()).isEqualTo(SonarProduct.SONARQUBE);
118134

119135
pythonVisitorContext = new PythonVisitorContext(pythonFile, parsingException, SonarProduct.SONARLINT);

0 commit comments

Comments
 (0)