1
/*******************************************************************************
2
* Copyright (c) 2001, 2011 IBM Corporation and others.
3
* All rights reserved. This program and the accompanying materials
4
* are made available under the terms of the Eclipse Public License v1.0
5
* which accompanies this distribution, and is available at
6
* http://www.eclipse.org/legal/epl-v10.html
9
* Rational Software - initial implementation
10
* Sergey Prigogin (Google)
11
*******************************************************************************/
12
package org.eclipse.cdt.core;
14
import java.util.StringTokenizer;
16
import org.eclipse.cdt.core.dom.ast.gnu.cpp.GPPLanguage;
17
import org.eclipse.cdt.core.dom.parser.AbstractCLikeLanguage;
18
import org.eclipse.cdt.core.model.CoreModel;
19
import org.eclipse.cdt.core.parser.IToken;
20
import org.eclipse.cdt.internal.core.CharOperation;
21
import org.eclipse.cdt.internal.core.Messages;
22
import org.eclipse.cdt.internal.core.model.CModelStatus;
23
import org.eclipse.cdt.internal.core.parser.scanner.ILexerLog;
24
import org.eclipse.cdt.internal.core.parser.scanner.Lexer;
25
import org.eclipse.core.resources.IProject;
26
import org.eclipse.core.runtime.IStatus;
27
import org.eclipse.core.runtime.Path;
28
import org.eclipse.core.runtime.Status;
29
import org.eclipse.osgi.util.NLS;
32
* @noextend This interface is not intended to be extended by clients.
33
* @noinstantiate This class is not intended to be instantiated by clients.
35
public class CConventions {
36
private final static String scopeResolutionOperator= "::"; //$NON-NLS-1$
37
private final static char fgDot= '.';
39
private final static String ILLEGAL_FILE_CHARS = "/\\:<>?*|\""; //$NON-NLS-1$
41
public static boolean isLegalIdentifier(String name) {
46
if (name.indexOf(' ') != -1) {
50
int length = name.length();
58
if ((!Character.isLetter(c)) && (c != '_')) {
62
for (int i = 1; i < length; ++i) {
64
if ((!Character.isLetterOrDigit(c)) && (c != '_')) {
73
* Validate the given CPP class name, either simple or qualified. For
74
* example, <code>"A::B::C"</code>, or <code>"C"</code>.
77
* @param name the name of a class
78
* @return a status object with code <code>IStatus.OK</code> if
79
* the given name is valid as a CPP class name,
80
* a status with code <code>IStatus.WARNING</code>
81
* indicating why the given name is discouraged,
82
* otherwise a status object indicating what is wrong with
85
public static IStatus validateClassName(String name) {
87
return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, Messages.convention_class_nullName, null);
89
String trimmed = name.trim();
90
if ((!name.equals(trimmed)) || (name.indexOf(" ") != -1) ){ //$NON-NLS-1$
91
return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, Messages.convention_class_nameWithBlanks, null);
93
int index = name.lastIndexOf(scopeResolutionOperator);
97
IStatus status = validateIdentifier(name, GPPLanguage.getDefault());
102
scannedID = name.toCharArray();
105
String pkg = name.substring(0, index).trim();
106
IStatus status = validateScopeName(pkg);
107
if (!status.isOK()) {
110
String type = name.substring(index + scopeResolutionOperator.length()).trim();
111
status = validateIdentifier(type, GPPLanguage.getDefault());
115
scannedID = type.toCharArray();
118
if (scannedID != null) {
119
if (CharOperation.contains('$', scannedID)) {
120
return new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, -1, Messages.convention_class_dollarName, null);
122
if (scannedID.length > 0 && scannedID[0] == '_') {
123
return new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, -1, Messages.convention_class_leadingUnderscore, null);
125
if (scannedID.length > 0 && Character.isLowerCase(scannedID[0])) {
126
return new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, -1, Messages.convention_class_lowercaseName, null);
128
return CModelStatus.VERIFIED_OK;
130
return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, NLS.bind(Messages.convention_class_invalidName, name), null);
134
* Validate the given CPP namespace name, either simple or qualified. For
135
* example, <code>"A::B::C"</code>, or <code>"C"</code>.
138
* @param name the name of a namespace
139
* @return a status object with code <code>IStatus.OK</code> if
140
* the given name is valid as a CPP class name,
141
* a status with code <code>IStatus.WARNING</code>
142
* indicating why the given name is discouraged,
143
* otherwise a status object indicating what is wrong with
146
public static IStatus validateNamespaceName(String name) {
148
return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, Messages.convention_namespace_nullName, null);
150
String trimmed = name.trim();
151
if ((!name.equals(trimmed)) || (name.indexOf(" ") != -1) ){ //$NON-NLS-1$
152
return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, Messages.convention_namespace_nameWithBlanks, null);
154
int index = name.lastIndexOf(scopeResolutionOperator);
158
IStatus status = validateIdentifier(name, GPPLanguage.getDefault());
163
scannedID = name.toCharArray();
166
String pkg = name.substring(0, index).trim();
167
IStatus status = validateScopeName(pkg);
168
if (!status.isOK()) {
171
String type = name.substring(index + scopeResolutionOperator.length()).trim();
172
status = validateIdentifier(type, GPPLanguage.getDefault());
176
scannedID = type.toCharArray();
179
if (scannedID != null) {
180
if (CharOperation.contains('$', scannedID)) {
181
return new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, -1, Messages.convention_namespace_dollarName, null);
183
if (scannedID.length > 0 && scannedID[0] == '_') {
184
return new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, -1, Messages.convention_namespace_leadingUnderscore, null);
186
// if (scannedID.length > 0 && Character.isLowerCase(scannedID[0])) {
187
// return new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, -1, Messages.convention.namespace.lowercaseName"), null); //$NON-NLS-1$
189
return CModelStatus.VERIFIED_OK;
191
return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, NLS.bind(Messages.convention_class_invalidName, name), null);
195
* Validate the given scope name.
197
* @return a status object with code <code>IStatus.OK</code> if
198
* the given name is valid as a class name, otherwise a status
199
* object indicating what is wrong with the name
201
public static IStatus validateScopeName(String name) {
203
return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, Messages.convention_scope_nullName, null);
206
if ((length = name.length()) == 0) {
207
return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, Messages.convention_scope_emptyName, null);
209
if (name.charAt(0) == fgDot || name.charAt(length-1) == fgDot) {
210
return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, Messages.convention_scope_dotName, null);
212
if (CharOperation.isWhitespace(name.charAt(0)) || CharOperation.isWhitespace(name.charAt(name.length() - 1))) {
213
return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, Messages.convention_scope_nameWithBlanks, null);
216
StringTokenizer st = new StringTokenizer(name, scopeResolutionOperator);
217
boolean firstToken = true;
218
while (st.hasMoreTokens()) {
219
String typeName = st.nextToken();
220
typeName = typeName.trim(); // grammar allows spaces
221
char[] scannedID = typeName.toCharArray();
222
if (scannedID == null) {
223
return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, NLS.bind(Messages.convention_illegalIdentifier, typeName), null);
225
if (firstToken && scannedID.length > 0 && scannedID[0] == '_') {
226
return new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, -1, Messages.convention_scope_leadingUnderscore, null);
228
if (firstToken && scannedID.length > 0 && Character.isLowerCase(scannedID[0])) {
229
return new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, -1, Messages.convention_scope_lowercaseName, null);
233
return CModelStatus.VERIFIED_OK;
237
* Validate the given field name.
239
* Syntax of a field name corresponds to VariableDeclaratorId (JLS2 8.3).
240
* For example, <code>"x"</code>.
242
* @param name the name of a field
243
* @return a status object with code <code>IStatus.OK</code> if
244
* the given name is valid as a field name, otherwise a status
245
* object indicating what is wrong with the name
247
public static IStatus validateFieldName(String name) {
248
return validateIdentifier(name, GPPLanguage.getDefault());
252
* Validate the given identifier.
253
* A valid identifier can act as a simple type name, method name or field name.
255
* @param id the C identifier
256
* @return a status object with code <code>IStatus.OK</code> if
257
* the given identifier is a valid C identifier, otherwise a status
258
* object indicating what is wrong with the identifier
259
* @deprecated Notice that the identifier is not being checked against language keywords.
260
* Use validateIdentifier(String id, AbstractCLikeLanguage language) instead.
263
public static IStatus validateIdentifier(String id) {
264
if (!isLegalIdentifier(id)) {
265
return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, NLS.bind(Messages.convention_illegalIdentifier, id), null);
268
if (!isValidIdentifier(id)) {
269
return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, NLS.bind(Messages.convention_invalid, id), null);
272
return CModelStatus.VERIFIED_OK;
276
* Validate the given C or C++ identifier.
277
* The identifier must not have the same spelling as a C or C++ keyword.
278
* A valid identifier can act as a simple type name, method name or field name.
280
* @param id the C identifier
281
* @return a status object with code <code>IStatus.OK</code> if
282
* the given identifier is a valid C identifier, otherwise a status
283
* object indicating what is wrong with the identifier
286
public static IStatus validateIdentifier(String id, AbstractCLikeLanguage language) {
287
if (!isLegalIdentifier(id)) {
288
return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, NLS.bind(Messages.convention_illegalIdentifier, id), null);
291
if (!isValidIdentifier(id)) {
292
return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, NLS.bind(Messages.convention_invalid, id), null);
295
if (isReservedKeyword(id, language)) {
296
return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, NLS.bind(Messages.convention_reservedKeyword, id), null);
299
return CModelStatus.VERIFIED_OK;
303
* Validate the given method name.
304
* The special names "<init>" and "<clinit>" are not valid.
306
* The syntax for a method name is defined by Identifier
307
* of MethodDeclarator (JLS2 8.4). For example "println".
309
* @param name the name of a method
310
* @return a status object with code <code>IStatus.OK</code> if
311
* the given name is valid as a method name, otherwise a status
312
* object indicating what is wrong with the name
314
public static IStatus validateMethodName(String name) {
315
if (name.startsWith("~")) { //$NON-NLS-1$
316
return validateIdentifier(name.substring(1), GPPLanguage.getDefault());
318
return validateIdentifier(name, GPPLanguage.getDefault());
322
* Validate the given include name.
324
* The name of an include without the surrounding double quotes or brackets
325
* For example, <code>stdio.h</code> or <code>iostream</code>.
327
* @param name the include declaration
328
* @return a status object with code <code>IStatus.OK</code> if
329
* the given name is valid as an include name, otherwise a status
330
* object indicating what is wrong with the name
333
public static IStatus validateIncludeName(IProject project, String name) {
334
String[] segments = new Path(name).segments();
335
for (int i = 0; i < segments.length; ++i) {
337
if (i == (segments.length - 1)) {
338
status = validateHeaderFileName(project, segments[i]);
340
status = validateFileName(segments[i]);
342
if (!status.isOK()) {
346
return CModelStatus.VERIFIED_OK;
349
public static boolean isValidIdentifier(String name){
350
// Create a scanner and get the type of the token
351
// assuming that you are given a valid identifier
353
Lexer lexer= new Lexer(name.toCharArray(), new Lexer.LexerOptions(), ILexerLog.NULL, null);
355
token = lexer.nextToken();
356
if (token.getType() == IToken.tIDENTIFIER && lexer.nextToken().getType() == IToken.tEND_OF_INPUT) {
359
} catch (Exception e) {
364
private static boolean isReservedKeyword(String name, AbstractCLikeLanguage language) {
365
String[] keywords = language.getKeywords();
366
for (String kw : keywords) {
373
private static boolean isLegalFilename(String name) {
374
if (name == null || name.length() == 0) {
378
//TODO we need platform-independent validation, see bug#24152
380
int len = name.length();
381
// if (Character.isWhitespace(name.charAt(0)) || Character.isWhitespace(name.charAt(len - 1))) {
384
for (int i = 0; i < len; i++) {
385
char c = name.charAt(i);
386
if (ILLEGAL_FILE_CHARS.indexOf(c) != -1) {
394
* Validate the given file name.
395
* The name must be the short file name (including the extension).
396
* It should not contain any prefix or path delimiters.
398
* @param name the file name
399
* @return a status object with code <code>IStatus.OK</code> if
400
* the given name is valid as a C/C++ file name,
401
* a status with code <code>IStatus.WARNING</code>
402
* indicating why the given name is discouraged,
403
* otherwise a status object indicating what is wrong with
406
public static IStatus validateFileName(String name) {
407
//TODO could use a preferences option for file naming conventions
408
if (name == null || name.length() == 0) {
409
return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, Messages.convention_filename_nullName, null);
411
if (!isLegalFilename(name)) {
412
//TODO we need platform-independent validation, see bug#24152
413
//return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, Messages.convention.filename.invalid"), null); //$NON-NLS-1$
414
return new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, -1, Messages.convention_filename_possiblyInvalid, null);
417
String trimmed = name.trim();
418
if ((!name.equals(trimmed)) || (name.indexOf(" ") != -1)) { //$NON-NLS-1$
419
return new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, -1, Messages.convention_filename_nameWithBlanks, null);
422
return CModelStatus.VERIFIED_OK;
426
* Validate the given header file name.
427
* The name must be the short file name (including the extension).
428
* It should not contain any prefix or path delimiters.
430
* @param name the header file name
431
* @return a status object with code <code>IStatus.OK</code> if
432
* the given name is valid as a C/C++ header file name,
433
* a status with code <code>IStatus.WARNING</code>
434
* indicating why the given name is discouraged,
435
* otherwise a status object indicating what is wrong with
438
public static IStatus validateHeaderFileName(IProject project, String name) {
439
//TODO could use a preferences option for header file naming conventions
440
IStatus val = validateFileName(name);
441
if (val.getSeverity() == IStatus.ERROR) {
445
if (!CoreModel.isValidHeaderUnitName(project, name)) {
446
return new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, -1, Messages.convention_headerFilename_filetype, null);
453
* Validate the given source file name.
454
* The name must be the short file name (including the extension).
455
* It should not contain any prefix or path delimiters.
457
* @param name the source file name
458
* @return a status object with code <code>IStatus.OK</code> if
459
* the given name is valid as a C/C++ source file name,
460
* a status with code <code>IStatus.WARNING</code>
461
* indicating why the given name is discouraged,
462
* otherwise a status object indicating what is wrong with
465
public static IStatus validateSourceFileName(IProject project, String name) {
466
//TODO could use a preferences option for source file naming conventions
467
IStatus val = validateFileName(name);
468
if (val.getSeverity() == IStatus.ERROR) {
472
if (!CoreModel.isValidSourceUnitName(project, name)) {
473
return new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, -1, Messages.convention_sourceFilename_filetype, null);
480
* Validate the given C++ enum name, either simple or qualified. For
481
* example, <code>"A::B::C"</code>, or <code>"C"</code>.
484
* @param name the name of a enum
485
* @return a status object with code <code>IStatus.OK</code> if
486
* the given name is valid as a CPP enum name,
487
* a status with code <code>IStatus.WARNING</code>
488
* indicating why the given name is discouraged,
489
* otherwise a status object indicating what is wrong with
493
public static IStatus validateEnumName(String name) {
495
return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, Messages.convention_enum_nullName, null);
497
String trimmed = name.trim();
498
if ((!name.equals(trimmed)) || (name.indexOf(" ") != -1) ){ //$NON-NLS-1$
499
return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, Messages.convention_enum_nameWithBlanks, null);
501
int index = name.lastIndexOf(scopeResolutionOperator);
505
IStatus status = validateIdentifier(name, GPPLanguage.getDefault());
510
scannedID = name.toCharArray();
513
String pkg = name.substring(0, index).trim();
514
IStatus status = validateScopeName(pkg);
515
if (!status.isOK()) {
518
String type = name.substring(index + scopeResolutionOperator.length()).trim();
519
status = validateIdentifier(type, GPPLanguage.getDefault());
523
scannedID = type.toCharArray();
526
if (scannedID != null) {
527
if (CharOperation.contains('$', scannedID)) {
528
return new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, -1, Messages.convention_enum_dollarName, null);
530
if (scannedID.length > 0 && scannedID[0] == '_') {
531
return new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, -1, Messages.convention_enum_leadingUnderscore, null);
533
if (scannedID.length > 0 && Character.isLowerCase(scannedID[0])) {
534
return new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, -1, Messages.convention_enum_lowercaseName, null);
536
return CModelStatus.VERIFIED_OK;
538
return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, Messages.convention_enum_invalidName, null);