blob: 7004f29f3084b2026fb39b6af58c4cba81050190 [file] [log] [blame]
/*******************************************************************************
* 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 "&lt;init&gt;" and "&lt;clinit&gt;" 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);
}
}