Bug 535274: Allow attributes on namespace definitions

Implementation and tests.

Change-Id: I0cca9dea8630ae66b005856338342b4173c48216
Signed-off-by: Hansruedi Patzen <hansruedi.patzen@hsr.ch>
Signed-off-by: Thomas Corbat <tcorbat@hsr.ch>
diff --git a/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/core/parser/tests/ast2/AST2CPPAttributeTests.java b/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/core/parser/tests/ast2/AST2CPPAttributeTests.java
index a1133c8..6779f5d 100644
--- a/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/core/parser/tests/ast2/AST2CPPAttributeTests.java
+++ b/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/core/parser/tests/ast2/AST2CPPAttributeTests.java
@@ -44,6 +44,7 @@
 import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTFunctionDeclarator;
 import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTFunctionDefinition;
 import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTIfStatement;
+import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTNamespaceDefinition;
 import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTParameterDeclaration;
 import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTReferenceOperator;
 import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTSimpleDeclSpecifier;
@@ -537,4 +538,22 @@
 		IASTTranslationUnit tu = parseAndCheckBindings(getAboveComment(), ParserLanguage.CPP, true);
 		checkAttributeRelations(getAttributeSpecifiers(tu), ICPPASTParameterDeclaration.class, ICPPASTSimpleDeclSpecifier.class);
 	}
+
+	//namespace [[attr]] NS {}
+	public void testAttributedNamedNamespace_Bug535274() throws Exception {
+		IASTTranslationUnit tu = parseAndCheckBindings(getAboveComment(), ParserLanguage.CPP, true);
+		checkAttributeRelations(getAttributeSpecifiers(tu), ICPPASTNamespaceDefinition.class);
+	}
+
+	//namespace [[attr]] {}
+	public void testAttributedUnnamedNamespace_Bug535274() throws Exception {
+		IASTTranslationUnit tu = parseAndCheckBindings(getAboveComment(), ParserLanguage.CPP, true);
+		checkAttributeRelations(getAttributeSpecifiers(tu), ICPPASTNamespaceDefinition.class);
+	}
+
+	//namespace NS __attribute__((__visibility__("default"))) {}
+	public void testGnuAndCppMixedAttributedNamedNamespace_Bug535274() throws Exception {
+		IASTTranslationUnit tu = parseAndCheckBindings(getAboveComment(), ParserLanguage.CPP, true);
+		checkAttributeRelations(getAttributeSpecifiers(tu), ICPPASTNamespaceDefinition.class);
+	}
 }
diff --git a/core/org.eclipse.cdt.core.tests/resources/rewrite/ASTWriterDeclarationTestSource.awts b/core/org.eclipse.cdt.core.tests/resources/rewrite/ASTWriterDeclarationTestSource.awts
index 4edde1b..09f789e 100644
--- a/core/org.eclipse.cdt.core.tests/resources/rewrite/ASTWriterDeclarationTestSource.awts
+++ b/core/org.eclipse.cdt.core.tests/resources/rewrite/ASTWriterDeclarationTestSource.awts
@@ -178,3 +178,19 @@
 void f([[attr1]] int p1, int [[attr2]] p2, [[attr3]] int p3)
 {
 }
+
+//!Attributed anonymous namespace
+//%CPP
+namespace [[foo]] 
+{
+
+
+}
+
+//!Mixed CPP and GNU attributes on named namespace
+//%CPP GNU
+namespace [[foo]] FOO __attribute__((__visibility__("default")))
+{
+
+
+}
diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/AbstractGNUSourceCodeParser.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/AbstractGNUSourceCodeParser.java
index b5159d8..1b5fa01 100644
--- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/AbstractGNUSourceCodeParser.java
+++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/AbstractGNUSourceCodeParser.java
@@ -2439,8 +2439,8 @@
     		return null;
 
     	IASTAttributeList result = nodeFactory.newGCCAttributeList();
-		final int startOffset = consume().getOffset();
-    	int endOffset = startOffset;
+    	final int startOffset = LA().getOffset();
+    	int endOffset = consume().getEndOffset();
     	if (LT(1) == IToken.tLPAREN) {
     		consume();
     		consume(IToken.tLPAREN);
@@ -2464,8 +2464,7 @@
     		consumeOrEOC(IToken.tRPAREN);
     		endOffset = consumeOrEOC(IToken.tRPAREN).getEndOffset();
     	}
