/// <summary> /// Validates the specified constructor signature string. /// It expects a constructor signature string which starts at the first parenthesis after the constructor's name /// identifier. /// </summary> /// <param name="input">The constructor signature string.</param> /// <param name="validateTypeCallback"> /// The validate type callback, used for additional custom validation of parameter types /// in the constructor signature. /// </param> /// <returns>A parse/validation results object. <see cref="CSharpConstructorSignatureValidationResult" /></returns> /// <exception cref="System.ArgumentNullException">Thrown if <paramref name="input" /> is <c>null</c>.</exception> public static CSharpConstructorSignatureValidationResult Validate(string input, CSharpParsedTypeValidateTypeCallback validateTypeCallback = null) { if (input == null) { throw new ArgumentNullException("input", "Constructor signature string cannot be null!"); } var result = new CSharpConstructorSignatureValidationResult { Success = true, ParameterForwarding = CSharpConstructorParameterForwarding.None }; if (string.IsNullOrWhiteSpace(input)) { result.Success = false; result.ErrorDescription = "Constructor signature cannot be whitespace."; return(result); } States state = States.Start; string accum = ""; var parameterTypes = new GenericArray <CSharpClassNameValidationResult>(); var parameterNames = new HashSet <string>(); var forwardedParameters = new GenericArray <string>(); int unclosedGenericsBrackets = 0; //weeeeeeeeeeeeeeee! for (int index = 0; index < input.Length; index++) { var c = input[index]; accum += c; if (c == '(') { switch (state) { case States.Start: state = States.WaitingForParamType; accum = ""; continue; case States.AccumulatingForwardingKeyword: switch (accum) { case "base(": result.ParameterForwarding = CSharpConstructorParameterForwarding.Base; break; case "this(": result.ParameterForwarding = CSharpConstructorParameterForwarding.This; break; default: result.Success = false; result.ErrorDescription = "Constructor forwarding keyword must be 'base' or 'this'."; result.ErrorIndex = index - 5; return(result); } accum = ""; state = States.WaitingForForwardedParam; continue; case States.AfterForwardingKeyword: state = States.WaitingForForwardedParam; accum = ""; continue; default: result.Success = false; result.ErrorDescription = "Unexpected opening parenthesis."; result.ErrorIndex = index; return(result); } } if (c == ':') { if (state == States.EndOfBasicSignature) { accum = ""; state = States.WaitingForForwardingKeyword; continue; } result.Success = false; result.ErrorDescription = "CSharpIDValidator.IsValidIdentifier:' character."; result.ErrorIndex = index; return(result); } if (c == '<') { if (state == States.AccumulatingParamType) { unclosedGenericsBrackets++; state = States.AccumulatingGenericType; continue; } if (state == States.AccumulatingGenericType) { unclosedGenericsBrackets++; continue; } result.Success = false; result.ErrorDescription = "CSharpIDValidator.IsValidIdentifier<' character."; result.ErrorIndex = index; return(result); } if (c == '>') { if (state == States.AccumulatingGenericType) { unclosedGenericsBrackets--; if (unclosedGenericsBrackets == 0) { state = States.AccumulatingParamType; } continue; } result.Success = false; result.ErrorDescription = "CSharpIDValidator.IsValidIdentifier>' character."; result.ErrorIndex = index; return(result); } if (c == ',' || c == ')') { switch (state) { case States.WaitingForParamType: if (parameterTypes.Count > 0 || (parameterTypes.Count == 0 && c == ',')) { result.Success = false; result.ErrorDescription = "Missing parameter declaration."; result.ErrorIndex = index - 1; return(result); } accum = ""; state = States.EndOfBasicSignature; continue; case States.WaitingForForwardedParam: if (forwardedParameters.Count > 0 || (forwardedParameters.Count == 0 && c == ',')) { result.Success = false; result.ErrorDescription = "Missing forwarded argument declaration."; result.ErrorIndex = index; return(result); } accum = ""; state = States.EndOfForwardingSignature; continue; case States.AccumulatingParamType: result.Success = false; result.ErrorDescription = "Missing parameter name."; result.ErrorIndex = index; return(result); case States.AccumulatingParamName: { var pname = accum.TrimEnd(',', ')').Trim(); if (!CSharpIDValidator.IsValidIdentifier(pname)) { result.Success = false; result.ErrorDescription = string.Format("Invalid parameter name '{0}'.", pname); result.ErrorIndex = index - accum.Length; return(result); } if (!parameterNames.Contains(pname)) { parameterNames.Add(pname); } else { result.Success = false; result.ErrorDescription = string.Format("Parameter name '{0}' used more than once.", pname); result.ErrorIndex = index - accum.Length; return(result); } accum = ""; state = c == ',' ? States.WaitingForParamType : States.EndOfBasicSignature; continue; } case States.AccumulatingForwardedParam: { var pname = accum.TrimEnd(',', ')').Trim(); if (!CSharpIDValidator.IsValidIdentifier(pname)) { result.Success = false; result.ErrorDescription = string.Format( "Invalid forwarded parameter '{0}', invalid identifier syntax.", pname); result.ErrorIndex = index - accum.Length; return(result); } if (!parameterNames.Contains(pname)) { result.Success = false; result.ErrorDescription = string.Format( "Invalid forwarded parameter '{0}', name was not previously declared.", pname); result.ErrorIndex = index - accum.Length; return(result); } forwardedParameters.Add(pname); accum = ""; state = c == ',' ? States.WaitingForForwardedParam : States.EndOfForwardingSignature; continue; } default: if (c == ',' && state == States.AccumulatingGenericType) { continue; } result.Success = false; result.ErrorDescription = string.Format( "CSharpIDValidator.IsValidIdentifier{0}' character.", c); result.ErrorIndex = index; return(result); } } if (!char.IsWhiteSpace(c)) { switch (state) { case States.EndOfBasicSignature: case States.EndOfForwardingSignature: result.Success = false; result.ErrorDescription = string.Format("Unexpected character '{0}' after signature.", c); result.ErrorIndex = index; return(result); case States.Start: result.Success = false; result.ErrorDescription = string.Format("Unexpected character '{0}', was expecting '('.", c); result.ErrorIndex = index; return(result); case States.WaitingForParamType: state = States.AccumulatingParamType; accum = "" + c; continue; case States.WaitingForParameterName: state = States.AccumulatingParamName; accum = "" + c; continue; case States.WaitingForForwardingKeyword: state = States.AccumulatingForwardingKeyword; accum = "" + c; continue; case States.WaitingForForwardedParam: state = States.AccumulatingForwardedParam; accum = "" + c; continue; } } if (char.IsWhiteSpace(c)) { switch (state) { case States.WaitingForParamType: case States.WaitingForParameterName: case States.EndOfBasicSignature: case States.WaitingForForwardingKeyword: case States.AfterForwardingKeyword: case States.WaitingForForwardedParam: accum = ""; continue; case States.AccumulatingForwardingKeyword: switch (accum) { case "base ": result.ParameterForwarding = CSharpConstructorParameterForwarding.Base; break; case "this ": result.ParameterForwarding = CSharpConstructorParameterForwarding.This; break; default: result.Success = false; result.ErrorDescription = "Constructor forwarding keyword must be 'base' or 'this'."; result.ErrorIndex = index - 5; return(result); } accum = ""; state = States.AfterForwardingKeyword; continue; case States.AccumulatingParamType: var ptype = accum.TrimEnd(); var validateParameter = CSharpClassNameValidator.ValidateInitialization(accum.TrimEnd(), true, validateTypeCallback); if (!validateParameter.Success) { result.Success = false; result.ErrorDescription = string.Format("Invalid parameter type '{0}': {1}", ptype, validateParameter.ErrorDescription); result.ErrorIndex = (index - accum.Length) + validateParameter.ErrorIndex + 1; return(result); } parameterTypes.Add(validateParameter); state = States.WaitingForParameterName; accum = ""; continue; } } } if (state != States.EndOfBasicSignature && state != States.EndOfForwardingSignature) { result.Success = false; result.ErrorDescription = "Incomplete constructor signature."; result.ErrorIndex = input.Length - 1; return(result); } result.ParameterTypes = parameterTypes; result.ParameterNames = parameterNames.ToGenericArray(); result.ForwardedParameters = forwardedParameters; return(result); }
private static CSharpClassNameValidationResult _Validate(string input, ClassSigType signatureType, bool allowBuiltinAliases, CSharpParsedTypeValidateTypeCallback validateTypeCallback, int index) { if (input == null) { throw new ArgumentNullException("input", "Class name/signature string cannot be null!"); } if (string.IsNullOrWhiteSpace(input)) { return(new CSharpClassNameValidationResult { Success = false, ErrorDescription = "Class name/signature cannot be whitespace.", ErrorIndex = index, }); } string fullyQualifiedName = ""; var genericArgs = new GenericArray <CSharpClassNameValidationResult>(); var qualifications = new GenericArray <Qualification>() { new Qualification(new StringBuilder(), index) }; string genericPart = ""; bool isGeneric = false; int genericBrackets = 0; foreach (var c in input) { //enter generic arguments if (c == '<') { isGeneric = true; genericBrackets++; } //check if we have gotten to the generic part of the type signature yet (if any) if (!isGeneric) { if (c == '.') { //qualifier operator is not allowed anywhere in declaration signatures because //they only consist of a raw type name and generic argument specifications if (signatureType == ClassSigType.Declaration) { return(new CSharpClassNameValidationResult { Success = false, ErrorDescription = string.Format( "'.' name qualifier operator is not valid in a class declaration/generic " + "type placeholder name."), ErrorIndex = index, }); } //detect a missing qualifier section that's terminated with a dot. if (string.IsNullOrWhiteSpace(qualifications.Last().ToString())) { return(new CSharpClassNameValidationResult { Success = false, ErrorDescription = "\'..\' is an invalid use of the qualification operator.", ErrorIndex = index, }); } qualifications.Add(new Qualification(new StringBuilder(), index + 1)); } else { qualifications.Last().Builder.Append(c); } } if (genericBrackets == 0 && !isGeneric) { //accumulate to our fully qualified name fullyQualifiedName += c; } else if (genericBrackets == 0 && isGeneric) { //we have exited even pairs of brackets and are on the //other side of the generic arguments at the end of the signature //there should not be anything here return(new CSharpClassNameValidationResult { Success = false, ErrorDescription = "extra content found after generic argument list.", ErrorIndex = index }); } else if (!(genericBrackets == 1 && c == '<')) { //passed the start of the first < in the signature by 1 if ((c == ',' || c == '>') && genericBrackets == 1) { //we have accumulated a type argument suitable for recursive decent //validate it recursively var validateGenericArgument = _Validate(genericPart.Trim(), signatureType, allowBuiltinAliases, validateTypeCallback, index - genericPart.Length); //return immediately on failure if (!validateGenericArgument.Success) { return(validateGenericArgument); } //add the validated 'tree' genericArgs.Add(validateGenericArgument); //reset the accumulator genericPart = ""; } else { //accumulate until we hit a comma or the > character genericPart += c; } } if (c == '>') { //exit a generic type scope genericBrackets--; } index++; } if (genericBrackets > 0) { //something is amiss with bracket matching return(new CSharpClassNameValidationResult { Success = false, ErrorDescription = "mismatched generic type brackets.", ErrorIndex = index }); } //there may be only one qualification, in which case //the base name is also the fully qualified name. var baseName = qualifications.Last(); if (qualifications.Count > 1) { //uses qualified names, this is definitely an initialization signature //because the qualifier '.' operator returns an error in declaration signatures //above the recursive call to validate foreach (var name in qualifications) { if (string.IsNullOrWhiteSpace(name.Builder.ToString())) { return(new CSharpClassNameValidationResult { Success = false, ErrorDescription = string.Format("qualified type name '{0}' is incomplete.", fullyQualifiedName), ErrorIndex = name.StartIndex }); } } foreach (var name in qualifications) { //check for syntax errors in the qualified name pieces, they need to be valid ID's and not keywords //IsValidIdentifier takes care of both these criteria if (!CSharpIDValidator.IsValidIdentifier(name.Builder.ToString())) { //sound something funky return(new CSharpClassNameValidationResult { Success = false, ErrorDescription = string.Format( "'{0}' is not valid in the given qualified type name '{1}'.", name.Builder, fullyQualifiedName), ErrorIndex = name.StartIndex }); } } } else { var shortName = baseName.Builder.ToString(); //single type argument to a generic type if (string.IsNullOrWhiteSpace(shortName)) { return(new CSharpClassNameValidationResult { Success = false, ErrorDescription = string.Format("missing generic {0} name.", signatureType == ClassSigType.Initialization ? "type argument" : "placeholder type"), ErrorIndex = baseName.StartIndex, }); } bool aliasInitialization = allowBuiltinAliases && CSharpKeywords.BuiltInTypeMap.ContainsKey(shortName) && signatureType == ClassSigType.Initialization; if (!aliasInitialization && !CSharpIDValidator.IsValidIdentifier(baseName.Builder.ToString())) { return(new CSharpClassNameValidationResult { Success = false, ErrorDescription = string.Format("'{0}' is not an allowed CSharp identifier.", baseName.Builder), ErrorIndex = baseName.StartIndex }); } } var baseNameString = baseName.Builder.ToString(); if (isGeneric && CSharpKeywords.IsTypeAliasKeyword(fullyQualifiedName)) { return(new CSharpClassNameValidationResult { Success = false, ErrorDescription = string.Format("Built in type alias '{0}' is not a generic type.", baseName.Builder), ErrorIndex = baseName.StartIndex }); } //success var classDescription = new CSharpClassNameValidationResult() { QualifiedName = fullyQualifiedName, BaseName = baseNameString, GenericArguments = genericArgs, IsGeneric = isGeneric, Success = true }; if (isGeneric) { classDescription.FullSignature = fullyQualifiedName + "<" + string.Join(", ", classDescription.GenericArguments.Select(x => x.FullSignature)) + ">"; } else { classDescription.FullSignature = fullyQualifiedName; } if (validateTypeCallback == null || signatureType != ClassSigType.Initialization) { return(classDescription); } var typeCheckResult = validateTypeCallback(classDescription); if (typeCheckResult.IsValid) { return(classDescription); } classDescription.FullSignature = null; classDescription.ErrorIndex = qualifications.First().StartIndex; classDescription.ErrorDescription = typeCheckResult.ErrorMessage; classDescription.Success = false; return(classDescription); }
/// <summary> /// Validates that the specified input string is syntactically valid C# type initialization signature, including /// generic types. /// </summary> /// <param name="input">The input string containing the proposed type value.</param> /// <param name="validateTypeCallback"> /// A call back to allow you to verify the existence of the types in the type signature /// as they are parsed. /// </param> /// <param name="allowBuiltInAliases">Allow built in aliases such as 'int' or 'char' to pass as class names</param> /// <returns>A parse/validation results object. <see cref="CSharpClassNameValidationResult" /></returns> public static CSharpClassNameValidationResult ValidateInitialization(string input, bool allowBuiltInAliases, CSharpParsedTypeValidateTypeCallback validateTypeCallback = null) { return(_Validate(input, ClassSigType.Initialization, allowBuiltInAliases, validateTypeCallback, 0)); }