Skip to content

Commit 0007395

Browse files
committed
0.22.0 - Fix a Java class signature parsing bug and a C# full type name resolution bug.
1 parent e6177ed commit 0007395

File tree

16 files changed

+161
-99
lines changed

16 files changed

+161
-99
lines changed

CHANGELOG.md

+11-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,17 @@ This project does its best to adhere to [Semantic Versioning](http://semver.org/
44

55

66
--------
7-
### [0.21.0](https://github.com/TeamworkGuy2/JParseCode/commit/7482a2a148a5f42e230c33897d3f17d907c5348f) - 2020-11-22
7+
### [0.22.0](N/A) - 2020-04-18
8+
#### Removed
9+
* `ProjectClassSet` `resolveSimpleName()` in favor of moving the two lines of code to the single calling location
10+
11+
#### Fixed
12+
* C# full type name resolution fixed to resolve against the parent namespaces the class resides in (affects types in class signatures, method signatures, and fields)
13+
* Java class signature parsing fixed to support both `extends` and implements in the same signature `implements` (can't believe I overlooked this and didn't have a unit test)
14+
15+
16+
--------
17+
### [0.21.0](https://github.com/TeamworkGuy2/JParseCode/commit/e6177edf59fb46bd8edcd830f0cf720eb44206b5) - 2020-11-26
818
__Parsing performance optimizations (~15-20%)__
919
#### Changed
1020
* Update dependencies `jtext-tokenizer@0.7.0` and `jtext-parser@0.18.0`

bin/jparse_code-with-tests.jar

1.54 KB
Binary file not shown.

bin/jparse_code.jar

37.5 KB
Binary file not shown.

package-lib.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version" : "0.21.0",
2+
"version" : "0.22.0",
33
"name" : "jparse-code",
44
"description" : "An in-progress suite of parsing/transpilation tools for C#, Java, and TypeScript code. Generates simple JSON ASTs.",
55
"homepage" : "https://github.com/TeamworkGuy2/JParseCode",
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
2+
namespace ParserExamples {
3+
4+
/// <summary>
5+
/// Base class for this project.
6+
/// </summary>
7+
/// <threadsafety>
8+
/// This class is mutable. And it is not thread-safe.
9+
/// </threadsafety>
10+
public abstract class BaseClass {
11+
12+
/// <summary>A string representation of this instance.</value>
13+
public virtual string ToString();
14+
}
15+
}

rsc/csharp/ParserExamples/Models/TrackInfo.cs

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
using System;
22
using System.Runtime.Serialization;
33

4-
namespace ParserExamples.Models {
5-
4+
namespace ParserExamples.Models
5+
{
66
/// <summary>
77
/// A class representing a Track.
88
/// </summary>
99
/// <threadsafety>
1010
/// This class is mutable. And it is not thread-safe.
1111
/// </threadsafety>
1212
[DataContract]
13-
public class TrackInfo : ISerializable, IComparable<TrackInfo> {
14-
13+
public class TrackInfo : BaseClass, ISerializable, IComparable<TrackInfo>
14+
{
1515
/// <value>The track name.</value>
1616
[DataMember]
1717
public string Name { get; set; }
@@ -41,5 +41,4 @@ public TType Refresh<TType>(TType tt) where TType : IConvertible {
4141
return tt;
4242
}
4343
}
44-
4544
}
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package ParserExamples;
2+
3+
/// <summary>
4+
/// Base class for this project.
5+
/// </summary>
6+
public abstract class AlbumInfo {
7+
8+
/// <summary>A string representation of this instance.</value>
9+
public String toString();
10+
11+
}

rsc/java/ParserExamples/Models/TrackInfo.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
/// This class is mutable. And it is not thread-safe.
1212
/// </threadsafety>
1313
@DataContract
14-
public class TrackInfo implements Serializable, Comparable<TrackInfo> {
14+
public class TrackInfo extends BaseClass implements Serializable, Comparable<TrackInfo> {
1515

1616
/// <value>The track name.</value>
1717
@DataMember

src/twg2/parser/codeParser/csharp/CsBlockParser.java

+34-26
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import twg2.ast.interm.field.FieldSig;
1515
import twg2.ast.interm.method.MethodSigSimple;
1616
import twg2.ast.interm.type.TypeSig;
17-
import twg2.collections.dataStructures.BaseList;
1817
import twg2.parser.codeParser.AstExtractor;
1918
import twg2.parser.codeParser.extractors.AccessModifierExtractor;
2019
import twg2.parser.codeParser.extractors.BlockExtractor;
@@ -179,53 +178,62 @@ public static void _extractBlocksFromTree(List<String> nameScope, SimpleTree<Cod
179178
}
180179

181180

182-
/** Reads backward from a '{' block through a simple class signature ({@code ClassName [: ClassName]}).
181+
/** Reads backward from a '{' block through a simple class signature ({@code ClassName [: ClassName, ClassName, ...]}).
183182
* Returns the iterator where {@code next()} would return the class name element.
184-
* @return {@code <className, extendImplementNames>}
183+
* @return {@code <className, extendImplementNames[]>}
185184
*/
186185
private static Entry<String, List<String>> readClassIdentifierAndExtends(ListIterator<SimpleTree<CodeToken>> iter) {
187186
var keywordUtil = CodeLanguageOptions.C_SHARP.getKeywordUtil();
188187
// class signatures are read backward from the opening '{'
189188
int prevCount = 0;
190189
var names = new ArrayList<String>();
190+
boolean lastNodeWasInheritanceKeyword = false;
191191
Entry<String, List<String>> nameCompoundRes = null;
192192

193-
// get the first element and begin checking
193+
// get the node and begin checking backward
194194
if(iter.hasPrevious()) { prevCount++; }
195195
SimpleTree<CodeToken> prevNode = iter.hasPrevious() ? iter.previous() : null;
196196

197-
while(prevNode != null && AstFragType.isIdentifierOrKeyword(prevNode.getData()) && !keywordUtil.blockModifiers().is(prevNode.getData()) && !keywordUtil.isInheritanceKeyword(prevNode.getData().getText())) {
197+
while(prevNode != null && (AstFragType.isIdentifierOrKeyword(prevNode.getData()) || keywordUtil.isInheritanceKeyword(prevNode.getData().getText())) && !keywordUtil.blockModifiers().is(prevNode.getData())) {
198198
// found an object initializer in the form 'new [Abc] {', not a class/interface definition so return nothing
199199
if(names.size() < 2 && CsKeyword.NEW.toSrc().equals(prevNode.getData().getText())) {
200200
break;
201201
}
202-
names.add(prevNode.getData().getText());
203-
prevNode = iter.hasPrevious() ? iter.previous() : null;
204-
if(iter.hasPrevious()) { prevCount++; }
205-
}
206202

207-
// if the class signature extends/implements, then the identifiers just read are the class/interface names, next read the actual class name
208-
if(prevNode != null && prevNode.getData().getText().equals(":")) {
209-
prevNode = iter.hasPrevious() ? iter.previous() : null;
210-
if(iter.hasPrevious()) { prevCount++; }
211-
if(prevNode != null && AstFragType.isIdentifierOrKeyword(prevNode.getData()) && !keywordUtil.blockModifiers().is(prevNode.getData())) {
212-
Collections.reverse(names);
213-
var extendImplementNames = names;
214-
String className = prevNode.getData().getText();
215-
nameCompoundRes = Tuples.of(className, extendImplementNames);
203+
if(!keywordUtil.isInheritanceKeyword(prevNode.getData().getText())) {
204+
names.add(prevNode.getData().getText());
205+
lastNodeWasInheritanceKeyword = false;
216206
}
217207
else {
218-
throw new IllegalStateException("found block with extend/implement names, but no class name " + names);
208+
lastNodeWasInheritanceKeyword = true;
219209
}
210+
211+
if(iter.hasPrevious()) { prevCount++; }
212+
prevNode = iter.hasPrevious() ? iter.previous() : null;
213+
}
214+
215+
// syntax check, ensure there's a class name identifier between the block modifier and inheritance keyword ':'
216+
if(lastNodeWasInheritanceKeyword) {
217+
throw new IllegalStateException("found class inheritance keyword ':' with no class name preceeding it, found '" + String.valueOf(prevNode) + "'");
220218
}
221-
// else, we should have only read one name with the loop and it is the class name
222-
else if(names.size() == 1) {
223-
String className = names.get(0);
224-
nameCompoundRes = Tuples.of(className, Collections.emptyList());
225-
// move iterator forward since the while loop doesn't use the last value (i.e. reads one element past the valid elements it wants to consume)
226-
if(prevCount > 0) {
227-
iter.next();
219+
220+
// move iterator forward since the while loop reads past the class name to the first block modifier
221+
if(prevCount > 0) {
222+
iter.next();
223+
}
224+
225+
// if a likely valid class block modifier has been reached, then the identifiers just read are the class/interface names and the class name
226+
if(prevNode != null && keywordUtil.blockModifiers().is(prevNode.getData())) {
227+
// TODO this is valid for the code: 'method<T>() where T : class { ... }'
228+
if(names.size() == 0) {
229+
return null;
230+
//throw new IllegalStateException("found block with no name");
228231
}
232+
233+
String className = names.remove(names.size() - 1);
234+
if(names.size() > 1) { Collections.reverse(names); }
235+
var extendImplementNames = names;
236+
nameCompoundRes = Tuples.of(className, extendImplementNames);
229237
}
230238

231239
return nameCompoundRes;

src/twg2/parser/codeParser/java/JavaBlockParser.java

+38-29
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import twg2.ast.interm.field.FieldSig;
1515
import twg2.ast.interm.method.MethodSigSimple;
1616
import twg2.ast.interm.type.TypeSig;
17-
import twg2.collections.dataStructures.BaseList;
1817
import twg2.collections.interfaces.ListReadOnly;
1918
import twg2.parser.codeParser.AstExtractor;
2019
import twg2.parser.codeParser.extractors.AccessModifierExtractor;
@@ -197,50 +196,60 @@ private static String parsePackageDeclaration(SimpleTree<CodeToken> astTree) {
197196
}
198197

199198

200-
/** Reads backward from a '{' block through a simple class signature ({@code ClassName [extends ClassName] [implements InterfaceNme]}).
199+
/** Reads backward from a '{' block through a simple class signature ({@code ClassName [extends ClassName] [implements InterfaceName, InterfaceName, ...]}).
201200
* Returns the iterator where {@code next()} would return the class name element.
202-
* @return {@code <className, extendImplementNames>}
201+
* @return {@code <className, extendImplementNames[]>}
203202
*/
204203
private static Entry<String, List<String>> readClassIdentifierAndExtends(ListIterator<SimpleTree<CodeToken>> iter) {
205-
var lang = CodeLanguageOptions.JAVA;
206-
var keywordUtil = lang.getKeywordUtil();
204+
var keywordUtil = CodeLanguageOptions.JAVA.getKeywordUtil();
207205
// class signatures are read backward from the opening '{'
208206
int prevCount = 0;
209207
var names = new ArrayList<String>();
208+
boolean lastNodeWasInheritanceKeyword = false;
210209
Entry<String, List<String>> nameCompoundRes = null;
211210

212-
// get the first element and begin checking
211+
// get the node and begin checking backward
213212
if(iter.hasPrevious()) { prevCount++; }
214213
SimpleTree<CodeToken> prevNode = iter.hasPrevious() ? iter.previous() : null;
215214

216-
while(prevNode != null && AstFragType.isIdentifierOrKeyword(prevNode.getData()) && !keywordUtil.blockModifiers().is(prevNode.getData()) && !keywordUtil.isInheritanceKeyword(prevNode.getData().getText())) {
217-
names.add(prevNode.getData().getText());
218-
prevNode = iter.hasPrevious() ? iter.previous() : null;
219-
if(iter.hasPrevious()) { prevCount++; }
220-
}
221-
222-
// if the class signature extends/implements, then the identifiers just read are the class/interface names, next read the actual class name
223-
if(prevNode != null && lang.getAstUtil().isKeyword(prevNode.getData(), JavaKeyword.EXTENDS, JavaKeyword.IMPLEMENTS)) {
224-
prevNode = iter.hasPrevious() ? iter.previous() : null;
225-
if(iter.hasPrevious()) { prevCount++; }
226-
if(prevNode != null && AstFragType.isIdentifierOrKeyword(prevNode.getData()) && !keywordUtil.blockModifiers().is(prevNode.getData())) {
227-
Collections.reverse(names);
228-
var extendImplementNames = names;
229-
String className = prevNode.getData().getText();
230-
nameCompoundRes = Tuples.of(className, extendImplementNames);
215+
// read all identifiers until a block modifier is reached, including inheritance keywords (i.e. 'extends' and 'implements')
216+
// this creates a backward list of 'implements' class names, then 'extends' class names, then the actual class name
217+
while(prevNode != null && AstFragType.isIdentifierOrKeyword(prevNode.getData()) && !keywordUtil.blockModifiers().is(prevNode.getData())) {
218+
if(!keywordUtil.isInheritanceKeyword(prevNode.getData().getText())) {
219+
names.add(prevNode.getData().getText());
220+
lastNodeWasInheritanceKeyword = false;
231221
}
222+
// basic syntax checking to make sure there's an identifier between 'extends'/'implements' keywords
232223
else {
233-
throw new IllegalStateException("found block with extend/implement names, but no class name " + names);
224+
if(lastNodeWasInheritanceKeyword) {
225+
throw new IllegalStateException("found two adjacent 'extends'/'implements' keywords with no intermediate class name");
226+
}
227+
lastNodeWasInheritanceKeyword = true;
234228
}
229+
230+
if(iter.hasPrevious()) { prevCount++; }
231+
prevNode = iter.hasPrevious() ? iter.previous() : null;
232+
}
233+
234+
// syntax check, ensure there's a class name identifier between the block modifier and 'extends'/'implements' keyword
235+
if(lastNodeWasInheritanceKeyword) {
236+
throw new IllegalStateException("found 'extends'/'implements' keyword but no class name");
237+
}
238+
239+
// move iterator forward since the while loop reads past the class name to the first block modifier
240+
if(prevCount > 0) {
241+
iter.next();
235242
}
236-
// else, we should have only read one name with the loop and it is the class name
237-
else if(names.size() == 1) {
238-
String className = names.get(0);
239-
nameCompoundRes = Tuples.of(className, Collections.emptyList());
240-
// move iterator forward since the while loop doesn't use the last value (i.e. reads one element past the valid elements it wants to consume)
241-
if(prevCount > 0) {
242-
iter.next();
243+
244+
// if a likely valid class block modifier has been reached, then the identifiers just read are the class/interface names and the class name
245+
if(prevNode != null && keywordUtil.blockModifiers().is(prevNode.getData())) {
246+
if(names.size() == 0) {
247+
throw new IllegalStateException("found block with no name");
243248
}
249+
String className = names.remove(names.size() - 1);
250+
if(names.size() > 1) { Collections.reverse(names); }
251+
var extendImplementNames = names;
252+
nameCompoundRes = Tuples.of(className, extendImplementNames);
244253
}
245254

246255
return nameCompoundRes;

src/twg2/parser/codeParser/tools/NameUtil.java

+11
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,15 @@ public static List<String> allExceptLastFqName(List<String> names) {
4949
return resNames;
5050
}
5151

52+
53+
public static String joinFqNameExceptLast(List<String> names) {
54+
int size = names.size() - 1;
55+
var res = new StringBuilder();
56+
for(int i = 0; i < size; i++) {
57+
if(i > 0) { res.append('.'); }
58+
res.append(names.get(i));
59+
}
60+
return res.toString();
61+
}
62+
5263
}

src/twg2/parser/project/ProjectClassSet.java

+20-28
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@
1111
import twg2.ast.interm.classes.ClassSigSimple;
1212
import twg2.collections.builder.ListUtil;
1313
import twg2.parser.codeParser.BlockType;
14+
import twg2.parser.codeParser.csharp.CsBlock;
1415
import twg2.parser.codeParser.tools.NameUtil;
1516
import twg2.parser.resolver.ClassSigResolver;
1617
import twg2.parser.resolver.FieldSigResolver;
1718
import twg2.parser.resolver.MethodSigResolver;
1819
import twg2.parser.workflow.CodeFileParsed;
1920

2021
/** A group of classes/interfaces representing all of the compilation units in a project.
21-
* Provides {@link #resolveSimpleName(String, List, Collection)} for resolving simple names to fully qualifying names
22+
* Provides {@link #resolveSimpleNameToClass(String, ClassAst.SimpleImpl, Collection)} for resolving simple names to fully qualifying names
2223
* @author TeamworkGuy2
2324
* @since 2015-12-8
2425
*/
@@ -31,16 +32,13 @@ public void addCompilationUnit(List<String> fullyQualifyingName, T_CODE_FILE cla
3132
String fullName = NameUtil.joinFqName(fullyQualifyingName);
3233
entryByFullyQualifyingName.put(fullName, classUnit);
3334

34-
// loop through the fully qualifying name parts and add the new compilation unit to each namespace set
35-
String partialName = "";
36-
for(String namePart : fullyQualifyingName) {
37-
partialName = NameUtil.appendToFqName(partialName, namePart);
38-
List<T_CODE_FILE> nsCompilationUnits = entriesByNamespaces.get(partialName);
39-
if(nsCompilationUnits == null) {
40-
entriesByNamespaces.put(partialName, nsCompilationUnits = new ArrayList<>());
41-
}
42-
nsCompilationUnits.add(classUnit);
35+
// add the compilation unit to its namespace set
36+
var namespace = NameUtil.joinFqNameExceptLast(fullyQualifyingName);
37+
List<T_CODE_FILE> nsCompilationUnits = entriesByNamespaces.get(namespace);
38+
if(nsCompilationUnits == null) {
39+
entriesByNamespaces.put(namespace, nsCompilationUnits = new ArrayList<>());
4340
}
41+
nsCompilationUnits.add(classUnit);
4442
}
4543

4644

@@ -136,26 +134,26 @@ public T_CLASS resolveClassNameAgainstNamespace(String simpleName, List<String>
136134
}
137135

138136

139-
public List<String> resolveSimpleName(String simpleName, List<List<String>> namespaces, Collection<List<String>> missingNamespacesDst) {
140-
var resolvedClass = resolveClassNameAgainstNamespaces(simpleName, namespaces, missingNamespacesDst);
141-
return resolvedClass != null ? resolvedClass.getSignature().getFullName() : null;
142-
}
143-
144-
145137
public T_CLASS resolveSimpleNameToClass(String simpleName, ClassAst.SimpleImpl<? extends BlockType> classScope, Collection<List<String>> missingNamespacesDst) {
146138
ClassSigSimple classSig = classScope.getSignature();
139+
boolean searchParentNamespaces = classScope.getBlockType().getClass() == CsBlock.class; // TODO workaround for C# supporting class resolution based on the parent namespace of a class
147140

148-
// try resolve using the nested class' parent class
141+
// try resolve using the class name (for nested/in-file classes)
149142
T_CLASS resolvedClass = resolveClassNameAgainstNamespace(simpleName, classSig.getFullName(), missingNamespacesDst);
150143

151-
// try resolve using the class' imports
152-
if(resolvedClass == null) {
153-
resolvedClass = resolveClassNameAgainstNamespaces(simpleName, classScope.getUsingStatements(), missingNamespacesDst);
144+
// try resolve using the class' parent packages/namespaces
145+
var fullNamespace = new ArrayList<>(classSig.getFullName());
146+
while(resolvedClass == null && fullNamespace.size() > 0) {
147+
resolvedClass = resolveClassNameAgainstNamespace(simpleName, NameUtil.allExceptLastFqName(fullNamespace), missingNamespacesDst);
148+
fullNamespace.remove(fullNamespace.size() - 1);
149+
if(!searchParentNamespaces) {
150+
break;
151+
}
154152
}
155153

156-
// try resolve using the class' parent package/namespace
154+
// try resolve using the class' imports
157155
if(resolvedClass == null) {
158-
resolvedClass = resolveClassNameAgainstNamespace(simpleName, NameUtil.allExceptLastFqName(classSig.getFullName()), missingNamespacesDst);
156+
resolvedClass = resolveClassNameAgainstNamespaces(simpleName, classScope.getUsingStatements(), missingNamespacesDst);
159157
}
160158

161159
// TODO support resolution of types that are generic class params
@@ -166,12 +164,6 @@ public T_CLASS resolveSimpleNameToClass(String simpleName, ClassAst.SimpleImpl<?
166164
}
167165

168166

169-
public List<String> resolveSimpleName(String simpleName, ClassAst.SimpleImpl<? extends BlockType> classScope, Collection<List<String>> missingNamespacesDst) {
170-
var resolvedClass = resolveSimpleNameToClass(simpleName, classScope, missingNamespacesDst);
171-
return resolvedClass != null ? resolvedClass.getSignature().getFullName() : null;
172-
}
173-
174-
175167

176168

177169
public static class Simple<T_BLOCK extends BlockType> extends ProjectClassSet<ClassAst.SimpleImpl<T_BLOCK>, CodeFileParsed.Simple<T_BLOCK>> {

0 commit comments

Comments
 (0)