-    	setRange(result, startOffset, endOffset);
-    	return result;
+    	return setRange(result, startOffset, endOffset);
     }
 
 	protected IASTAttribute singleAttribute() throws EndOfFileException, BacktrackException {
diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/CPPASTNamespaceDefinition.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/CPPASTNamespaceDefinition.java
index 92ac1a8..524e14f 100644
--- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/CPPASTNamespaceDefinition.java
+++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/CPPASTNamespaceDefinition.java
@@ -125,6 +125,9 @@
 	        }
 		}
 
+		if (!acceptByCPPAttributeSpecifiers(action))
+			return false;
+
 		if (fName != null && !fName.accept(action))
 			return false;
 
diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/GNUCPPSourceParser.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/GNUCPPSourceParser.java
index 4cde7a5..0be1add 100644
--- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/GNUCPPSourceParser.java
+++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/GNUCPPSourceParser.java
@@ -2676,6 +2676,8 @@
 		}
 		consume(IToken.t_namespace);
 
+		List<IASTAttributeSpecifier> attributeSpecifiers = attributeSpecifierSeq();
+
 		// optional name
 		ICPPASTName name = null;
 		if (LT(1) == IToken.tIDENTIFIER) {
@@ -2686,7 +2688,7 @@
 		}
 
 		// bug 195701, gcc 4.2 allows visibility attribute for namespaces.
-		List<IASTAttributeSpecifier> attributeSpecifiers = __attribute_decl_seq(true, false);
+		attributeSpecifiers = CollectionUtils.merge(attributeSpecifiers, __attribute_decl_seq(true, false));
 
 		if (LT(1) == IToken.tLBRACE) {
 			ICPPASTNamespaceDefinition outer = null;
diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/semantics/CPPVisitor.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/semantics/CPPVisitor.java
index 370a745..f71a7bb 100644
--- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/semantics/CPPVisitor.java
+++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/cpp/semantics/CPPVisitor.java
@@ -3088,4 +3088,15 @@
 		ArrayUtil.reverse(operands);
 		return operands;
 	}
+
+	/**
+	 * Determines whether the given {@code namespace} definition denotes
+	 * an anonymous namespace.
+	 * @param namespace
+	 * @return {@code true} if the {@code namespace} is anonymous, false otherwise
+	 */
+	public static boolean isAnonymousNamespace(ICPPASTNamespaceDefinition namespace) {
+		IASTName name = namespace.getName();
+		return name == null || name.toString().isEmpty();
+	}
 }
diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/astwriter/DeclarationWriter.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/astwriter/DeclarationWriter.java
index 27aada0..e1eb215 100644
--- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/astwriter/DeclarationWriter.java
+++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/astwriter/DeclarationWriter.java
@@ -207,6 +207,7 @@
 			scribe.printStringSpace(Keywords.INLINE);
 		}
 		scribe.printStringSpace(Keywords.NAMESPACE);
+		writeCPPAttributes(namespaceDefinition, EnumSet.of(SpaceLocation.AFTER));
 		namespaceDefinition.getName().accept(visitor);
 		writeGCCAttributes(namespaceDefinition, EnumSet.of(SpaceLocation.BEFORE));
 		if (!hasTrailingComments(namespaceDefinition.getName())) {
diff --git a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/formatter/CodeFormatterVisitor.java b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/formatter/CodeFormatterVisitor.java
index 16fef87..4971c06 100644
--- a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/formatter/CodeFormatterVisitor.java
+++ b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/formatter/CodeFormatterVisitor.java
@@ -18,6 +18,7 @@
 import java.util.EmptyStackException;
 import java.util.List;
 import java.util.Stack;
+import java.util.function.Predicate;
 
 import org.eclipse.cdt.core.CCorePlugin;
 import org.eclipse.cdt.core.dom.ast.ASTVisitor;
@@ -104,6 +105,7 @@
 import org.eclipse.cdt.core.dom.ast.c.ICASTDesignatedInitializer;
 import org.eclipse.cdt.core.dom.ast.c.ICASTDesignator;
 import org.eclipse.cdt.core.dom.ast.c.ICASTVisitor;
+import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTAttributeList;
 import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTBinaryExpression;
 import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCastExpression;
 import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCatchHandler;
@@ -149,6 +151,7 @@
 import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTVisibilityLabel;
 import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTVisitor;
 import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTWhileStatement;
+import org.eclipse.cdt.core.dom.ast.gnu.IGCCASTAttributeList;
 import org.eclipse.cdt.core.dom.ast.gnu.c.ICASTKnRFunctionDeclarator;
 import org.eclipse.cdt.core.formatter.DefaultCodeFormatterConstants;
 import org.eclipse.cdt.core.formatter.DefaultCodeFormatterOptions;
@@ -1103,7 +1106,13 @@
 		// namespace <name>
 		scribe.printNextToken(Token.t_namespace, false);
 		scribe.space();
-		node.getName().accept(this);
+		formatLeadingAttributes(node, ICPPASTAttributeList.class::isInstance);
+		boolean isNamedNamespace = !CPPVisitor.isAnonymousNamespace(node);
+		if (isNamedNamespace) {
+			IASTName name = node.getName();
+			name.accept(this);
+		}
+		formatAttributes(node, isNamedNamespace, false, IGCCASTAttributeList.class::isInstance);
 
 		// member declarations
 		IASTDeclaration[] memberDecls= node.getDeclarations();
@@ -1537,13 +1546,22 @@
 		return -1;
 	}
 
