1 // Copyright (c) 2014, Matthew Brennan Jones <matthew.brennan.jones@gmail.com>
2 // Distributed under the Boost Software License, Version 1.0.
3 //    (See accompanying file LICENSE_1_0.txt or copy at
4 //          http://www.boost.org/LICENSE_1_0.txt)
5 
6 module dscanner.analysis.duplicate_attribute;
7 
8 import std.stdio;
9 import std.string;
10 import dparse.ast;
11 import dparse.lexer;
12 import dscanner.analysis.base;
13 import dscanner.analysis.helpers;
14 import dsymbol.scope_ : Scope;
15 
16 /**
17  * Checks for duplicate attributes such as @property, @safe,
18  * @trusted, @system, pure, and nothrow
19  */
20 final class DuplicateAttributeCheck : BaseAnalyzer
21 {
22 	alias visit = BaseAnalyzer.visit;
23 
24 	mixin AnalyzerInfo!"duplicate_attribute";
25 
26 	this(string fileName, const(Scope)* sc, bool skipTests = false)
27 	{
28 		super(fileName, sc, skipTests);
29 	}
30 
31 	override void visit(const Declaration node)
32 	{
33 		checkAttributes(node);
34 		node.accept(this);
35 	}
36 
37 	void checkAttributes(const Declaration node)
38 	{
39 		bool hasProperty;
40 		bool hasSafe;
41 		bool hasTrusted;
42 		bool hasSystem;
43 		bool hasPure;
44 		bool hasNoThrow;
45 
46 		// Check the attributes
47 		foreach (attribute; node.attributes)
48 		{
49 			size_t line, column;
50 			string attributeName = getAttributeName(attribute, line, column);
51 			if (!attributeName || line == 0 || column == 0)
52 				return;
53 
54 			// Check for the attributes
55 			checkDuplicateAttribute(attributeName, "property", line, column, hasProperty);
56 			checkDuplicateAttribute(attributeName, "safe", line, column, hasSafe);
57 			checkDuplicateAttribute(attributeName, "trusted", line, column, hasTrusted);
58 			checkDuplicateAttribute(attributeName, "system", line, column, hasSystem);
59 			checkDuplicateAttribute(attributeName, "pure", line, column, hasPure);
60 			checkDuplicateAttribute(attributeName, "nothrow", line, column, hasNoThrow);
61 		}
62 
63 		// Just return if missing function nodes
64 		if (!node.functionDeclaration || !node.functionDeclaration.memberFunctionAttributes)
65 			return;
66 
67 		// Check the functions
68 		foreach (memberFunctionAttribute; node.functionDeclaration.memberFunctionAttributes)
69 		{
70 			size_t line, column;
71 			string attributeName = getAttributeName(memberFunctionAttribute, line, column);
72 			if (!attributeName || line == 0 || column == 0)
73 				return;
74 
75 			// Check for the attributes
76 			checkDuplicateAttribute(attributeName, "property", line, column, hasProperty);
77 			checkDuplicateAttribute(attributeName, "safe", line, column, hasSafe);
78 			checkDuplicateAttribute(attributeName, "trusted", line, column, hasTrusted);
79 			checkDuplicateAttribute(attributeName, "system", line, column, hasSystem);
80 			checkDuplicateAttribute(attributeName, "pure", line, column, hasPure);
81 			checkDuplicateAttribute(attributeName, "nothrow", line, column, hasNoThrow);
82 		}
83 	}
84 
85 	void checkDuplicateAttribute(const string attributeName,
86 			const string attributeDesired, size_t line, size_t column, ref bool hasAttribute)
87 	{
88 		// Just return if not an attribute
89 		if (attributeName != attributeDesired)
90 			return;
91 
92 		// Already has that attribute
93 		if (hasAttribute)
94 		{
95 			string message = "Attribute '%s' is duplicated.".format(attributeName);
96 			addErrorMessage(line, column, "dscanner.unnecessary.duplicate_attribute", message);
97 		}
98 
99 		// Mark it as having that attribute
100 		hasAttribute = true;
101 	}
102 
103 	string getAttributeName(const Attribute attribute, ref size_t line, ref size_t column)
104 	{
105 		// Get the name from the attribute identifier
106 		if (attribute && attribute.atAttribute && attribute.atAttribute.identifier !is Token.init
107 				&& attribute.atAttribute.identifier.text
108 				&& attribute.atAttribute.identifier.text.length)
109 		{
110 			auto token = attribute.atAttribute.identifier;
111 			line = token.line;
112 			column = token.column;
113 			return token.text;
114 		}
115 
116 		// Get the attribute from the storage class token
117 		if (attribute && attribute.attribute.type != tok!"")
118 		{
119 			line = attribute.attribute.line;
120 			column = attribute.attribute.column;
121 			return attribute.attribute.type.str;
122 		}
123 
124 		return null;
125 	}
126 
127 	string getAttributeName(const MemberFunctionAttribute memberFunctionAttribute,
128 			ref size_t line, ref size_t column)
129 	{
130 		// Get the name from the tokenType
131 		if (memberFunctionAttribute && memberFunctionAttribute.tokenType !is IdType.init
132 				&& memberFunctionAttribute.tokenType.str
133 				&& memberFunctionAttribute.tokenType.str.length)
134 		{
135 			// FIXME: How do we get the line/column number?
136 			return memberFunctionAttribute.tokenType.str;
137 		}
138 
139 		// Get the name from the attribute identifier
140 		if (memberFunctionAttribute && memberFunctionAttribute.atAttribute
141 				&& memberFunctionAttribute.atAttribute.identifier !is Token.init
142 				&& memberFunctionAttribute.atAttribute.identifier.type == tok!"identifier"
143 				&& memberFunctionAttribute.atAttribute.identifier.text
144 				&& memberFunctionAttribute.atAttribute.identifier.text.length)
145 		{
146 			auto iden = memberFunctionAttribute.atAttribute.identifier;
147 			line = iden.line;
148 			column = iden.column;
149 			return iden.text;
150 		}
151 
152 		return null;
153 	}
154 }
155 
156 unittest
157 {
158 	import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
159 
160 	StaticAnalysisConfig sac = disabledConfig();
161 	sac.duplicate_attribute = Check.enabled;
162 	assertAnalyzerWarnings(q{
163 		class ExampleAttributes
164 		{
165 			@property @safe bool xxx() // ok
166 			{
167 				return false;
168 			}
169 
170 			// Duplicate before
171 			@property @property bool aaa() // [warn]: Attribute 'property' is duplicated.
172 			{
173 				return false;
174 			}
175 
176 			// Duplicate after
177 			bool bbb() @safe @safe // [warn]: Attribute 'safe' is duplicated.
178 			{
179 				return false;
180 			}
181 
182 			// Duplicate before and after
183 			@system bool ccc() @system // [warn]: Attribute 'system' is duplicated.
184 			{
185 				return false;
186 			}
187 
188 			// Duplicate before and after
189 			@trusted bool ddd() @trusted // [warn]: Attribute 'trusted' is duplicated.
190 			{
191 				return false;
192 			}
193 		}
194 
195 		class ExamplePureNoThrow
196 		{
197 			pure nothrow bool aaa() // ok
198 			{
199 				return false;
200 			}
201 
202 			pure pure bool bbb() // [warn]: Attribute 'pure' is duplicated.
203 			{
204 				return false;
205 			}
206 
207 			// FIXME: There is no way to get the line/column number of the attribute like this
208 			bool ccc() pure pure // FIXME: [warn]: Attribute 'pure' is duplicated.
209 			{
210 				return false;
211 			}
212 
213 			nothrow nothrow bool ddd() // [warn]: Attribute 'nothrow' is duplicated.
214 			{
215 				return false;
216 			}
217 
218 			// FIXME: There is no way to get the line/column number of the attribute like this
219 			bool eee() nothrow nothrow // FIXME: [warn]: Attribute 'nothrow' is duplicated.
220 			{
221 				return false;
222 			}
223 		}
224 	}}, sac);
225 
226 	stderr.writeln("Unittest for DuplicateAttributeCheck passed.");
227 }