diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/SealedTests.java b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/SealedTests.java index 43c2709da3..c8f8639fdc 100644 --- a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/SealedTests.java +++ b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/SealedTests.java @@ -67,7 +67,7 @@ public void testSealed1() { " }\n" + " final int maxDistanceBetweenServicesInKilometers = 100_000\n" + " @Override int getMaxServiceIntervalInMonths() { return 12 }\n" + - "}", + "}\n", "Truck.groovy", "final class Truck extends Vehicle implements Serviceable {\n" + @@ -78,7 +78,7 @@ public void testSealed1() { " }\n" + " final int maxDistanceBetweenServicesInKilometers = 100_000\n" + " @Override int getMaxServiceIntervalInMonths() { return 18 }\n" + - "}", + "}\n", }; //@formatter:on @@ -96,28 +96,95 @@ public void testSealed2() { "Bar.groovy", "class Bar {\n" + // missing "extends Foo" - "}", + "}\n", "Baz.groovy", "class Baz extends Foo {\n" + // missing "final", "sealed" or "non-sealed" - "}", + "}\n", "Boo.groovy", "class Boo extends Foo {\n" + // not permitted - "}", + "}\n", }; //@formatter:on - runNegativeTest(sources, + runNegativeTest(sources, !javaModelSealedSupport() + ? "----------\n" + "1. ERROR in Boo.groovy (at line 1)\n" + "\tclass Boo extends Foo {\n" + "\t ^^^\n" + "Groovy:The class 'Boo' is not a permitted subclass of the sealed class 'Foo'.\n" + + "----------\n" + : + "----------\n" + + "1. ERROR in Foo.groovy (at line 1)\n" + + "\t@groovy.transform.Sealed(permittedSubclasses=[Bar,Baz])\n" + + "\t ^^^\n" + + "Permitted class Bar does not declare Foo as direct super class\n" + + "----------\n" + + "----------\n" + + "1. ERROR in Baz.groovy (at line 1)\n" + + "\tclass Baz extends Foo {\n" + + "\t ^^^\n" + + "The class Baz with a sealed direct superclass or a sealed direct superinterface Foo should be declared either final, sealed, or non-sealed\n" + + "----------\n" + + "----------\n" + + "1. ERROR in Boo.groovy (at line 1)\n" + + "\tclass Boo extends Foo {\n" + + "\t ^^^\n" + + "The class Boo with a sealed direct superclass or a sealed direct superinterface Foo should be declared either final, sealed, or non-sealed\n" + + "----------\n" + + "2. ERROR in Boo.groovy (at line 1)\n" + + "\tclass Boo extends Foo {\n" + + "\t ^^^\n" + + "Groovy:The class 'Boo' is not a permitted subclass of the sealed class 'Foo'.\n" + + "----------\n" + + "3. ERROR in Boo.groovy (at line 1)\n" + + "\tclass Boo extends Foo {\n" + + "\t ^^^\n" + + "The type Boo extending a sealed class Foo should be a permitted subtype of Foo\n" + + "----------\n"); + } + + @Test + public void testSealed3() { + assumeTrue(javaModelSealedSupport()); + + //@formatter:off + String[] sources = { + "p/Foo.groovy", + "package p\n" + + "@groovy.transform.Sealed(permittedSubclasses=[Bar.class,p.Baz])\n" + + "@groovy.transform.PackageScope abstract class Foo {\n" + + "}\n", + + "p/Bar.java", + "package p;\n" + + "final class Bar extends Foo {\n" + + "}\n", + + "p/Baz.java", + "package p;\n" + + "class Baz extends Foo {\n" + // missing "final", "sealed" or "non-sealed" + "}\n", + }; + //@formatter:on + + runNegativeTest(sources, + "----------\n" + + "1. ERROR in p\\Baz.java (at line 2)\n" + + "\tclass Baz extends Foo {\n" + + "\t ^^^\n" + + "The class Baz with a sealed direct superclass or a sealed direct superinterface Foo should be declared either final, sealed, or non-sealed\n" + "----------\n"); } // non-sealed without extends // sealed without permits - // java extension + + private static boolean javaModelSealedSupport() { + return org.eclipse.jdt.core.JavaCore.getPlugin().getBundle().getVersion() + .compareTo(org.osgi.framework.Version.parseVersion("3.24")) >= 0; + } } diff --git a/base/org.codehaus.groovy40/src/org/apache/groovy/parser/antlr4/AstBuilder.java b/base/org.codehaus.groovy40/src/org/apache/groovy/parser/antlr4/AstBuilder.java index d68e3f81e4..f620ced2c8 100644 --- a/base/org.codehaus.groovy40/src/org/apache/groovy/parser/antlr4/AstBuilder.java +++ b/base/org.codehaus.groovy40/src/org/apache/groovy/parser/antlr4/AstBuilder.java @@ -769,10 +769,8 @@ public Tuple2 visitEnhancedForControl(final EnhancedForCo parameter.setColumnNumber(ctx.variableModifiersOpt().getStart().getCharPositionInLine() + 1); parameter.setStart(locationSupport.findOffset(parameter.getLineNumber(), parameter.getColumnNumber())); - Optional var = new ModifierManager(this, this.visitVariableModifiersOpt(ctx.variableModifiersOpt())).get(VAR); - if (var != null && var.isPresent()) { - parameter.setNodeMetaData("reserved.type.name", var.get()); - } + new ModifierManager(this, this.visitVariableModifiersOpt(ctx.variableModifiersOpt())) + .get(VAR).ifPresent(var -> parameter.setNodeMetaData("reserved.type.name", var)); // GRECLIPSE end return tuple(parameter, (Expression) this.visit(ctx.expression())); @@ -1459,7 +1457,7 @@ private void initUsingGenerics(final ClassNode classNode) { public ClassNode visitClassDeclaration(final ClassDeclarationContext ctx) { String packageName = Optional.ofNullable(moduleNode.getPackageName()).orElse(""); String className = this.visitIdentifier(ctx.identifier()); - if (VAR_STR.equals(className)) { + if ("var".equals(className)) { throw createParsingFailedException("var cannot be used for type declarations", ctx.identifier()); } @@ -1496,31 +1494,29 @@ public ClassNode visitClassDeclaration(final ClassDeclarationContext ctx) { } } - List modifierNodeList = ctx.getNodeMetaData(TYPE_DECLARATION_MODIFIERS); - Objects.requireNonNull(modifierNodeList, "modifierNodeList should not be null"); - ModifierManager modifierManager = new ModifierManager(this, modifierNodeList); + ModifierManager modifierManager = new ModifierManager(this, ctx.getNodeMetaData(TYPE_DECLARATION_MODIFIERS)); - Optional finalModifierNodeOptional = modifierManager.get(FINAL); - Optional sealedModifierNodeOptional = modifierManager.get(SEALED); - Optional nonSealedModifierNodeOptional = modifierManager.get(NON_SEALED); - boolean isFinal = finalModifierNodeOptional.isPresent(); - boolean isSealed = sealedModifierNodeOptional.isPresent(); - boolean isNonSealed = nonSealedModifierNodeOptional.isPresent(); + Optional finalModifier = modifierManager.get(FINAL); + Optional sealedModifier = modifierManager.get(SEALED); + Optional nonSealedModifier = modifierManager.get(NON_SEALED); + boolean isFinal = finalModifier.isPresent(); + boolean isSealed = sealedModifier.isPresent(); + boolean isNonSealed = nonSealedModifier.isPresent(); boolean isRecord = asBoolean(ctx.RECORD()); boolean hasRecordHeader = asBoolean(ctx.formalParameters()); if (isRecord) { - if (asBoolean(ctx.EXTENDS())) { - throw createParsingFailedException("No extends clause allowed for record declaration", ctx.EXTENDS()); - } if (!hasRecordHeader) { throw createParsingFailedException("header declaration of record is expected", ctx.identifier()); } + if (asBoolean(ctx.EXTENDS())) { + throw createParsingFailedException("No extends clause allowed for record declaration", ctx.EXTENDS()); + } if (isSealed) { - throw createParsingFailedException("`sealed` is not allowed for record declaration", sealedModifierNodeOptional.get()); + throw createParsingFailedException("`sealed` is not allowed for record declaration", sealedModifier.get()); } if (isNonSealed) { - throw createParsingFailedException("`non-sealed` is not allowed for record declaration", nonSealedModifierNodeOptional.get()); + throw createParsingFailedException("`non-sealed` is not allowed for record declaration", nonSealedModifier.get()); } } else { if (hasRecordHeader) { @@ -1529,15 +1525,15 @@ public ClassNode visitClassDeclaration(final ClassDeclarationContext ctx) { } if (isSealed && isNonSealed) { - throw createParsingFailedException("type cannot be defined with both `sealed` and `non-sealed`", nonSealedModifierNodeOptional.get()); + throw createParsingFailedException("type cannot be defined with both `sealed` and `non-sealed`", nonSealedModifier.get()); } if (isFinal && (isSealed || isNonSealed)) { - throw createParsingFailedException("type cannot be defined with both " + (isSealed ? "`sealed`" : "`non-sealed`") + " and `final`", finalModifierNodeOptional.get()); + throw createParsingFailedException("type cannot be defined with both " + (isSealed ? "`sealed`" : "`non-sealed`") + " and `final`", finalModifier.get()); } if ((isAnnotation || isEnum) && (isSealed || isNonSealed)) { - ModifierNode mn = isSealed ? sealedModifierNodeOptional.get() : nonSealedModifierNodeOptional.get(); + ModifierNode mn = isSealed ? sealedModifier.get() : nonSealedModifier.get(); throw createParsingFailedException("modifier `" + mn.getText() + "` is not allowed for " + (isEnum ? "enum" : "annotation definition"), mn); } @@ -1579,7 +1575,16 @@ public ClassNode visitClassDeclaration(final ClassDeclarationContext ctx) { configureAST(classNode, ctx); // GRECLIPSE add ASTNode nameNode = configureAST(new ConstantExpression(className), ctx.identifier()); - classNode.setNameStart(nameNode.getStart()); classNode.setNameEnd(nameNode.getEnd() - 1); + classNode.setNameStart(nameNode.getStart()); classNode.setNameEnd(nameNode.getEnd()-1); + // keep track of restricted identifiers for highlighting + if (isRecord || isSealed || isNonSealed || hasPermits) { + List list = new ArrayList<>(4); + if (isSealed) list.add(sealedModifier.get()); + if (isNonSealed) list.add(nonSealedModifier.get()); + if (isRecord) list.add(configureAST(new ASTNode(), ctx.RECORD())); + if (hasPermits) list.add(configureAST(new ASTNode(), ctx.PERMITS())); + classNode.putNodeMetaData("special.keyword", Collections.unmodifiableList(list)); + } // GRECLIPSE end classNode.setSyntheticPublic(syntheticPublic); classNode.setGenericsTypes(this.visitTypeParameters(ctx.typeParameters())); @@ -1661,12 +1666,8 @@ public ClassNode visitClassDeclaration(final ClassDeclarationContext ctx) { ctx.classBody().putNodeMetaData(CLASS_DECLARATION_CLASS_NODE, classNode); this.visitClassBody(ctx.classBody()); if (isRecord) { - Optional fieldNodeOptional = - classNode.getFields().stream() - .filter(f -> !isTrue(f, IS_RECORD_GENERATED) && !f.isStatic()).findFirst(); - if (fieldNodeOptional.isPresent()) { - createParsingFailedException("Instance field is not allowed in `record`", fieldNodeOptional.get()); - } + classNode.getFields().stream().filter(f -> !isTrue(f, IS_RECORD_GENERATED) && !f.isStatic()).findFirst() + .ifPresent(fn -> createParsingFailedException("Instance field is not allowed in `record`", fn)); } classNodeStack.pop(); @@ -2309,10 +2310,8 @@ private DeclarationListStatement createMultiAssignmentDeclarationListStatement(f ), this.createGroovyTokenByType(ctx.ASSIGN().getSymbol(), Types.ASSIGN), this.visitVariableInitializer(ctx.variableInitializer())); - Optional var = modifierManager.get(VAR); - if (var != null && var.isPresent()) { - de.setNodeMetaData("reserved.type.name", var.get()); - } + modifierManager.get(VAR).ifPresent(var -> + de.setNodeMetaData("reserved.type.name", var)); configureAST(modifierManager.attachAnnotations(de), ctx); return configureAST(new DeclarationListStatement(de), ctx); // GRECLIPSE end @@ -2343,15 +2342,11 @@ public DeclarationListStatement visitVariableDeclaration(final VariableDeclarati declarationExpressionList.forEach(e -> { VariableExpression variableExpression = (VariableExpression) e.getLeftExpression(); - - modifierManager.processVariableExpression(variableExpression); - modifierManager.attachAnnotations(e); // GRECLIPSE add - Optional var = modifierManager.get(VAR); - if (var != null && var.isPresent()) { - e.setNodeMetaData("reserved.type.name", var.get()); - } + modifierManager.get(VAR).ifPresent(var -> e.setNodeMetaData("reserved.type.name", var)); // GRECLIPSE end + modifierManager.processVariableExpression(variableExpression); + modifierManager.attachAnnotations(e); }); int size = declarationExpressionList.size(); @@ -5475,7 +5470,6 @@ public List getDeclarationExpressions() { private static final String SQ_STR = "'"; private static final String DQ_STR = "\""; private static final String DOLLAR_SLASH_STR = "$/"; - private static final String VAR_STR = "var"; private static final Map QUOTATION_MAP = Maps.of( DQ_STR, DQ_STR, diff --git a/base/org.eclipse.jdt.groovy.core/src/org/codehaus/jdt/groovy/internal/compiler/ast/GroovyCompilationUnitDeclaration.java b/base/org.eclipse.jdt.groovy.core/src/org/codehaus/jdt/groovy/internal/compiler/ast/GroovyCompilationUnitDeclaration.java index a5adc8940f..8551a2b8b8 100644 --- a/base/org.eclipse.jdt.groovy.core/src/org/codehaus/jdt/groovy/internal/compiler/ast/GroovyCompilationUnitDeclaration.java +++ b/base/org.eclipse.jdt.groovy.core/src/org/codehaus/jdt/groovy/internal/compiler/ast/GroovyCompilationUnitDeclaration.java @@ -1136,6 +1136,9 @@ private void createTypeDeclarations(ModuleNode moduleNode) { if (!isEnum && !isTrait(classNode)) configureSuperClass(typeDeclaration, isRecord(classNode) ? ClassHelper.make("java.lang.Record") : classNode.getSuperClass()); configureSuperInterfaces(typeDeclaration, classNode); + if (isSealed(classNode)) + configurePermittedSubtypes(typeDeclaration, classNode); + typeDeclaration.fields = createFieldDeclarations(classNode, isEnum); typeDeclaration.methods = createConstructorAndMethodDeclarations(classNode, isEnum, typeDeclaration); @@ -1342,9 +1345,9 @@ private void createConstructorDeclarations(ClassNode classNode, boolean isEnum, Parameter[] components = classNode.getNodeMetaData("_RECORD_HEADER"); if (components != null) { if (classNode.getDeclaredConstructor(components) == null) { - ConstructorDeclaration constructorDecl = new ConstructorDeclaration(unitDeclaration.compilationResult); + ConstructorDeclaration constructorDecl = new ConstructorDeclaration(unitDeclaration.compilationResult); // TODO: TypeDeclaration.createDefaultConstructorForRecord() constructorDecl.arguments = createArguments(components); - constructorDecl.bits |= /*ASTNode.IsCanonicalConstructor*/0x200; + constructorDecl.bits |= ASTNode.Bit10;//ASTNode.IsCanonicalConstructor //constructorDecl.bits |= ASTNode.IsImplicit; //constructorDecl.constructorCall = SuperReference.implicitSuperConstructorCall(); constructorDecl.modifiers = getModifiers(classNode) & ExtraCompilerModifiers.AccVisibilityMASK; @@ -1563,15 +1566,52 @@ private void configureSuperClass(TypeDeclaration typeDeclaration, ClassNode supe private void configureSuperInterfaces(TypeDeclaration typeDeclaration, ClassNode classNode) { ClassNode[] interfaces = classNode.getInterfaces(); if (interfaces != null && interfaces.length > 0) { - typeDeclaration.superInterfaces = new TypeReference[interfaces.length]; - for (int i = 0, n = interfaces.length; i < n; i += 1) { - typeDeclaration.superInterfaces[i] = createTypeReferenceForClassNode(interfaces[i]); - } + typeDeclaration.superInterfaces = createTypeReferencesForClassNodes(interfaces); } else { typeDeclaration.superInterfaces = new TypeReference[0]; } } + private void configurePermittedSubtypes(TypeDeclaration typeDeclaration, ClassNode classNode) { + java.util.function.Function createTypeReference = e -> { + if (e instanceof ClassExpression) { + return createTypeReferenceForClassNode(e.getType()); + } + if (e instanceof VariableExpression) { + char[] simpleName = e.getText().toCharArray(); // assume it's a type name + TypeReference t = verify(new SingleTypeReference(simpleName, toPos(e.getStart(), e.getEnd() - 1))); + t.bits |= ASTNode.IgnoreRawTypeCheck; + return t; + } + if (e instanceof PropertyExpression) { + PropertyExpression p = (PropertyExpression) e; + if ("class".equals(p.getPropertyAsString())) { + return createTypeReferenceForClassLiteral(p); + } + char[][] compoundName = CharOperation.splitOn('.', e.getText().toCharArray()); // assume it's a type name + TypeReference t = new QualifiedTypeReference(compoundName, positionsFor(compoundName, e.getStart(), e.getEnd())); + t.bits |= ASTNode.IgnoreRawTypeCheck; + return t; + } + Util.log(IStatus.WARNING, "Unhandled reference type: " + e.getClass().getName()); + return null; + }; + + for (AnnotationNode annotation : classNode.getAnnotations()) { + if (isType("groovy.transform.Sealed", annotation.getClassNode().getName())) { + Expression permitsSpecification = annotation.getMember("permittedSubclasses"); + if (permitsSpecification instanceof ListExpression) { // typeDeclaration.permittedTypes = ... + TypeReference[] permittedTypes = ((ListExpression) permitsSpecification).getExpressions() + .stream().map(createTypeReference).toArray(TypeReference[]::new); + ReflectionUtils.setPrivateField(TypeDeclaration.class, "permittedTypes", typeDeclaration, permittedTypes); + } else if (permitsSpecification != null) { + TypeReference[] permittedTypes = {createTypeReference.apply(permitsSpecification)}; + ReflectionUtils.setPrivateField(TypeDeclaration.class, "permittedTypes", typeDeclaration, permittedTypes); + } + } + } + } + private Annotation[] createAnnotations(List groovyAnnotations) { if (groovyAnnotations != null && !groovyAnnotations.isEmpty()) { List annotations = new ArrayList<>(groovyAnnotations.size()); @@ -2359,14 +2399,18 @@ private int endOffset(org.codehaus.groovy.ast.ASTNode node) { private int getModifiers(ClassNode node) { int modifiers = node.getModifiers(); if (isRecord(node)) { - modifiers |= /*3.26+:Flags.AccRecord*/0x1000000; + modifiers |= ASTNode.Bit25; //3.26+:Flags.AccRecord } else if (isTrait(node)) { modifiers |= Flags.AccInterface; } + if (isSealed(node)) { + modifiers |= ASTNode.Bit29; //3.24+:Flags.AccSealed + } else if (isNonSealed(node)) { + modifiers |= ASTNode.Bit27; //3.24+:Flags.AccNonSealed + } if (node.isInterface()) { modifiers &= ~Flags.AccAbstract; - } - if (node.isEnum()) { + } else if (node.isEnum()) { modifiers &= ~(Flags.AccAbstract | Flags.AccFinal); } if (!(node instanceof InnerClassNode)) { @@ -2382,10 +2426,13 @@ private int getModifiers(ClassNode node) { private int getModifiers(FieldNode node) { int modifiers = node.getModifiers(); + // native and non-native (aka emulated) record fields are final + if (node.getDeclaringClass().getAnnotations().stream().anyMatch(annotation -> + isType("groovy.transform.RecordType", annotation.getClassNode().getName()))) { + modifiers |= Flags.AccFinal; + } if (node.getDeclaringClass().getProperty(node.getName()) != null && hasPackageScopeXform(node, PackageScopeTarget.FIELDS)) { modifiers &= ~Flags.AccPrivate; - } else if (isRecord(node.getDeclaringClass())) { - modifiers |= Flags.AccFinal; } return modifiers; } @@ -2460,24 +2507,23 @@ private boolean isAliasForType(ImportReference importReference, char[] typeName) return false; } + private boolean isNonSealed(ClassNode classNode) { + return classNode.getAnnotations().stream().anyMatch(annotation -> + isType("groovy.transform.NonSealed", annotation.getClassNode().getName())); + } + private boolean isRecord(ClassNode classNode) { + // TODO: support @groovy.transform.RecordType(mode=NATIVE) return classNode.getNodeMetaData("_RECORD_HEADER") != null; } - private boolean isTrait(ClassNode classNode) { - return unitDeclaration.traitHelper.isTrait(classNode); + private boolean isSealed(ClassNode classNode) { + return classNode.getAnnotations().stream().anyMatch(annotation -> + isType("groovy.transform.Sealed", annotation.getClassNode().getName())); } - /** - * @return true if this is varargs, using the same definition as in AsmClassGenerator.isVargs(Parameter[]) - */ - private boolean isVargs(Parameter[] parameters) { - if (parameters.length == 0) { - return false; - } - Parameter last = parameters[parameters.length - 1]; - ClassNode type = last.getType(); - return type.isArray(); + private boolean isTrait(ClassNode classNode) { + return unitDeclaration.traitHelper.isTrait(classNode); } /** @@ -2506,6 +2552,18 @@ private boolean isType(String expect, String actual) { return false; } + /** + * @return true if this is varargs, using the same definition as in AsmClassGenerator.isVargs(Parameter[]) + */ + private boolean isVargs(Parameter[] parameters) { + if (parameters.length == 0) { + return false; + } + Parameter last = parameters[parameters.length - 1]; + ClassNode type = last.getType(); + return type.isArray(); + } + /** * Ensures lexical ordering and synthetic de-duplication. */ @@ -2631,6 +2689,9 @@ private void fixupSourceLocationsForTypeDeclaration(GroovyTypeDeclaration typeDe if (isRecord(classNode)) { //typeDeclaration.restrictedIdentifierStart = classNode.getStart(); ReflectionUtils.setPrivateField(TypeDeclaration.class, "restrictedIdentifierStart", typeDeclaration, classNode.getStart()); + } else if (isSealed(classNode)) { + //typeDeclaration.restrictedIdentifierStart = classNode.getNameEnd()+2; + ReflectionUtils.setPrivateField(TypeDeclaration.class, "restrictedIdentifierStart", typeDeclaration, classNode.getNameEnd()+2); } } } @@ -2881,7 +2942,7 @@ private AnonInnerFinder(Object enclosingDecl) { public void visitMethodNode(MethodNode node) { node.getCode().visit(this); if (node.hasDefaultValue()) { - Arrays.stream(node.getParameters()) + Stream.of(node.getParameters()) .filter(Parameter::hasInitialExpression) .forEach(p -> p.getInitialExpression().visit(this)); } diff --git a/ide-test/org.codehaus.groovy.eclipse.tests/src/org/codehaus/groovy/eclipse/test/ui/SemanticHighlightingTests.groovy b/ide-test/org.codehaus.groovy.eclipse.tests/src/org/codehaus/groovy/eclipse/test/ui/SemanticHighlightingTests.groovy index 16a709f0c9..03deb3f780 100644 --- a/ide-test/org.codehaus.groovy.eclipse.tests/src/org/codehaus/groovy/eclipse/test/ui/SemanticHighlightingTests.groovy +++ b/ide-test/org.codehaus.groovy.eclipse.tests/src/org/codehaus/groovy/eclipse/test/ui/SemanticHighlightingTests.groovy @@ -1566,7 +1566,7 @@ final class SemanticHighlightingTests extends GroovyEclipseTestSuite { |} |non-sealed class Bar extends Foo { |} - |class Baz { + |final class Baz { |} |'''.stripMargin() @@ -1576,13 +1576,37 @@ final class SemanticHighlightingTests extends GroovyEclipseTestSuite { new HighlightedTypedPosition(contents.indexOf('Baz'), 3, CLASS), new HighlightedTypedPosition(contents.lastIndexOf('Foo'), 3, CLASS), new HighlightedTypedPosition(contents.lastIndexOf('Bar'), 3, CLASS), - new HighlightedTypedPosition(contents.lastIndexOf('Baz'), 3, CLASS)/*, - new HighlightedTypedPosition(contents.indexOf('sealed'), 6, RESERVED), - new HighlightedTypedPosition(contents.indexOf('permits'), 7, RESERVED), - new HighlightedTypedPosition(contents.indexOf('non-sealed'), 10, RESERVED)*/) + new HighlightedTypedPosition(contents.lastIndexOf('Baz'), 3, CLASS), + new HighlightedTypedPosition(contents.indexOf('sealed'), 6, KEYWORD), + new HighlightedTypedPosition(contents.indexOf('permits'), 7, KEYWORD), + new HighlightedTypedPosition(contents.indexOf('non-sealed'), 10, KEYWORD)) } - @Test // https://issues.apache.org/jira/browse/GROOVY-9630 + @Test // GROOVY-10433 + void testSpecialName() { + String contents = '''\ + |int _; + |int non; + |int var; + |int record; + |int sealed; + |int permits; + |f(non-sealed) + |'''.stripMargin() + + assertHighlighting(contents, + new HighlightedTypedPosition(contents.indexOf('_'), 1, VARIABLE), + new HighlightedTypedPosition(contents.indexOf('non'), 3, VARIABLE), + new HighlightedTypedPosition(contents.indexOf('var'), 3, VARIABLE), + new HighlightedTypedPosition(contents.indexOf('record'), 6, VARIABLE), + new HighlightedTypedPosition(contents.indexOf('sealed'), 6, VARIABLE), + new HighlightedTypedPosition(contents.indexOf('permits'), 7, VARIABLE), + new HighlightedTypedPosition(contents.indexOf('f'), 1, UNKNOWN ), + new HighlightedTypedPosition(contents.lastIndexOf('non'), 3, VARIABLE), + new HighlightedTypedPosition(contents.lastIndexOf('sealed'), 6, VARIABLE)) + } + + @Test // GROOVY-9630 void testVarKeyword0() { String contents = '''\ |def var @@ -1590,7 +1614,7 @@ final class SemanticHighlightingTests extends GroovyEclipseTestSuite { |'''.stripMargin() assertHighlighting(contents, - new HighlightedTypedPosition(contents.indexOf('var'), 3, VARIABLE), + new HighlightedTypedPosition(contents.indexOf('var'), 3, VARIABLE), new HighlightedTypedPosition(contents.lastIndexOf('var'), 3, VARIABLE)) } diff --git a/ide/org.codehaus.groovy.eclipse.ui/src/org/codehaus/groovy/eclipse/editor/highlighting/SemanticHighlightingReferenceRequestor.java b/ide/org.codehaus.groovy.eclipse.ui/src/org/codehaus/groovy/eclipse/editor/highlighting/SemanticHighlightingReferenceRequestor.java index 1347274bf2..2fe31a8141 100644 --- a/ide/org.codehaus.groovy.eclipse.ui/src/org/codehaus/groovy/eclipse/editor/highlighting/SemanticHighlightingReferenceRequestor.java +++ b/ide/org.codehaus.groovy.eclipse.ui/src/org/codehaus/groovy/eclipse/editor/highlighting/SemanticHighlightingReferenceRequestor.java @@ -136,9 +136,9 @@ public VisitStatus acceptASTNode(ASTNode node, TypeLookupResult result, IJavaEle checkOuterClass(result.type, outer -> { acceptASTNode(outer, new TypeLookupResult(outer, outer, outer, TypeLookupResult.TypeConfidence.EXACT, result.scope), enclosingElement); }); - } else if (node.getNodeMetaData("_RECORD_HEADER") != null && unitLength() > node.getEnd()) { - int offset = CharOperation.indexOf("record".toCharArray(), contents, true, node.getStart(), ((ClassNode) node).getNameStart()); - if (offset >= 0) typedPositions.add(new HighlightedTypedPosition(offset, "record".length(), HighlightKind.KEYWORD)); + } else if (node.getNodeMetaData("special.keyword") != null) { + Iterable words = node.getNodeMetaData("special.keyword"); + words.forEach(word -> typedPositions.add(new HighlightedTypedPosition(word.getStart(), word.getLength(), HighlightKind.KEYWORD))); } if (!(enclosingElement instanceof IImportDeclaration || ClassHelper.isPrimitiveType((ClassNode) node) || ((ClassNode) node).isScriptBody())) { pos = handleClassReference((ClassNode) node, result.type);