+	private void formatLeadingAttributes(IASTAttributeOwner owner) {
+		formatAttributes(owner, false, true);
+	}
+
 	/**
 	 * Formats the attributes leading a node.
 	 * Same as {@code formatAttributes(owner, false, true);}
 	 * @param owner Node containing attributes
+	 * @param filter Filter predicate for specifying which attributes to print
 	 */
-	private void formatLeadingAttributes(IASTAttributeOwner owner) {
-		formatAttributes(owner, false, true);
+	private void formatLeadingAttributes(IASTAttributeOwner owner, Predicate<IASTAttributeSpecifier> predicate) {
+		formatAttributes(owner, false, true, predicate);
+	}
+
+	private void formatAttributes(IASTAttributeOwner owner, boolean printLeadingSpace, boolean printTrailingSpace) {
+		formatAttributes(owner, printLeadingSpace, printTrailingSpace, unsused -> true);
 	}
 
 	/**
@@ -1552,8 +1570,10 @@
 	 * @param owner Node containing attributes
 	 * @param printLeadingSpace Print a space before the first attribute
 	 * @param printTrailingSpace Print a space after the last attribute
+	 * @param filter Filter predicate for specifying which attributes to print
 	 */
-	private void formatAttributes(IASTAttributeOwner owner, boolean printLeadingSpace, boolean printTrailingSpace) {
+	private void formatAttributes(IASTAttributeOwner owner, boolean printLeadingSpace,
+			boolean printTrailingSpace, Predicate<IASTAttributeSpecifier> filter) {
 		if (owner == null) {
 			return;
 		}
@@ -1563,7 +1583,9 @@
 				scribe.space();
 			}
 			for (IASTAttributeSpecifier attributeSpecifier : attributeSpecifiers) {
-				formatRaw(attributeSpecifier);
+				if (filter.test(attributeSpecifier)) {
+					formatRaw(attributeSpecifier);
+				}
 			}
 			if (printTrailingSpace) {
 				scribe.space();
diff --git a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/CodeFormatterTest.java b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/CodeFormatterTest.java
index 5ace075..7bee37b 100644
--- a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/CodeFormatterTest.java
+++ b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/CodeFormatterTest.java
@@ -15,8 +15,6 @@
 import java.util.HashMap;
 import java.util.Map;
 
-import junit.framework.TestSuite;
-
 import org.eclipse.jface.text.BadLocationException;
 import org.eclipse.jface.text.Document;
 import org.eclipse.jface.text.IDocument;
@@ -33,6 +31,8 @@
 import org.eclipse.cdt.internal.corext.util.CodeFormatterUtil;
 import org.eclipse.cdt.internal.formatter.align.Alignment;
 
+import junit.framework.TestSuite;
+
 /**
  * Tests for the CodeFormatter.
  *
@@ -3525,4 +3525,17 @@
 	public void testAttributedGotoLabel_Bug535278_5() throws Exception {
 		assertFormatterResult();
 	}
+
+	//namespace[[foo]]{
+	//}
+	//namespace[[foo]]NS  __attribute__((__visibility__("default"))){
+	//}
+
+	//namespace [[foo]] {
+	//}
+	//namespace [[foo]] NS __attribute__((__visibility__("default"))) {
+	//}
+	public void testAttributedNamesapces_Bug535274() throws Exception {
+		assertFormatterResult();
+	}
 }