Fixed loop vars like for(var x in list) declared in top level outside of methods being declared duplicate

This commit is contained in:
bigguy345 committed 2026-03-07 15:48:10 +02:00
1 parent ec885d5ddb
commit c00ad8e0a7
2 files changed
+136 -4

No files matched your search

@@ -19,7 +19,7 @@ class LoopVariableParser {
"\\bfor\\s*\\(\\s*(?:var|let|const)\\s+(\\w+)\\s+in\\s+([^)]+)\\)");
private static final Pattern FOR_OF = Pattern.compile(
"\\bfor\\s*\\(\\s*(?:var|let|const)\\s+(\\w+)\\s+of\\s+([^)]+)\\)");
private static final Pattern FOR_IN_OF_LOOKAHEAD = Pattern.compile("^\\s+(?:in|of)\\s");
static final Pattern FOR_IN_OF_LOOKAHEAD = Pattern.compile("^\\s+(?:in|of)\\s");
private static final Set<String> SKIP_TYPES = new HashSet<>(Arrays.asList(
"return", "if", "while", "for", "switch", "catch", "new", "throw"));
@@ -63,6 +63,73 @@ class LoopVariableParser {
Map<String, List<FieldInfo>> locals = methodLocals.computeIfAbsent(ownerMethod.getDeclarationOffset(), k -> new HashMap<>());
scanRange(bodyText, bodyStart, bodyEnd, ownerMethod, locals, js);
}
scanTopLevelLoops();
}
private void scanTopLevelLoops() {
boolean js = doc.isJavaScript();
if (!js) {
for (Matcher m = ENHANCED_FOR.matcher(text); m.find(); ) {
int absForPos = m.start();
if (doc.isExcluded(absForPos) || isInsideAnyScope(absForPos)) continue;
String typeName = m.group(1).trim();
String varName = m.group(2);
TypeInfo typeInfo = doc.resolveType(typeName);
ScopeInfo scope = computeForBodyScope(absForPos, 0, text.length());
FieldInfo fi = FieldInfo.localField(varName, typeInfo, m.start(2), null, -1, -1, 0);
fi.setScopeInfo(scope);
doc.addTopLevelLocal(fi);
}
for (Matcher m = CLASSIC_FOR.matcher(text); m.find(); ) {
int absForPos = m.start();
if (doc.isExcluded(absForPos) || isInsideAnyScope(absForPos)) continue;
String typeName = m.group(1).trim();
String varName = m.group(2);
if (SKIP_TYPES.contains(typeName)) continue;
TypeInfo typeInfo = doc.resolveType(typeName);
ScopeInfo scope = computeForBodyScope(absForPos, 0, text.length());
FieldInfo fi = FieldInfo.localField(varName, typeInfo, m.start(2), null, -1, -1, 0);
fi.setScopeInfo(scope);
doc.addTopLevelLocal(fi);
}
} else {
for (Matcher m = FOR_IN.matcher(text); m.find(); ) {
int absForPos = m.start();
if (doc.isExcluded(absForPos) || isInsideAnyScope(absForPos)) continue;
String varName = m.group(1);
ScopeInfo scope = computeForBodyScope(absForPos, 0, text.length());
FieldInfo fi = FieldInfo.localField(varName, TypeInfo.fromPrimitive("int"), m.start(1), null, -1, -1, 0);
fi.setScopeInfo(scope);
doc.addTopLevelLocal(fi);
}
for (Matcher m = FOR_OF.matcher(text); m.find(); ) {
int absForPos = m.start();
if (doc.isExcluded(absForPos) || isInsideAnyScope(absForPos)) continue;
String varName = m.group(1);
String iterableExpr = m.group(2).trim();
TypeInfo iterableType = doc.resolveExpressionType(iterableExpr, absForPos);
TypeInfo elementType = (iterableType != null) ? iterableType.getElementType() : null;
if (elementType == null) elementType = TypeInfo.ANY;
ScopeInfo scope = computeForBodyScope(absForPos, 0, text.length());
FieldInfo fi = FieldInfo.localField(varName, elementType, m.start(1), null, -1, -1, 0);
fi.setScopeInfo(scope);
doc.addTopLevelLocal(fi);
}
}
}
private boolean isInsideAnyScope(int pos) {
for (MethodInfo method : doc.getAllMethods()) {
if (method.containsPosition(pos)) return true;
}
for (InnerCallableScope scope : doc.getInnerScopes()) {
if (scope.containsPosition(pos)) return true;
}
return false;
}
private void scanRange(String bodyText, int bodyStart, int bodyEnd,
@@ -113,6 +113,9 @@ public class ScriptDocument {
private final Map<String, FieldInfo> globalFields = new HashMap<>();
// Local variables per method (methodStartOffset -> {varName -> FieldInfo})
private final Map<Integer, Map<String, List<FieldInfo>>> methodLocals = new HashMap<>();
// Top-level variables (outside methods) - for JavaScript, these can be treated as globals, but we keep them separate for clarity
// Mainly top level loops like for (var x : collection) { } where x is a local variable but not inside a method
private final Map<String, List<FieldInfo>> topLevelLocals = new HashMap<>();
// Inner callable scopes (lambdas, JS function expressions) - NOT in methods list
private final List<InnerCallableScope> innerScopes = new ArrayList<>();
@@ -349,6 +352,7 @@ public class ScriptDocument {
wildcardPackages.clear();
excludedRanges.clear();
methodLocals.clear();
topLevelLocals.clear();
scriptTypes.clear();
innerScopes.clear();
methodCalls.clear();
@@ -1442,6 +1446,36 @@ public class ScriptDocument {
return best;
}
private FieldInfo pickVisibleTopLevelLocal(String name, int position) {
return pickVisibleLocal(topLevelLocals.get(name), position);
}
void addTopLevelLocal(FieldInfo fieldInfo) {
if (fieldInfo == null) {
return;
}
topLevelLocals.computeIfAbsent(fieldInfo.getName(), k -> new ArrayList<>()).add(fieldInfo);
}
private Collection<FieldInfo> getVisibleTopLevelLocals(int position) {
List<FieldInfo> visible = new ArrayList<>();
for (List<FieldInfo> candidates : topLevelLocals.values()) {
FieldInfo best = pickVisibleLocal(candidates, position);
if (best != null) {
visible.add(best);
}
}
return visible;
}
Collection<FieldInfo> getTopLevelLocalCandidates() {
List<FieldInfo> all = new ArrayList<>();
for (List<FieldInfo> fields : topLevelLocals.values()) {
all.addAll(fields);
}
return all;
}
/**
* Parse local variables inside methods/functions - UNIFIED for both Java and JavaScript.
* Stores results in the shared 'methodLocals' map (methodOffset -> varName -> FieldInfo).
@@ -2488,7 +2522,10 @@ public class ScriptDocument {
}
}
if (insideInner) continue;
int afterVar = m.end(1);
if (afterVar < text.length() && LoopVariableParser.FOR_IN_OF_LOOKAHEAD.matcher(text.substring(afterVar)).find()) continue;
String varName = m.group(1);
String initializer = null;
int initializerStart = -1;
@@ -2560,6 +2597,10 @@ public class ScriptDocument {
if (isExcluded(position))
continue;
int enclosingParen = findEnclosingParenStart(0, position);
if (enclosingParen >= 0 && "for".equals(readKeywordBefore(enclosingParen)))
continue;
int modifiers = parseModifiers(typeNameRaw);
String typeName = stripModifiers(typeNameRaw);
@@ -6900,7 +6941,8 @@ public class ScriptDocument {
FieldInfo finalTargetField = targetField;
boolean isScriptField = targetField != null &&
(globalFields.containsValue(targetField) ||
methodLocals.values().stream().anyMatch(m -> m.values().stream().anyMatch(list -> list.contains(finalTargetField))));
methodLocals.values().stream().anyMatch(m -> m.values().stream().anyMatch(list -> list.contains(finalTargetField))) ||
topLevelLocals.values().stream().anyMatch(list -> list.contains(finalTargetField)));
// Determine if the target field is final
// For script fields, use the modifiers; for external fields, use reflection
@@ -7653,7 +7695,10 @@ public class ScriptDocument {
}
}
// 3. For Java only: Check if we're inside a script type and look for fields there
FieldInfo topLocal = pickVisibleTopLevelLocal(name, position);
if (topLocal != null) return topLocal;
// 4. For Java only: Check if we're inside a script type and look for fields there
if (!isJavaScript()) {
ScriptTypeInfo enclosingType = findEnclosingScriptType(position);
if (enclosingType != null && enclosingType.hasField(name)) {
@@ -7882,6 +7927,14 @@ public class ScriptDocument {
}
}
}
for (List<FieldInfo> fields : topLevelLocals.values()) {
for (FieldInfo field : fields) {
AssignmentInfo assign = field.findAssignmentAtPosition(position);
if (assign != null) {
return assign;
}
}
}
for (AssignmentInfo assign : declarationErrors) {
if (assign.containsLhsPosition(position)) {
@@ -7974,6 +8027,8 @@ public class ScriptDocument {
}
}
variables.addAll(getVisibleTopLevelLocals(position));
// Add global fields (only those visible at position)
for (FieldInfo globalField : globalFields.values()) {
if (globalField.isVisibleAt(position)) {
@@ -8037,6 +8092,11 @@ public class ScriptDocument {
}
}
}
for (List<FieldInfo> fields : topLevelLocals.values()) {
for (FieldInfo field : fields) {
errored.addAll(field.getErroredAssignments());
}
}
// Check external field assignments
for (AssignmentInfo assign : externalFieldAssignments) {
@@ -8232,6 +8292,11 @@ public class ScriptDocument {
}
}
FieldInfo topLevelLocal = pickVisibleTopLevelLocal(varName, position);
if (topLevelLocal != null) {
return topLevelLocal.getTypeInfo();
}
// Check global fields
if (globalFields.containsKey(varName)) {
return globalFields.get(varName).getTypeInfo();