| /******************************************************************************* |
| * Copyright (c) 2001, 2013 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Rational Software - initial implementation |
| * Sergey Prigogin (Google) |
| *******************************************************************************/ |
| package org.eclipse.cdt.core; |
| |
| import java.util.StringTokenizer; |
| |
| import org.eclipse.cdt.core.dom.ast.gnu.cpp.GPPLanguage; |
| import org.eclipse.cdt.core.dom.parser.AbstractCLikeLanguage; |
| import org.eclipse.cdt.core.model.CoreModel; |
| import org.eclipse.cdt.core.parser.IToken; |
| import org.eclipse.cdt.internal.core.CharOperation; |
| import org.eclipse.cdt.internal.core.Messages; |
| import org.eclipse.cdt.internal.core.model.CModelStatus; |
| import org.eclipse.cdt.internal.core.parser.scanner.ILexerLog; |
| import org.eclipse.cdt.internal.core.parser.scanner.Lexer; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.osgi.util.NLS; |
| |
| /** |
| * @noextend This interface is not intended to be extended by clients. |
| * @noinstantiate This class is not intended to be instantiated by clients. |
| */ |
| public class CConventions { |
| private static final String scopeResolutionOperator= "::"; //$NON-NLS-1$ |
| private static final char fgDot= '.'; |
| |
| private static final String ILLEGAL_FILE_CHARS = "/\\:<>?*|\""; //$NON-NLS-1$ |
| |
| public static boolean isLegalIdentifier(String name) { |
| if (name == null) { |
| return false; |
| } |
| |
| if (name.indexOf(' ') != -1) { |
| return false; |
| } |
| |
| int length = name.length(); |
| char c; |
| |
| if (length == 0) { |
| return false; |
| } |
| |
| c = name.charAt(0); |
| if ((!Character.isLetter(c)) && (c != '_')) { |
| return false; |
| } |
| |
| for (int i = 1; i < length; ++i) { |
| c = name.charAt(i); |
| if ((!Character.isLetterOrDigit(c)) && (c != '_')) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Validate the given CPP class name, either simple or qualified. For |
| * example, <code>"A::B::C"</code>, or <code>"C"</code>. |
| * <p> |
| * |
| * @param name the name of a class |
| * @return a status object with code <code>IStatus.OK</code> if |
| * the given name is valid as a CPP class name, |
| * a status with code <code>IStatus.WARNING</code> |
| * indicating why the given name is discouraged, |
| * otherwise a status object indicating what is wrong with |
| * the name |
| */ |
| public static IStatus validateClassName(String name) { |
| if (name == null) { |
| return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, Messages.convention_class_nullName, null); |
| } |
| String trimmed = name.trim(); |
| if ((!name.equals(trimmed)) || (name.indexOf(" ") != -1) ){ //$NON-NLS-1$ |
| return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, Messages.convention_class_nameWithBlanks, null); |
| } |
| int index = name.lastIndexOf(scopeResolutionOperator); |
| char[] scannedID; |
| if (index == -1) { |
| // simple name |
| IStatus status = validateIdentifier(name, GPPLanguage.getDefault()); |
| if (!status.isOK()){ |
| return status; |
| } |
| |
| scannedID = name.toCharArray(); |
| } else { |
| // qualified name |
| String pkg = name.substring(0, index).trim(); |
| IStatus status = validateScopeName(pkg); |
| if (!status.isOK()) { |
| return status; |
| } |
| String type = name.substring(index + scopeResolutionOperator.length()).trim(); |
| status = validateIdentifier(type, GPPLanguage.getDefault()); |
| if (!status.isOK()){ |
| return status; |
| } |
| scannedID = type.toCharArray(); |
| } |
| |
| if (scannedID != null) { |
| if (CharOperation.contains('$', scannedID)) { |
| return new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, -1, Messages.convention_class_dollarName, null); |
| } |
| if (scannedID.length > 0 && scannedID[0] == '_') { |
| return new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, -1, Messages.convention_class_leadingUnderscore, null); |
| } |
| if (scannedID.length > 0 && Character.isLowerCase(scannedID[0])) { |
| return new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, -1, Messages.convention_class_lowercaseName, null); |
| } |
| return CModelStatus.VERIFIED_OK; |
| } |
| return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, NLS.bind(Messages.convention_class_invalidName, name), null); |
| } |
| |
| /** |
| * Validate the given CPP namespace name, either simple or qualified. For |
| * example, <code>"A::B::C"</code>, or <code>"C"</code>. |
| * <p> |
| * |
| * @param name the name of a namespace |
| * @return a status object with code <code>IStatus.OK</code> if |
| * the given name is valid as a CPP class name, |
| * a status with code <code>IStatus.WARNING</code> |
| * indicating why the given name is discouraged, |
| * otherwise a status object indicating what is wrong with |
| * the name |
| */ |
| public static IStatus validateNamespaceName(String name) { |
| if (name == null) { |
| return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, Messages.convention_namespace_nullName, null); |
| } |
| String trimmed = name.trim(); |
| if ((!name.equals(trimmed)) || (name.indexOf(" ") != -1) ){ //$NON-NLS-1$ |
| return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, Messages.convention_namespace_nameWithBlanks, null); |
| } |
| int index = name.lastIndexOf(scopeResolutionOperator); |
| char[] scannedID; |
| if (index == -1) { |
| // simple name |
| IStatus status = validateIdentifier(name, GPPLanguage.getDefault()); |
| if (!status.isOK()){ |
| return status; |
| } |
| |
| scannedID = name.toCharArray(); |
| } else { |
| // qualified name |
| String pkg = name.substring(0, index).trim(); |
| IStatus status = validateScopeName(pkg); |
| if (!status.isOK()) { |
| return status; |
| } |
| String type = name.substring(index + scopeResolutionOperator.length()).trim(); |
| status = validateIdentifier(type, GPPLanguage.getDefault()); |
| if (!status.isOK()){ |
| return status; |
| } |
| scannedID = type.toCharArray(); |
| } |
| |
| if (scannedID != null) { |
| if (CharOperation.contains('$', scannedID)) { |
| return new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, -1, Messages.convention_namespace_dollarName, null); |
| } |
| if (scannedID.length > 0 && scannedID[0] == '_') { |
| return new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, -1, Messages.convention_namespace_leadingUnderscore, null); |
| } |
| // if (scannedID.length > 0 && Character.isLowerCase(scannedID[0])) { |
| // return new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, -1, Messages.convention.namespace.lowercaseName"), null); //$NON-NLS-1$ |
| // } |
| return CModelStatus.VERIFIED_OK; |
| } |
| return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, NLS.bind(Messages.convention_class_invalidName, name), null); |
| } |
| |
| /** |
| * Validate the given scope name. |
| * <p> |
| * @return a status object with code <code>IStatus.OK</code> if |
| * the given name is valid as a class name, otherwise a status |
| * object indicating what is wrong with the name |
| */ |
| public static IStatus validateScopeName(String name) { |
| if (name == null) { |
| return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, Messages.convention_scope_nullName, null); |
| } |
| int length; |
| if ((length = name.length()) == 0) { |
| return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, Messages.convention_scope_emptyName, null); |
| } |
| if (name.charAt(0) == fgDot || name.charAt(length-1) == fgDot) { |
| return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, Messages.convention_scope_dotName, null); |
| } |
| if (CharOperation.isWhitespace(name.charAt(0)) || CharOperation.isWhitespace(name.charAt(name.length() - 1))) { |
| return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, Messages.convention_scope_nameWithBlanks, null); |
| } |
| |
| StringTokenizer st = new StringTokenizer(name, scopeResolutionOperator); |
| boolean firstToken = true; |
| while (st.hasMoreTokens()) { |
| String typeName = st.nextToken(); |
| typeName = typeName.trim(); // grammar allows spaces |
| char[] scannedID = typeName.toCharArray(); |
| if (scannedID == null) { |
| return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, NLS.bind(Messages.convention_illegalIdentifier, typeName), null); |
| } |
| if (firstToken && scannedID.length > 0 && scannedID[0] == '_') { |
| return new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, -1, Messages.convention_scope_leadingUnderscore, null); |
| } |
| if (firstToken && scannedID.length > 0 && Character.isLowerCase(scannedID[0])) { |
| return new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, -1, Messages.convention_scope_lowercaseName, null); |
| } |
| firstToken = false; |
| } |
| return CModelStatus.VERIFIED_OK; |
| } |
| |
| /** |
| * Validate the given field name. |
| * <p> |
| * Syntax of a field name corresponds to VariableDeclaratorId (JLS2 8.3). |
| * For example, <code>"x"</code>. |
| * |
| * @param name the name of a field |
| * @return a status object with code <code>IStatus.OK</code> if |
| * the given name is valid as a field name, otherwise a status |
| * object indicating what is wrong with the name |
| */ |
| public static IStatus validateFieldName(String name) { |
| return validateIdentifier(name, GPPLanguage.getDefault()); |
| } |
| |
| /** |
| * Validate the given identifier. |
| * A valid identifier can act as a simple type name, method name or field name. |
| * |
| * @param id the C identifier |
| * @return a status object with code <code>IStatus.OK</code> if |
| * the given identifier is a valid C identifier, otherwise a status |
| * object indicating what is wrong with the identifier |
| * @deprecated Notice that the identifier is not being checked against language keywords. |
| * Use validateIdentifier(String id, AbstractCLikeLanguage language) instead. |
| */ |
| @Deprecated |
| public static IStatus validateIdentifier(String id) { |
| if (!isLegalIdentifier(id)) { |
| return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, NLS.bind(Messages.convention_illegalIdentifier, id), null); |
| } |
| |
| if (!isValidIdentifier(id)) { |
| return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, NLS.bind(Messages.convention_invalid, id), null); |
| } |
| |
| return CModelStatus.VERIFIED_OK; |
| } |
| |
| /** |
| * Validate the given C or C++ identifier. |
| * The identifier must not have the same spelling as a C or C++ keyword. |
| * A valid identifier can act as a simple type name, method name or field name. |
| * |
| * @param id the C identifier |
| * @return a status object with code <code>IStatus.OK</code> if |
| * the given identifier is a valid C identifier, otherwise a status |
| * object indicating what is wrong with the identifier |
| * @since 5.3 |
| */ |
| public static IStatus validateIdentifier(String id, AbstractCLikeLanguage language) { |
| if (!isLegalIdentifier(id)) { |
| return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, NLS.bind(Messages.convention_illegalIdentifier, id), null); |
| } |
| |
| if (!isValidIdentifier(id)) { |
| return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, NLS.bind(Messages.convention_invalid, id), null); |
| } |
| |
| if (isReservedKeyword(id, language) || isBuiltinType(id, language)) { |
| return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, NLS.bind(Messages.convention_reservedKeyword, id), null); |
| } |
| |
| return CModelStatus.VERIFIED_OK; |
| } |
| |
| /** |
| * Validate the given method name. |
| * The special names "<init>" and "<clinit>" are not valid. |
| * <p> |
| * The syntax for a method name is defined by Identifier |
| * of MethodDeclarator (JLS2 8.4). For example "println". |
| * |
| * @param name the name of a method |
| * @return a status object with code <code>IStatus.OK</code> if |
| * the given name is valid as a method name, otherwise a status |
| * object indicating what is wrong with the name |
| */ |
| public static IStatus validateMethodName(String name) { |
| if (name.startsWith("~")) { //$NON-NLS-1$ |
| return validateIdentifier(name.substring(1), GPPLanguage.getDefault()); |
| } |
| return validateIdentifier(name, GPPLanguage.getDefault()); |
| } |
| |
| /** |
| * Validate the given include name. |
| * <p> |
| * The name of an include without the surrounding double quotes or brackets |
| * For example, <code>stdio.h</code> or <code>iostream</code>. |
| * |
| * @param name the include declaration |
| * @return a status object with code <code>IStatus.OK</code> if |
| * the given name is valid as an include name, otherwise a status |
| * object indicating what is wrong with the name |
| */ |
| |
| public static IStatus validateIncludeName(IProject project, String name) { |
| String[] segments = new Path(name).segments(); |
| for (int i = 0; i < segments.length; ++i) { |
| IStatus status; |
| if (i == (segments.length - 1)) { |
| status = validateHeaderFileName(project, segments[i]); |
| } else { |
| status = validateFileName(segments[i]); |
| } |
| if (!status.isOK()) { |
| return status; |
| } |
| } |
| return CModelStatus.VERIFIED_OK; |
| } |
| |
| public static boolean isValidIdentifier(String name){ |
| // Create a scanner and get the type of the token |
| // assuming that you are given a valid identifier |
| IToken token = null; |
| Lexer lexer= new Lexer(name.toCharArray(), new Lexer.LexerOptions(), ILexerLog.NULL, null); |
| try { |
| token = lexer.nextToken(); |
| if (token.getType() == IToken.tIDENTIFIER && lexer.nextToken().getType() == IToken.tEND_OF_INPUT) { |
| return true; |
| } |
| } catch (Exception e) { |
| } |
| return false; |
| } |
| |
| private static boolean isReservedKeyword(String name, AbstractCLikeLanguage language) { |
| String[] keywords = language.getKeywords(); |
| for (String kw : keywords) { |
| if (kw.equals(name)) |
| return true; |
| } |
| return false; |
| } |
| |
| private static boolean isBuiltinType(String name, AbstractCLikeLanguage language) { |
| String[] types = language.getBuiltinTypes(); |
| for (String type : types) { |
| if (type.equals(name)) |
| return true; |
| } |
| return false; |
| } |
| |
| private static boolean isLegalFilename(String name) { |
| if (name == null || name.isEmpty()) { |
| return false; |
| } |
| |
| //TODO we need platform-independent validation, see bug#24152 |
| |
| int len = name.length(); |
| // if (Character.isWhitespace(name.charAt(0)) || Character.isWhitespace(name.charAt(len - 1))) { |
| // return false; |
| // } |
| for (int i = 0; i < len; i++) { |
| char c = name.charAt(i); |
| if (ILLEGAL_FILE_CHARS.indexOf(c) != -1) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Validate the given file name. |
| * The name must be the short file name (including the extension). |
| * It should not contain any prefix or path delimiters. |
| * |
| * @param name the file name |
| * @return a status object with code <code>IStatus.OK</code> if |
| * the given name is valid as a C/C++ file name, |
| * a status with code <code>IStatus.WARNING</code> |
| * indicating why the given name is discouraged, |
| * otherwise a status object indicating what is wrong with |
| * the name |
| */ |
| public static IStatus validateFileName(String name) { |
| //TODO could use a preferences option for file naming conventions |
| if (name == null || name.length() == 0) { |
| return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, Messages.convention_filename_nullName, null); |
| } |
| if (!isLegalFilename(name)) { |
| //TODO we need platform-independent validation, see bug#24152 |
| //return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, Messages.convention.filename.invalid"), null); //$NON-NLS-1$ |
| return new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, -1, Messages.convention_filename_possiblyInvalid, null); |
| } |
| |
| String trimmed = name.trim(); |
| if ((!name.equals(trimmed)) || (name.indexOf(" ") != -1)) { //$NON-NLS-1$ |
| return new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, -1, Messages.convention_filename_nameWithBlanks, null); |
| } |
| |
| return CModelStatus.VERIFIED_OK; |
| } |
| |
| /** |
| * Validate the given header file name. |
| * The name must be the short file name (including the extension). |
| * It should not contain any prefix or path delimiters. |
| * |
| * @param name the header file name |
| * @return a status object with code <code>IStatus.OK</code> if |
| * the given name is valid as a C/C++ header file name, |
| * a status with code <code>IStatus.WARNING</code> |
| * indicating why the given name is discouraged, |
| * otherwise a status object indicating what is wrong with |
| * the name |
| */ |
| public static IStatus validateHeaderFileName(IProject project, String name) { |
| //TODO could use a preferences option for header file naming conventions |
| IStatus val = validateFileName(name); |
| if (val.getSeverity() == IStatus.ERROR) { |
| return val; |
| } |
| |
| if (!CoreModel.isValidHeaderUnitName(project, name)) { |
| return new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, -1, Messages.convention_headerFilename_filetype, null); |
| } |
| |
| return val; |
| } |
| |
| /** |
| * Validate the given source file name. |
| * The name must be the short file name (including the extension). |
| * It should not contain any prefix or path delimiters. |
| * |
| * @param name the source file name |
| * @return a status object with code <code>IStatus.OK</code> if |
| * the given name is valid as a C/C++ source file name, |
| * a status with code <code>IStatus.WARNING</code> |
| * indicating why the given name is discouraged, |
| * otherwise a status object indicating what is wrong with |
| * the name |
| */ |
| public static IStatus validateSourceFileName(IProject project, String name) { |
| //TODO could use a preferences option for source file naming conventions |
| IStatus val = validateFileName(name); |
| if (val.getSeverity() == IStatus.ERROR) { |
| return val; |
| } |
| |
| if (!CoreModel.isValidSourceUnitName(project, name)) { |
| return new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, -1, Messages.convention_sourceFilename_filetype, null); |
| } |
| |
| return val; |
| } |
| |
| /** |
| * Validate the given C++ enum name, either simple or qualified. For |
| * example, <code>"A::B::C"</code>, or <code>"C"</code>. |
| * <p> |
| * |
| * @param name the name of a enum |
| * @return a status object with code <code>IStatus.OK</code> if |
| * the given name is valid as a CPP enum name, |
| * a status with code <code>IStatus.WARNING</code> |
| * indicating why the given name is discouraged, |
| * otherwise a status object indicating what is wrong with |
| * the name |
| * @since 4.0 |
| */ |
| public static IStatus validateEnumName(String name) { |
| if (name == null) { |
| return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, Messages.convention_enum_nullName, null); |
| } |
| String trimmed = name.trim(); |
| if ((!name.equals(trimmed)) || (name.indexOf(" ") != -1) ){ //$NON-NLS-1$ |
| return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, Messages.convention_enum_nameWithBlanks, null); |
| } |
| int index = name.lastIndexOf(scopeResolutionOperator); |
| char[] scannedID; |
| if (index == -1) { |
| // simple name |
| IStatus status = validateIdentifier(name, GPPLanguage.getDefault()); |
| if (!status.isOK()){ |
| return status; |
| } |
| |
| scannedID = name.toCharArray(); |
| } else { |
| // qualified name |
| String pkg = name.substring(0, index).trim(); |
| IStatus status = validateScopeName(pkg); |
| if (!status.isOK()) { |
| return status; |
| } |
| String type = name.substring(index + scopeResolutionOperator.length()).trim(); |
| status = validateIdentifier(type, GPPLanguage.getDefault()); |
| if (!status.isOK()){ |
| return status; |
| } |
| scannedID = type.toCharArray(); |
| } |
| |
| if (scannedID != null) { |
| if (CharOperation.contains('$', scannedID)) { |
| return new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, -1, Messages.convention_enum_dollarName, null); |
| } |
| if (scannedID.length > 0 && scannedID[0] == '_') { |
| return new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, -1, Messages.convention_enum_leadingUnderscore, null); |
| } |
| if (scannedID.length > 0 && Character.isLowerCase(scannedID[0])) { |
| return new Status(IStatus.WARNING, CCorePlugin.PLUGIN_ID, -1, Messages.convention_enum_lowercaseName, null); |
| } |
| return CModelStatus.VERIFIED_OK; |
| } |
| return new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, -1, Messages.convention_enum_invalidName, null); |
| } |
| } |