예제 #1
0
        public void OnError(RazorError error)
        {
            EnusreNotTerminated();
            AssertOnOwnerTask();

            _errorSink.OnError(error);
        }
            private TagHelperRequiredAttributeValueComparison? ParseCssValueComparison(ErrorSink errorSink)
            {
                Debug.Assert(!AtEnd);
                TagHelperRequiredAttributeValueComparison valueComparison;

                if (CssValueComparisons.TryGetValue(Current, out valueComparison))
                {
                    var op = Current;
                    _index++;

                    if (op != '=' && At('='))
                    {
                        // Two length operator (ex: ^=). Move past the second piece
                        _index++;
                    }
                    else if (op != '=') // We're at an incomplete operator (ex: [foo^]
                    {
                        errorSink.OnError(
                            SourceLocation.Zero,
                            Resources.FormatTagHelperDescriptorFactory_PartialRequiredAttributeOperator(_requiredAttributes, op),
                            length: 0);
                        return null;
                    }
                }
                else if (!At(']'))
                {
                    errorSink.OnError(
                        SourceLocation.Zero,
                        Resources.FormatTagHelperDescriptorFactory_InvalidRequiredAttributeOperator(Current, _requiredAttributes),
                        length: 0);
                    return null;
                }

                return valueComparison;
            }
        private static bool TryValidateName(
            string name,
            string whitespaceError,
            Func <char, string> characterErrorBuilder,
            ErrorSink errorSink)
        {
            var validName = true;

            if (string.IsNullOrWhiteSpace(name))
            {
                errorSink.OnError(SourceLocation.Zero, whitespaceError, length: 0);

                validName = false;
            }
            else
            {
                foreach (var character in name)
                {
                    if (char.IsWhiteSpace(character) ||
                        InvalidNonWhitespaceNameCharacters.Contains(character))
                    {
                        var error = characterErrorBuilder(character);
                        errorSink.OnError(SourceLocation.Zero, error, length: 0);

                        validName = false;
                    }
                }
            }

            return(validName);
        }
        private static bool ValidateName(
            string name,
            bool targetingAttributes,
            ErrorSink errorSink)
        {
            if (!targetingAttributes &&
                string.Equals(
                    name,
                    TagHelperDescriptorProvider.ElementCatchAllTarget,
                    StringComparison.OrdinalIgnoreCase))
            {
                // '*' as the entire name is OK in the TargetElement catch-all case.
                return(true);
            }
            else if (targetingAttributes &&
                     name.EndsWith(
                         TagHelperDescriptorProvider.RequiredAttributeWildcardSuffix,
                         StringComparison.OrdinalIgnoreCase))
            {
                // A single '*' at the end of a required attribute is valid; everywhere else is invalid. Strip it from
                // the end so we can validate the rest of the name.
                name = name.Substring(0, name.Length - 1);
            }

            var targetName = targetingAttributes ?
                             Resources.TagHelperDescriptorFactory_Attribute :
                             Resources.TagHelperDescriptorFactory_Tag;
            var validName = true;

            if (string.IsNullOrWhiteSpace(name))
            {
                errorSink.OnError(
                    SourceLocation.Zero,
                    Resources.FormatTargetElementAttribute_NameCannotBeNullOrWhitespace(targetName));

                validName = false;
            }
            else
            {
                foreach (var character in name)
                {
                    if (char.IsWhiteSpace(character) ||
                        InvalidNonWhitespaceNameCharacters.Contains(character))
                    {
                        errorSink.OnError(
                            SourceLocation.Zero,
                            Resources.FormatTargetElementAttribute_InvalidName(
                                targetName.ToLower(),
                                name,
                                character));

                        validName = false;
                    }
                }
            }

            return(validName);
        }
        private static bool EnsureValidPrefix(
            string prefix,
            SourceLocation directiveLocation,
            ErrorSink errorSink)
        {
            foreach (var character in prefix)
            {
                // Prefixes are correlated with tag names, tag names cannot have whitespace.
                if (char.IsWhiteSpace(character) ||
                    TagHelperDescriptorFactory.InvalidNonWhitespaceNameCharacters.Contains(character))
                {
                    errorSink.OnError(
                        directiveLocation,
                        Resources.FormatTagHelperDescriptorResolver_InvalidTagHelperPrefixValue(
                            SyntaxConstants.CSharp.TagHelperPrefixKeyword,
                            character,
                            prefix),
                        prefix.Length);

                    return(false);
                }
            }

            return(true);
        }
        private static LookupInfo GetLookupInfo(
            TagHelperDirectiveDescriptor directiveDescriptor,
            ErrorSink errorSink)
        {
            var lookupText    = directiveDescriptor.DirectiveText;
            var lookupStrings = lookupText?.Split(new[] { ',' });

            // Ensure that we have valid lookupStrings to work with. The valid format is "typeName, assemblyName"
            if (lookupStrings == null ||
                lookupStrings.Any(string.IsNullOrWhiteSpace) ||
                lookupStrings.Length != 2)
            {
                errorSink.OnError(
                    directiveDescriptor.Location,
                    Resources.FormatTagHelperDescriptorResolver_InvalidTagHelperLookupText(lookupText),
                    GetErrorLength(lookupText));

                return(null);
            }

            var trimmedAssemblyName = lookupStrings[1].Trim();

            // + 1 is for the comma separator in the lookup text.
            var assemblyNameIndex =
                lookupStrings[0].Length + 1 + lookupStrings[1].IndexOf(trimmedAssemblyName, StringComparison.Ordinal);
            var assemblyNamePrefix   = directiveDescriptor.DirectiveText.Substring(0, assemblyNameIndex);
            var assemblyNameLocation = SourceLocation.Advance(directiveDescriptor.Location, assemblyNamePrefix);

            return(new LookupInfo
            {
                TypePattern = lookupStrings[0].Trim(),
                AssemblyName = trimmedAssemblyName,
                AssemblyNameLocation = assemblyNameLocation,
            });
        }
        public void VisitCallsOnCompleteWhenAllNodesHaveBeenVisited()
        {
            // Arrange
            Mock<ParserVisitor> targetMock = new Mock<ParserVisitor>();
            var root = new BlockBuilder() { Type = BlockType.Comment }.Build();
            var errorSink = new ErrorSink();
            errorSink.OnError(new RazorError("Foo", new SourceLocation(1, 0, 1), length: 3));
            errorSink.OnError(new RazorError("Bar", new SourceLocation(2, 0, 2), length: 3));
            var results = new ParserResults(root, Enumerable.Empty<TagHelperDescriptor>(), errorSink);

            // Act
            targetMock.Object.Visit(results);

            // Assert
            targetMock.Verify(v => v.OnComplete());
        }
        /// <summary>
        /// Internal for unit testing.
        /// </summary>
        internal static bool ValidateParentTagName(string parentTag, ErrorSink errorSink)
        {
            if (parentTag == null)
            {
                return true;
            }
            else if (string.IsNullOrWhiteSpace(parentTag))
            {
                var error = Resources.FormatHtmlTargetElementAttribute_NameCannotBeNullOrWhitespace(
                    Resources.TagHelperDescriptorFactory_ParentTag);
                errorSink.OnError(SourceLocation.Zero, error, length: 0);
                return false;
            }
            else if (!TryValidateName(
                parentTag,
                invalidCharacter => Resources.FormatHtmlTargetElementAttribute_InvalidName(
                    Resources.TagHelperDescriptorFactory_ParentTag.ToLower(),
                    parentTag,
                    invalidCharacter),
                errorSink))
            {
                return false;
            }

            return true;
        }
        private static bool ValidateName(string name, bool targetingAttributes, ErrorSink errorSink)
        {
            if (!targetingAttributes &&
                string.Equals(
                    name,
                    TagHelperDescriptorProvider.ElementCatchAllTarget,
                    StringComparison.OrdinalIgnoreCase))
            {
                // '*' as the entire name is OK in the HtmlTargetElement catch-all case.
                return true;
            }

            var targetName = targetingAttributes ?
                Resources.TagHelperDescriptorFactory_Attribute :
                Resources.TagHelperDescriptorFactory_Tag;

            if (string.IsNullOrWhiteSpace(name))
            {
                var error = Resources.FormatHtmlTargetElementAttribute_NameCannotBeNullOrWhitespace(targetName);
                errorSink.OnError(SourceLocation.Zero, error, length: 0);
                return false;
            }
            else if (!TryValidateName(
                name,
                invalidCharacter => Resources.FormatHtmlTargetElementAttribute_InvalidName(
                    targetName.ToLower(),
                    name,
                    invalidCharacter),
                errorSink))
            {
                return false;
            }

            return true;
        }
        public void VisitSendsErrorsToVisitor()
        {
            // Arrange
            var targetMock = new Mock <ParserVisitor>();
            var root       = new BlockBuilder()
            {
                Type = BlockType.Comment
            }.Build();
            var errorSink = new ErrorSink();
            var errors    = new List <RazorError>
            {
                new RazorError("Foo", new SourceLocation(1, 0, 1), length: 3),
                new RazorError("Bar", new SourceLocation(2, 0, 2), length: 3),
            };

            foreach (var error in errors)
            {
                errorSink.OnError(error);
            }
            var results = new ParserResults(root, Enumerable.Empty <TagHelperDescriptor>(), errorSink);

            // Act
            targetMock.Object.Visit(results);

            // Assert
            targetMock.Verify(v => v.VisitError(errors[0]));
            targetMock.Verify(v => v.VisitError(errors[1]));
        }
예제 #11
0
        private static LookupInfo GetLookupInfo(TagHelperDirectiveDescriptor directiveDescriptor,
                                                ErrorSink errorSink)
        {
            var lookupText    = directiveDescriptor.DirectiveText;
            var lookupStrings = lookupText?.Split(new[] { ',' });

            // Ensure that we have valid lookupStrings to work with. Valid formats are:
            // "assemblyName"
            // "typeName, assemblyName"
            if (lookupStrings == null ||
                lookupStrings.Any(string.IsNullOrWhiteSpace) ||
                lookupStrings.Length != 2)
            {
                errorSink.OnError(
                    directiveDescriptor.Location,
                    Resources.FormatTagHelperDescriptorResolver_InvalidTagHelperLookupText(lookupText));

                return(null);
            }

            return(new LookupInfo
            {
                TypePattern = lookupStrings[0].Trim(),
                AssemblyName = lookupStrings[1].Trim()
            });
        }
        // Internal for testing.
        internal static bool ValidateTagHelperAttributeDescriptor(
            TagHelperAttributeDescriptor attributeDescriptor,
            ITypeInfo parentType,
            ErrorSink errorSink)
        {
            string nameOrPrefix;

            if (attributeDescriptor.IsIndexer)
            {
                nameOrPrefix = Resources.TagHelperDescriptorFactory_Prefix;
            }
            else if (string.IsNullOrEmpty(attributeDescriptor.Name))
            {
                errorSink.OnError(
                    SourceLocation.Zero,
                    Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameNullOrEmpty(
                        parentType.FullName,
                        attributeDescriptor.PropertyName),
                    length: 0);

                return(false);
            }
            else
            {
                nameOrPrefix = Resources.TagHelperDescriptorFactory_Name;
            }

            return(ValidateTagHelperAttributeNameOrPrefix(
                       attributeDescriptor.Name,
                       parentType,
                       attributeDescriptor.PropertyName,
                       errorSink,
                       nameOrPrefix));
        }
예제 #13
0
        /// <inheritdoc />
        protected override IEnumerable <TagHelperDescriptor> GetTagHelperDescriptors(
            [NotNull] Block documentRoot,
            [NotNull] ErrorSink errorSink)
        {
            var visitor = new ViewImportsTagHelperDirectiveSpanVisitor(
                TagHelperDescriptorResolver,
                _viewImportsDirectiveDescriptors,
                errorSink);

            var descriptors = visitor.GetDescriptors(documentRoot);

            foreach (var descriptor in descriptors)
            {
                foreach (var attributeDescriptor in descriptor.Attributes)
                {
                    if (attributeDescriptor.IsIndexer &&
                        string.Equals(
                            attributeDescriptor.TypeName,
                            _modelExpressionTypeName,
                            StringComparison.Ordinal))
                    {
                        errorSink.OnError(SourceLocation.Undefined, Resources.FormatMvcRazorParser_InvalidPropertyType(
                                              descriptor.TypeName,
                                              attributeDescriptor.Name,
                                              _modelExpressionTypeName));
                    }
                }
            }

            return(descriptors);
        }
        // Internal for unit testing
        internal static IEnumerable<string> GetValidAllowedChildren(
            IEnumerable<string> allowedChildren,
            string tagHelperName,
            ErrorSink errorSink)
        {
            var validAllowedChildren = new List<string>();

            foreach (var name in allowedChildren)
            {
                if (string.IsNullOrWhiteSpace(name))
                {
                    var whitespaceError = Resources.FormatTagHelperDescriptorFactory_InvalidRestrictChildrenAttributeNameNullWhitespace(
                        nameof(RestrictChildrenAttribute),
                        tagHelperName);
                    errorSink.OnError(SourceLocation.Zero, whitespaceError, length: 0);
                }
                else if (TryValidateName(
                    name,
                    invalidCharacter => Resources.FormatTagHelperDescriptorFactory_InvalidRestrictChildrenAttributeName(
                        nameof(RestrictChildrenAttribute),
                        name,
                        tagHelperName,
                        invalidCharacter),
                    errorSink))
                {
                    validAllowedChildren.Add(name);
                }
            }

            return validAllowedChildren;
        }
예제 #15
0
        private static void ValidateDescriptors(
            IEnumerable <TagHelperDescriptor> descriptors,
            string tagName,
            Block tagBlock,
            ErrorSink errorSink)
        {
            // Ensure that all descriptors associated with this tag have appropriate TagStructures. Cannot have
            // multiple descriptors that expect different TagStructures (other than TagStructure.Unspecified).
            TagHelperDescriptor baseDescriptor = null;

            foreach (var descriptor in descriptors)
            {
                if (descriptor.TagStructure != TagStructure.Unspecified)
                {
                    // Can't have a set of TagHelpers that expect different structures.
                    if (baseDescriptor != null && baseDescriptor.TagStructure != descriptor.TagStructure)
                    {
                        errorSink.OnError(
                            tagBlock.Start,
                            RazorResources.FormatTagHelperParseTreeRewriter_InconsistentTagStructure(
                                baseDescriptor.TypeName,
                                descriptor.TypeName,
                                tagName,
                                nameof(TagHelperDescriptor.TagStructure)),
                            tagBlock.Length);
                    }

                    baseDescriptor = descriptor;
                }
            }
        }
예제 #16
0
        /// <summary>
        /// Locates valid <see cref="ITagHelper"/> types from the <see cref="Assembly"/> named <paramref name="name"/>.
        /// </summary>
        /// <param name="name">The name of an <see cref="Assembly"/> to search.</param>
        /// <param name="documentLocation">The <see cref="SourceLocation"/> of the associated
        /// <see cref="Parser.SyntaxTree.SyntaxTreeNode"/> responsible for the current <see cref="Resolve"/> call.
        /// </param>
        /// <param name="errorSink">The <see cref="ErrorSink"/> used to record errors found when resolving
        /// <see cref="ITagHelper"/> types.</param>
        /// <returns>An <see cref="IEnumerable{ITypeInfo}"/> of valid <see cref="ITagHelper"/> types.</returns>
        public IEnumerable <ITypeInfo> Resolve(
            string name,
            SourceLocation documentLocation,
            ErrorSink errorSink)
        {
            if (errorSink == null)
            {
                throw new ArgumentNullException(nameof(errorSink));
            }

            if (string.IsNullOrEmpty(name))
            {
                var errorLength = name == null ? 1 : Math.Max(name.Length, 1);
                errorSink.OnError(
                    documentLocation,
                    Resources.TagHelperTypeResolver_TagHelperAssemblyNameCannotBeEmptyOrNull,
                    errorLength);

                return(Enumerable.Empty <ITypeInfo>());
            }

            var assemblyName = new AssemblyName(name);

            IEnumerable <ITypeInfo> libraryTypes;

            try
            {
                libraryTypes = GetTopLevelExportedTypes(assemblyName);
            }
            catch (Exception ex)
            {
                errorSink.OnError(
                    documentLocation,
                    Resources.FormatTagHelperTypeResolver_CannotResolveTagHelperAssembly(
                        assemblyName.Name,
                        ex.Message),
                    name.Length);

                return(Enumerable.Empty <ITypeInfo>());
            }

            return(libraryTypes.Where(IsTagHelper));
        }
예제 #17
0
        public static void OnError(this ErrorSink errorSink, MappingLocation mappingLocation, string message)
        {
            var location = new SourceLocation(
                mappingLocation.FilePath,
                mappingLocation.AbsoluteIndex,
                mappingLocation.LineIndex,
                mappingLocation.CharacterIndex);

            errorSink.OnError(location, message, mappingLocation.ContentLength);
        }
예제 #18
0
        /// <inheritdoc />
        public IEnumerable<Type> Resolve(
            string name,
            SourceLocation documentLocation,
            ErrorSink errorSink)
        {
            if (errorSink == null)
            {
                throw new ArgumentNullException(nameof(errorSink));
            }

            if (string.IsNullOrEmpty(name))
            {
                var errorLength = name == null ? 1 : Math.Max(name.Length, 1);
                errorSink.OnError(
                    documentLocation,
                    Resources.TagHelperTypeResolver_TagHelperAssemblyNameCannotBeEmptyOrNull,
                    errorLength);

                return Type.EmptyTypes;
            }

            var assemblyName = new AssemblyName(name);

            IEnumerable<TypeInfo> libraryTypes;
            try
            {
                libraryTypes = GetExportedTypes(assemblyName);
            }
            catch (Exception ex)
            {
                errorSink.OnError(
                    documentLocation,
                    Resources.FormatTagHelperTypeResolver_CannotResolveTagHelperAssembly(
                        assemblyName.Name,
                        ex.Message),
                    name.Length);

                return Type.EmptyTypes;
            }

            return libraryTypes.Where(IsTagHelper).Select(t => t.AsType());
        }
        public void VisitCallsOnCompleteWhenAllNodesHaveBeenVisited()
        {
            // Arrange
            var targetMock = new Mock <ParserVisitor>();
            var root       = new BlockBuilder()
            {
                Type = BlockType.Comment
            }.Build();
            var errorSink = new ErrorSink();

            errorSink.OnError(new RazorError("Foo", new SourceLocation(1, 0, 1), length: 3));
            errorSink.OnError(new RazorError("Bar", new SourceLocation(2, 0, 2), length: 3));
            var results = new ParserResults(root, Enumerable.Empty <TagHelperDescriptor>(), errorSink);

            // Act
            targetMock.Object.Visit(results);

            // Assert
            targetMock.Verify(v => v.OnComplete());
        }
        public IEnumerable <Type> Resolve(
            string name,
            SourceLocation documentLocation,
            ErrorSink errorSink)
        {
            if (errorSink == null)
            {
                throw new ArgumentNullException(nameof(errorSink));
            }

            if (string.IsNullOrEmpty(name))
            {
                var errorLength = name == null ? 1 : Math.Max(name.Length, 1);
                errorSink.OnError(
                    documentLocation,
                    "Tag Helper Assembly Name Cannot Be Empty Or Null",
                    errorLength);

                return(Type.EmptyTypes);
            }


            IEnumerable <TypeInfo> libraryTypes;

            try
            {
                libraryTypes = GetExportedTypes(name);
            }
            catch (Exception ex)
            {
                errorSink.OnError(
                    documentLocation,
                    $"Cannot Resolve Tag Helper Assembly: {name}, {ex.Message}",
                    name.Length);

                return(Type.EmptyTypes);
            }

            return(libraryTypes.Select(a => a.AsType()));
        }
예제 #21
0
        private static void OnAllowedChildrenTagError(
            TagHelperBlockTracker tracker,
            Block tagBlock,
            ErrorSink errorSink)
        {
            var tagName = GetTagName(tagBlock);
            var allowedChildrenString = string.Join(", ", tracker.AllowedChildren);
            var errorMessage          = RazorResources.FormatTagHelperParseTreeRewriter_InvalidNestedTag(
                tagName,
                tracker.Builder.TagName,
                allowedChildrenString);

            errorSink.OnError(tagBlock.Start, errorMessage, tagBlock.Length);
        }
            private bool EnsureNotAtEnd(ErrorSink errorSink)
            {
                if (AtEnd)
                {
                    errorSink.OnError(
                        SourceLocation.Zero,
                        Resources.FormatTagHelperDescriptorFactory_CouldNotFindMatchingEndBrace(_requiredAttributes),
                        length: 0);

                    return false;
                }

                return true;
            }
예제 #23
0
        /// <summary>
        /// Loads an <see cref="Assembly"/> using the given <paramref name="name"/> and resolves
        /// all valid <see cref="ITagHelper"/> <see cref="Type"/>s.
        /// </summary>
        /// <param name="name">The name of an <see cref="Assembly"/> to search.</param>
        /// <param name="documentLocation">The <see cref="SourceLocation"/> of the associated
        /// <see cref="Parser.SyntaxTree.SyntaxTreeNode"/> responsible for the current <see cref="Resolve"/> call.
        /// </param>
        /// <param name="errorSink">The <see cref="ErrorSink"/> used to record errors found when resolving
        /// <see cref="ITagHelper"/> <see cref="Type"/>s.</param>
        /// <returns>An <see cref="IEnumerable{Type}"/> of valid <see cref="ITagHelper"/> <see cref="Type"/>s.
        /// </returns>
        public IEnumerable <Type> Resolve(string name,
                                          SourceLocation documentLocation,
                                          [NotNull] ErrorSink errorSink)
        {
            if (string.IsNullOrEmpty(name))
            {
                errorSink.OnError(documentLocation,
                                  Resources.TagHelperTypeResolver_TagHelperAssemblyNameCannotBeEmptyOrNull);

                return(Type.EmptyTypes);
            }

            var assemblyName = new AssemblyName(name);

            IEnumerable <TypeInfo> libraryTypes;

            try
            {
                libraryTypes = GetExportedTypes(assemblyName);
            }
            catch (Exception ex)
            {
                errorSink.OnError(
                    documentLocation,
                    Resources.FormatTagHelperTypeResolver_CannotResolveTagHelperAssembly(
                        assemblyName.Name,
                        ex.Message));

                return(Type.EmptyTypes);
            }

            var validTagHelpers = libraryTypes.Where(IsTagHelper);

            // Convert from TypeInfo[] to Type[]
            return(validTagHelpers.Select(type => type.AsType()));
        }
        private static void OnAllowedChildrenEndTagError(
            TagHelperTracker tracker,
            string tagName,
            MarkupEndTagSyntax tagBlock,
            ErrorSink errorSink,
            RazorSourceDocument source)
        {
            var allowedChildrenString = string.Join(", ", tracker.AllowedChildren);
            var errorStart            = GetEndTagDeclarationErrorStart(tagBlock, source);

            errorSink.OnError(
                RazorDiagnosticFactory.CreateTagHelper_InvalidNestedTag(
                    new SourceSpan(errorStart, tagName.Length),
                    tagName,
                    tracker.TagName,
                    allowedChildrenString));
        }
예제 #25
0
        public void Compile_ReturnsFailedResultIfParseFails()
        {
            // Arrange
            var errorSink = new ErrorSink();

            errorSink.OnError(new RazorError("some message", 1, 1, 1, 1));
            var generatorResult = new GeneratorResults(
                new Block(new BlockBuilder {
                Type = BlockType.Comment
            }),
                Enumerable.Empty <TagHelperDescriptor>(),
                errorSink,
                new CodeGeneratorResult("", new LineMapping[0]),
                new ChunkTree());
            var host = new Mock <IMvcRazorHost>();

            host.Setup(h => h.GenerateCode(It.IsAny <string>(), It.IsAny <Stream>()))
            .Returns(generatorResult)
            .Verifiable();

            var fileInfo = new Mock <IFileInfo>();

            fileInfo.Setup(f => f.CreateReadStream())
            .Returns(Stream.Null);

            var compiler         = new Mock <ICompilationService>(MockBehavior.Strict);
            var relativeFileInfo = new RelativeFileInfo(fileInfo.Object, @"Views\index\home.cshtml");
            var razorService     = new RazorCompilationService(compiler.Object, host.Object, GetOptions());

            // Act
            var result = razorService.Compile(relativeFileInfo);

            // Assert
            Assert.NotNull(result.CompilationFailures);
            Assert.Collection(result.CompilationFailures,
                              failure =>
            {
                var message = Assert.Single(failure.Messages);
                Assert.Equal("some message", message.Message);
            });
            host.Verify();
        }
 private void ValidateParentAllowsContent(Span child, ErrorSink errorSink)
 {
     if (HasAllowedChildren())
     {
         var content = child.Content;
         if (!string.IsNullOrWhiteSpace(content))
         {
             var trimmedStart          = content.TrimStart();
             var whitespace            = content.Substring(0, content.Length - trimmedStart.Length);
             var errorStart            = SourceLocation.Advance(child.Start, whitespace);
             var length                = trimmedStart.TrimEnd().Length;
             var allowedChildren       = _currentTagHelperTracker.AllowedChildren;
             var allowedChildrenString = string.Join(", ", allowedChildren);
             errorSink.OnError(
                 errorStart,
                 RazorResources.FormatTagHelperParseTreeRewriter_CannotHaveNonTagContent(
                     _currentTagHelperTracker.TagName,
                     allowedChildrenString),
                 length);
         }
     }
 }
예제 #27
0
            private string ParseCssValue(ErrorSink errorSink)
            {
                int valueStart;
                int valueEnd;

                if (At('\'') || At('"'))
                {
                    var quote = Current;

                    // Move past the quote
                    _index++;

                    valueStart = _index;
                    valueEnd   = _requiredAttributes.IndexOf(quote, _index);
                    if (valueEnd == -1)
                    {
                        errorSink.OnError(
                            SourceLocation.Zero,
                            Resources.FormatTagHelperDescriptorFactory_InvalidRequiredAttributeMismatchedQuotes(
                                _requiredAttributes,
                                quote),
                            length: 0);
                        return(null);
                    }
                    _index = valueEnd + 1;
                }
                else
                {
                    valueStart = _index;
                    var valueEndIndex = _requiredAttributes.IndexOfAny(InvalidCssQuotelessValueCharacters, _index);
                    valueEnd = valueEndIndex == -1 ? _requiredAttributes.Length : valueEndIndex;
                    _index   = valueEnd;
                }

                var value = _requiredAttributes.Substring(valueStart, valueEnd - valueStart);

                return(value);
            }
예제 #28
0
        public void Compile_ReturnsFailedResultIfParseFails()
        {
            // Arrange
            var errorSink = new ErrorSink();
            errorSink.OnError(new RazorError("some message", 1, 1, 1, 1));
            var generatorResult = new GeneratorResults(
                    new Block(new BlockBuilder { Type = BlockType.Comment }),
                    Enumerable.Empty<TagHelperDescriptor>(),
                    errorSink,
                    new CodeGeneratorResult("", new LineMapping[0]),
                    new ChunkTree());
            var host = new Mock<IMvcRazorHost>();
            host.Setup(h => h.GenerateCode(It.IsAny<string>(), It.IsAny<Stream>()))
                .Returns(generatorResult)
                .Verifiable();

            var fileInfo = new Mock<IFileInfo>();
            fileInfo.Setup(f => f.CreateReadStream())
                    .Returns(Stream.Null);

            var compiler = new Mock<ICompilationService>(MockBehavior.Strict);
            var relativeFileInfo = new RelativeFileInfo(fileInfo.Object, @"Views\index\home.cshtml");
            var razorService = new RazorCompilationService(compiler.Object, host.Object, GetFileProviderAccessor(), NullLoggerFactory.Instance);

            // Act
            var result = razorService.Compile(relativeFileInfo);

            // Assert
            Assert.NotNull(result.CompilationFailures);
            Assert.Collection(result.CompilationFailures,
                failure =>
                {
                    var message = Assert.Single(failure.Messages);
                    Assert.Equal("some message", message.Message);
                });
            host.Verify();
        }
        public void VisitSendsErrorsToVisitor()
        {
            // Arrange
            Mock<ParserVisitor> targetMock = new Mock<ParserVisitor>();
            var root = new BlockBuilder() { Type = BlockType.Comment }.Build();
            var errorSink = new ErrorSink();
            List<RazorError> errors = new List<RazorError>
            {
                new RazorError("Foo", new SourceLocation(1, 0, 1), length: 3),
                new RazorError("Bar", new SourceLocation(2, 0, 2), length: 3),
            };
            foreach (var error in errors)
            {
                errorSink.OnError(error);
            }
            var results = new ParserResults(root, Enumerable.Empty<TagHelperDescriptor>(), errorSink);

            // Act
            targetMock.Object.Visit(results);

            // Assert
            targetMock.Verify(v => v.VisitError(errors[0]));
            targetMock.Verify(v => v.VisitError(errors[1]));
        }
        private static bool EnsureValidPrefix(
            string prefix,
            SourceLocation directiveLocation,
            ErrorSink errorSink)
        {
            foreach (var character in prefix)
            {
                // Prefixes are correlated with tag names, tag names cannot have whitespace.
                if (char.IsWhiteSpace(character) ||
                    TagHelperDescriptorFactory.InvalidNonWhitespaceNameCharacters.Contains(character))
                {
                    errorSink.OnError(
                        directiveLocation,
                        Resources.FormatTagHelperDescriptorResolver_InvalidTagHelperPrefixValue(
                            SyntaxConstants.CSharp.TagHelperPrefixKeyword,
                            character,
                            prefix),
                        prefix.Length);

                    return false;
                }
            }

            return true;
        }
        private static TryParseResult TryParseBlock(
            string tagName,
            Block block,
            IEnumerable <TagHelperDescriptor> descriptors,
            ErrorSink errorSink)
        {
            // TODO: Accept more than just spans: https://github.com/aspnet/Razor/issues/96.
            // The first child will only ever NOT be a Span if a user is doing something like:
            // <input @checked />

            var childSpan = block.Children.First() as Span;

            if (childSpan == null || childSpan.Kind != SpanKind.Markup)
            {
                errorSink.OnError(
                    block.Start,
                    RazorResources.FormatTagHelpers_CannotHaveCSharpInTagDeclaration(tagName),
                    block.Length);

                return(null);
            }

            var builder = new BlockBuilder(block);

            // If there's only 1 child it means that it's plain text inside of the attribute.
            // i.e. <div class="plain text in attribute">
            if (builder.Children.Count == 1)
            {
                return(TryParseSpan(childSpan, descriptors, errorSink));
            }

            var nameSymbols = childSpan
                              .Symbols
                              .OfType <HtmlSymbol>()
                              .SkipWhile(symbol => !HtmlMarkupParser.IsValidAttributeNameSymbol(symbol)) // Skip prefix
                              .TakeWhile(nameSymbol => HtmlMarkupParser.IsValidAttributeNameSymbol(nameSymbol))
                              .Select(nameSymbol => nameSymbol.Content);

            var name = string.Concat(nameSymbols);

            if (string.IsNullOrEmpty(name))
            {
                errorSink.OnError(
                    childSpan.Start,
                    RazorResources.FormatTagHelpers_AttributesMustHaveAName(tagName),
                    childSpan.Length);

                return(null);
            }

            // Have a name now. Able to determine correct isBoundNonStringAttribute value.
            var result = CreateTryParseResult(name, descriptors);

            var firstChild = builder.Children[0] as Span;

            if (firstChild != null && firstChild.Symbols[0] is HtmlSymbol)
            {
                var htmlSymbol = firstChild.Symbols[firstChild.Symbols.Count - 1] as HtmlSymbol;
                switch (htmlSymbol.Type)
                {
                // Treat NoQuotes and DoubleQuotes equivalently. We purposefully do not persist NoQuotes
                // ValueStyles at code generation time to protect users from rendering dynamic content with spaces
                // that can break attributes.
                // Ex: <tag my-attribute=@value /> where @value results in the test "hello world".
                // This way, the above code would render <tag my-attribute="hello world" />.
                case HtmlSymbolType.Equals:
                case HtmlSymbolType.DoubleQuote:
                    result.AttributeValueStyle = HtmlAttributeValueStyle.DoubleQuotes;
                    break;

                case HtmlSymbolType.SingleQuote:
                    result.AttributeValueStyle = HtmlAttributeValueStyle.SingleQuotes;
                    break;

                default:
                    result.AttributeValueStyle = HtmlAttributeValueStyle.Minimized;
                    break;
                }
            }

            // Remove first child i.e. foo="
            builder.Children.RemoveAt(0);

            // Grabbing last child to check if the attribute value is quoted.
            var endNode = block.Children.Last();

            if (!endNode.IsBlock)
            {
                var endSpan = (Span)endNode;

                // In some malformed cases e.g. <p bar="false', the last Span (false' in the ex.) may contain more
                // than a single HTML symbol. Do not ignore those other symbols.
                var symbolCount = endSpan.Symbols.Count();
                var endSymbol   = symbolCount == 1 ? (HtmlSymbol)endSpan.Symbols.First() : null;

                // Checking to see if it's a quoted attribute, if so we should remove end quote
                if (endSymbol != null && IsQuote(endSymbol))
                {
                    builder.Children.RemoveAt(builder.Children.Count - 1);
                }
            }

            // We need to rebuild the chunk generators of the builder and its children (this is needed to
            // ensure we don't do special attribute chunk generation since this is a tag helper).
            block = RebuildChunkGenerators(builder.Build(), result.IsBoundAttribute);

            // If there's only 1 child at this point its value could be a simple markup span (treated differently than
            // block level elements for attributes).
            if (block.Children.Count() == 1)
            {
                var child = block.Children.First() as Span;
                if (child != null)
                {
                    // After pulling apart the block we just have a value span.
                    var spanBuilder = new SpanBuilder(child);

                    result.AttributeValueNode =
                        CreateMarkupAttribute(spanBuilder, result.IsBoundNonStringAttribute);

                    return(result);
                }
            }

            var isFirstSpan = true;

            result.AttributeValueNode = ConvertToMarkupAttributeBlock(
                block,
                (parentBlock, span) =>
            {
                // If the attribute was requested by a tag helper but the corresponding property was not a
                // string, then treat its value as code. A non-string value can be any C# value so we need
                // to ensure the SyntaxTreeNode reflects that.
                if (result.IsBoundNonStringAttribute)
                {
                    // For bound non-string attributes, we'll only allow a transition span to appear at the very
                    // beginning of the attribute expression. All later transitions would appear as code so that
                    // they are part of the generated output. E.g.
                    // key="@value" -> MyTagHelper.key = value
                    // key=" @value" -> MyTagHelper.key =  @value
                    // key="1 + @case" -> MyTagHelper.key = 1 + @case
                    // key="@int + @case" -> MyTagHelper.key = int + @case
                    // key="@(a + b) -> MyTagHelper.key = a + b
                    // key="4 + @(a + b)" -> MyTagHelper.key = 4 + @(a + b)
                    if (isFirstSpan && span.Kind == SpanKind.Transition)
                    {
                        // do nothing.
                    }
                    else
                    {
                        var spanBuilder = new SpanBuilder(span);

                        if (parentBlock.Type == BlockType.Expression &&
                            (spanBuilder.Kind == SpanKind.Transition ||
                             spanBuilder.Kind == SpanKind.MetaCode))
                        {
                            // Change to a MarkupChunkGenerator so that the '@' \ parenthesis is generated as part of the output.
                            spanBuilder.ChunkGenerator = new MarkupChunkGenerator();
                        }

                        spanBuilder.Kind = SpanKind.Code;
                        span             = spanBuilder.Build();
                    }
                }

                isFirstSpan = false;

                return(span);
            });

            return(result);
        }
        private static IList <TagHelperAttributeNode> GetTagAttributes(
            string tagName,
            bool validStructure,
            Block tagBlock,
            IEnumerable <TagHelperDescriptor> descriptors,
            ErrorSink errorSink)
        {
            // Ignore all but one descriptor per type since this method uses the TagHelperDescriptors only to get the
            // contained TagHelperAttributeDescriptor's.
            descriptors = descriptors.Distinct(TypeBasedTagHelperDescriptorComparer.Default);

            var attributes = new List <TagHelperAttributeNode>();

            // We skip the first child "<tagname" and take everything up to the ending portion of the tag ">" or "/>".
            // The -2 accounts for both the start and end tags. If the tag does not have a valid structure then there's
            // no end tag to ignore.
            var symbolOffset      = validStructure ? 2 : 1;
            var attributeChildren = tagBlock.Children.Skip(1).Take(tagBlock.Children.Count() - symbolOffset);

            foreach (var child in attributeChildren)
            {
                TryParseResult result;
                if (child.IsBlock)
                {
                    result = TryParseBlock(tagName, (Block)child, descriptors, errorSink);
                }
                else
                {
                    result = TryParseSpan((Span)child, descriptors, errorSink);
                }

                // Only want to track the attribute if we succeeded in parsing its corresponding Block/Span.
                if (result != null)
                {
                    SourceLocation?errorLocation = null;

                    // Check if it's a bound attribute that is minimized or if it's a bound non-string attribute that
                    // is null or whitespace.
                    if ((result.IsBoundAttribute && result.AttributeValueNode == null) ||
                        (result.IsBoundNonStringAttribute &&
                         IsNullOrWhitespaceAttributeValue(result.AttributeValueNode)))
                    {
                        errorLocation = GetAttributeNameStartLocation(child);

                        errorSink.OnError(
                            errorLocation.Value,
                            RazorResources.FormatRewriterError_EmptyTagHelperBoundAttribute(
                                result.AttributeName,
                                tagName,
                                GetPropertyType(result.AttributeName, descriptors)),
                            result.AttributeName.Length);
                    }

                    // Check if the attribute was a prefix match for a tag helper dictionary property but the
                    // dictionary key would be the empty string.
                    if (result.IsMissingDictionaryKey)
                    {
                        if (!errorLocation.HasValue)
                        {
                            errorLocation = GetAttributeNameStartLocation(child);
                        }

                        errorSink.OnError(
                            errorLocation.Value,
                            RazorResources.FormatTagHelperBlockRewriter_IndexerAttributeNameMustIncludeKey(
                                result.AttributeName,
                                tagName),
                            result.AttributeName.Length);
                    }

                    var attributeNode = new TagHelperAttributeNode(
                        result.AttributeName,
                        result.AttributeValueNode,
                        result.AttributeValueStyle);

                    attributes.Add(attributeNode);
                }
                else
                {
                    // Error occured while parsing the attribute. Don't try parsing the rest to avoid misleading errors.
                    break;
                }
            }

            return(attributes);
        }
예제 #33
0
        /// <inheritdoc />
        protected override IEnumerable<TagHelperDescriptor> GetTagHelperDescriptors(
            Block documentRoot,
            ErrorSink errorSink)
        {
            if (documentRoot == null)
            {
                throw new ArgumentNullException(nameof(documentRoot));
            }

            if (errorSink == null)
            {
                throw new ArgumentNullException(nameof(errorSink));
            }

            var visitor = new ViewImportsTagHelperDirectiveSpanVisitor(
                TagHelperDescriptorResolver,
                _viewImportsDirectiveDescriptors,
                errorSink);

            var descriptors = visitor.GetDescriptors(documentRoot);
            foreach (var descriptor in descriptors)
            {
                foreach (var attributeDescriptor in descriptor.Attributes)
                {
                    if (attributeDescriptor.IsIndexer &&
                        string.Equals(
                            attributeDescriptor.TypeName,
                            _modelExpressionTypeName,
                            StringComparison.Ordinal))
                    {
                        errorSink.OnError(
                            SourceLocation.Undefined,
                            Resources.FormatMvcRazorParser_InvalidPropertyType(
                                descriptor.TypeName,
                                attributeDescriptor.Name,
                                _modelExpressionTypeName),
                            length: 0);
                    }
                }
            }

            return descriptors;
        }
예제 #34
0
        private static bool ValidateTagHelperAttributeNameOrPrefix(
            string attributeNameOrPrefix,
            Type parentType,
            string propertyName,
            ErrorSink errorSink,
            string nameOrPrefix)
        {
            if (string.IsNullOrEmpty(attributeNameOrPrefix))
            {
                // ValidateTagHelperAttributeDescriptor validates Name is non-null and non-empty. The empty string is
                // valid for DictionaryAttributePrefix and null is impossible at this point because it means "don't
                // create a descriptor". (Empty DictionaryAttributePrefix is a corner case which would bind every
                // attribute of a target element. Likely not particularly useful but unclear what minimum length
                // should be required and what scenarios a minimum length would break.)
                return true;
            }

            if (string.IsNullOrWhiteSpace(attributeNameOrPrefix))
            {
                // Provide a single error if the entire name is whitespace, not an error per character.
                errorSink.OnError(
                    SourceLocation.Zero,
                    Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameOrPrefixWhitespace(
                        parentType.FullName,
                        propertyName,
                        nameOrPrefix));

                return false;
            }

            // data-* attributes are explicitly not implemented by user agents and are not intended for use on
            // the server; therefore it's invalid for TagHelpers to bind to them.
            if (attributeNameOrPrefix.StartsWith(DataDashPrefix, StringComparison.OrdinalIgnoreCase))
            {
                errorSink.OnError(
                    SourceLocation.Zero,
                    Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameOrPrefixStart(
                        parentType.FullName,
                        propertyName,
                        nameOrPrefix,
                        attributeNameOrPrefix,
                        DataDashPrefix));

                return false;
            }

            var isValid = true;
            foreach (var character in attributeNameOrPrefix)
            {
                if (char.IsWhiteSpace(character) || InvalidNonWhitespaceNameCharacters.Contains(character))
                {
                    errorSink.OnError(
                        SourceLocation.Zero,
                        Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameOrPrefixCharacter(
                            parentType.FullName,
                            propertyName,
                            nameOrPrefix,
                            attributeNameOrPrefix,
                            character));

                    isValid = false;
                }
            }

            return isValid;
        }
        private static IEnumerable <TagHelperAttributeDescriptor> GetAttributeDescriptors(
            ITypeInfo type,
            bool designTime,
            ErrorSink errorSink)
        {
            var attributeDescriptors = new List <TagHelperAttributeDescriptor>();

            // Keep indexer descriptors separate to avoid sorting the combined list later.
            var indexerDescriptors = new List <TagHelperAttributeDescriptor>();

            var accessibleProperties = type.Properties.Where(IsAccessibleProperty);

            foreach (var property in accessibleProperties)
            {
                if (ShouldSkipDescriptorCreation(designTime, property))
                {
                    continue;
                }

                var attributeNameAttribute = property
                                             .GetCustomAttributes <HtmlAttributeNameAttribute>()
                                             .FirstOrDefault();
                var hasExplicitName =
                    attributeNameAttribute != null && !string.IsNullOrEmpty(attributeNameAttribute.Name);
                var attributeName = hasExplicitName ? attributeNameAttribute.Name : ToHtmlCase(property.Name);

                TagHelperAttributeDescriptor mainDescriptor = null;
                if (property.HasPublicSetter)
                {
                    mainDescriptor = ToAttributeDescriptor(property, attributeName, designTime);
                    if (!ValidateTagHelperAttributeDescriptor(mainDescriptor, type, errorSink))
                    {
                        // HtmlAttributeNameAttribute.Name is invalid. Ignore this property completely.
                        continue;
                    }
                }
                else if (hasExplicitName)
                {
                    // Specified HtmlAttributeNameAttribute.Name though property has no public setter.
                    errorSink.OnError(
                        SourceLocation.Zero,
                        Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty(
                            type.FullName,
                            property.Name,
                            typeof(HtmlAttributeNameAttribute).FullName,
                            nameof(HtmlAttributeNameAttribute.Name)),
                        length: 0);
                    continue;
                }

                bool isInvalid;
                var  indexerDescriptor = ToIndexerAttributeDescriptor(
                    property,
                    attributeNameAttribute,
                    parentType: type,
                    errorSink: errorSink,
                    defaultPrefix: attributeName + "-",
                    designTime: designTime,
                    isInvalid: out isInvalid);
                if (indexerDescriptor != null &&
                    !ValidateTagHelperAttributeDescriptor(indexerDescriptor, type, errorSink))
                {
                    isInvalid = true;
                }

                if (isInvalid)
                {
                    // The property type or HtmlAttributeNameAttribute.DictionaryAttributePrefix (or perhaps the
                    // HTML-casing of the property name) is invalid. Ignore this property completely.
                    continue;
                }

                if (mainDescriptor != null)
                {
                    attributeDescriptors.Add(mainDescriptor);
                }

                if (indexerDescriptor != null)
                {
                    indexerDescriptors.Add(indexerDescriptor);
                }
            }

            attributeDescriptors.AddRange(indexerDescriptors);

            return(attributeDescriptors);
        }
        private static bool ValidateTagHelperAttributeNameOrPrefix(
            string attributeNameOrPrefix,
            ITypeInfo parentType,
            string propertyName,
            ErrorSink errorSink,
            string nameOrPrefix)
        {
            if (string.IsNullOrEmpty(attributeNameOrPrefix))
            {
                // ValidateTagHelperAttributeDescriptor validates Name is non-null and non-empty. The empty string is
                // valid for DictionaryAttributePrefix and null is impossible at this point because it means "don't
                // create a descriptor". (Empty DictionaryAttributePrefix is a corner case which would bind every
                // attribute of a target element. Likely not particularly useful but unclear what minimum length
                // should be required and what scenarios a minimum length would break.)
                return(true);
            }

            if (string.IsNullOrWhiteSpace(attributeNameOrPrefix))
            {
                // Provide a single error if the entire name is whitespace, not an error per character.
                errorSink.OnError(
                    SourceLocation.Zero,
                    Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameOrPrefixWhitespace(
                        parentType.FullName,
                        propertyName,
                        nameOrPrefix),
                    length: 0);

                return(false);
            }

            // data-* attributes are explicitly not implemented by user agents and are not intended for use on
            // the server; therefore it's invalid for TagHelpers to bind to them.
            if (attributeNameOrPrefix.StartsWith(DataDashPrefix, StringComparison.OrdinalIgnoreCase))
            {
                errorSink.OnError(
                    SourceLocation.Zero,
                    Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameOrPrefixStart(
                        parentType.FullName,
                        propertyName,
                        nameOrPrefix,
                        attributeNameOrPrefix,
                        DataDashPrefix),
                    length: 0);

                return(false);
            }

            var isValid = true;

            foreach (var character in attributeNameOrPrefix)
            {
                if (char.IsWhiteSpace(character) || InvalidNonWhitespaceNameCharacters.Contains(character))
                {
                    errorSink.OnError(
                        SourceLocation.Zero,
                        Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameOrPrefixCharacter(
                            parentType.FullName,
                            propertyName,
                            nameOrPrefix,
                            attributeNameOrPrefix,
                            character),
                        length: 0);

                    isValid = false;
                }
            }

            return(isValid);
        }
예제 #37
0
        // This method handles cases when the attribute is a simple span attribute such as
        // class="something moresomething".  This does not handle complex attributes such as
        // class="@myclass". Therefore the span.Content is equivalent to the entire attribute.
        private static TryParseResult TryParseSpan(
            Span span,
            IEnumerable<TagHelperDescriptor> descriptors,
            ErrorSink errorSink)
        {
            var afterEquals = false;
            var builder = new SpanBuilder
            {
                ChunkGenerator = span.ChunkGenerator,
                EditHandler = span.EditHandler,
                Kind = span.Kind
            };

            // Will contain symbols that represent a single attribute value: <input| class="btn"| />
            var htmlSymbols = span.Symbols.OfType<HtmlSymbol>().ToArray();
            var capturedAttributeValueStart = false;
            var attributeValueStartLocation = span.Start;

            // The symbolOffset is initialized to 0 to expect worst case: "class=". If a quote is found later on for
            // the attribute value the symbolOffset is adjusted accordingly.
            var symbolOffset = 0;
            string name = null;

            // Iterate down through the symbols to find the name and the start of the value.
            // We subtract the symbolOffset so we don't accept an ending quote of a span.
            for (var i = 0; i < htmlSymbols.Length - symbolOffset; i++)
            {
                var symbol = htmlSymbols[i];

                if (afterEquals)
                {
                    // We've captured all leading whitespace, the attribute name, and an equals with an optional
                    // quote/double quote. We're now at: " asp-for='|...'" or " asp-for=|..."
                    // The goal here is to capture all symbols until the end of the attribute. Note this will not
                    // consume an ending quote due to the symbolOffset.

                    // When symbols are accepted into SpanBuilders, their locations get altered to be offset by the
                    // parent which is why we need to mark our start location prior to adding the symbol.
                    // This is needed to know the location of the attribute value start within the document.
                    if (!capturedAttributeValueStart)
                    {
                        capturedAttributeValueStart = true;

                        attributeValueStartLocation = span.Start + symbol.Start;
                    }

                    builder.Accept(symbol);
                }
                else if (name == null && HtmlMarkupParser.IsValidAttributeNameSymbol(symbol))
                {
                    // We've captured all leading whitespace prior to the attribute name.
                    // We're now at: " |asp-for='...'" or " |asp-for=..."
                    // The goal here is to capture the attribute name.

                    var symbolContents = htmlSymbols
                        .Skip(i) // Skip prefix
                        .TakeWhile(nameSymbol => HtmlMarkupParser.IsValidAttributeNameSymbol(nameSymbol))
                        .Select(nameSymbol => nameSymbol.Content);

                    // Move the indexer past the attribute name symbols.
                    i += symbolContents.Count() - 1;

                    name = string.Concat(symbolContents);
                    attributeValueStartLocation = SourceLocation.Advance(attributeValueStartLocation, name);
                }
                else if (symbol.Type == HtmlSymbolType.Equals)
                {
                    Debug.Assert(
                        name != null,
                        "Name should never be null here. The parser should guarantee an attribute has a name.");

                    // We've captured all leading whitespace and the attribute name.
                    // We're now at: " asp-for|='...'" or " asp-for|=..."
                    // The goal here is to consume the equal sign and the optional single/double-quote.

                    // The coming symbols will either be a quote or value (in the case that the value is unquoted).

                    SourceLocation symbolStartLocation;

                    // Skip the whitespace preceding the start of the attribute value.
                    do
                    {
                        i++; // Start from the symbol after '='.
                    } while (i < htmlSymbols.Length &&
                        (htmlSymbols[i].Type == HtmlSymbolType.WhiteSpace ||
                        htmlSymbols[i].Type == HtmlSymbolType.NewLine));

                    // Check for attribute start values, aka single or double quote
                    if (i < htmlSymbols.Length && IsQuote(htmlSymbols[i]))
                    {
                        symbolStartLocation = htmlSymbols[i].Start;

                        // If there's a start quote then there must be an end quote to be valid, skip it.
                        symbolOffset = 1;
                    }
                    else
                    {
                        // We are at the symbol after equals. Go back to equals to ensure we don't skip past that symbol.
                        i--;

                        symbolStartLocation = symbol.Start;
                    }

                    attributeValueStartLocation =
                        span.Start +
                        symbolStartLocation +
                        new SourceLocation(absoluteIndex: 1, lineIndex: 0, characterIndex: 1);

                    afterEquals = true;
                }
                else if (symbol.Type == HtmlSymbolType.WhiteSpace)
                {
                    // We're at the start of the attribute, this branch may be hit on the first iterations of
                    // the loop since the parser separates attributes with their spaces included as symbols.
                    // We're at: "| asp-for='...'" or "| asp-for=..."
                    // Note: This will not be hit even for situations like asp-for  ="..." because the core Razor
                    // parser currently does not know how to handle attributes in that format. This will be addressed
                    // by https://github.com/aspnet/Razor/issues/123.

                    attributeValueStartLocation = SourceLocation.Advance(attributeValueStartLocation, symbol.Content);
                }
            }

            // After all symbols have been added we need to set the builders start position so we do not indirectly
            // modify each symbol's Start location.
            builder.Start = attributeValueStartLocation;

            if (name == null)
            {
                // We couldn't find a name, if the original span content was whitespace it ultimately means the tag
                // that owns this "attribute" is malformed and is expecting a user to type a new attribute.
                // ex: <myTH class="btn"| |
                if (!string.IsNullOrWhiteSpace(span.Content))
                {
                    errorSink.OnError(
                        span.Start,
                        RazorResources.TagHelperBlockRewriter_TagHelperAttributeListMustBeWellFormed,
                        span.Content.Length);
                }

                return null;
            }

            var result = CreateTryParseResult(name, descriptors);

            // If we're not after an equal then we should treat the value as if it were a minimized attribute.
            Span attributeValue = null;
            if (afterEquals)
            {
                attributeValue = CreateMarkupAttribute(builder, result.IsBoundNonStringAttribute);
            }

            result.AttributeValueNode = attributeValue;
            return result;
        }
예제 #38
0
        private static TryParseResult TryParseBlock(
            string tagName,
            Block block,
            IEnumerable<TagHelperDescriptor> descriptors,
            ErrorSink errorSink)
        {
            // TODO: Accept more than just spans: https://github.com/aspnet/Razor/issues/96.
            // The first child will only ever NOT be a Span if a user is doing something like:
            // <input @checked />

            var childSpan = block.Children.First() as Span;

            if (childSpan == null || childSpan.Kind != SpanKind.Markup)
            {
                errorSink.OnError(
                    block.Start,
                    RazorResources.FormatTagHelpers_CannotHaveCSharpInTagDeclaration(tagName),
                    block.Length);

                return null;
            }

            var builder = new BlockBuilder(block);

            // If there's only 1 child it means that it's plain text inside of the attribute.
            // i.e. <div class="plain text in attribute">
            if (builder.Children.Count == 1)
            {
                return TryParseSpan(childSpan, descriptors, errorSink);
            }

            var nameSymbols = childSpan
                .Symbols
                .OfType<HtmlSymbol>()
                .SkipWhile(symbol => !HtmlMarkupParser.IsValidAttributeNameSymbol(symbol)) // Skip prefix
                .TakeWhile(nameSymbol => HtmlMarkupParser.IsValidAttributeNameSymbol(nameSymbol))
                .Select(nameSymbol => nameSymbol.Content);

            var name = string.Concat(nameSymbols);
            if (string.IsNullOrEmpty(name))
            {
                errorSink.OnError(
                    childSpan.Start,
                    RazorResources.FormatTagHelpers_AttributesMustHaveAName(tagName),
                    childSpan.Length);

                return null;
            }

            // Have a name now. Able to determine correct isBoundNonStringAttribute value.
            var result = CreateTryParseResult(name, descriptors);

            // Remove first child i.e. foo="
            builder.Children.RemoveAt(0);

            // Grabbing last child to check if the attribute value is quoted.
            var endNode = block.Children.Last();
            if (!endNode.IsBlock)
            {
                var endSpan = (Span)endNode;

                // In some malformed cases e.g. <p bar="false', the last Span (false' in the ex.) may contain more
                // than a single HTML symbol. Do not ignore those other symbols.
                var symbolCount = endSpan.Symbols.Count();
                var endSymbol = symbolCount == 1 ? (HtmlSymbol)endSpan.Symbols.First() : null;

                // Checking to see if it's a quoted attribute, if so we should remove end quote
                if (endSymbol != null && IsQuote(endSymbol))
                {
                    builder.Children.RemoveAt(builder.Children.Count - 1);
                }
            }

            // We need to rebuild the chunk generators of the builder and its children (this is needed to
            // ensure we don't do special attribute chunk generation since this is a tag helper).
            block = RebuildChunkGenerators(builder.Build(), result.IsBoundAttribute);

            // If there's only 1 child at this point its value could be a simple markup span (treated differently than
            // block level elements for attributes).
            if (block.Children.Count() == 1)
            {
                var child = block.Children.First() as Span;
                if (child != null)
                {
                    // After pulling apart the block we just have a value span.
                    var spanBuilder = new SpanBuilder(child);

                    result.AttributeValueNode =
                        CreateMarkupAttribute(spanBuilder, result.IsBoundNonStringAttribute);

                    return result;
                }
            }

            var isFirstSpan = true;

            result.AttributeValueNode = ConvertToMarkupAttributeBlock(
                block,
                (parentBlock, span) =>
                {
                    // If the attribute was requested by a tag helper but the corresponding property was not a
                    // string, then treat its value as code. A non-string value can be any C# value so we need
                    // to ensure the SyntaxTreeNode reflects that.
                    if (result.IsBoundNonStringAttribute)
                    {
                        // For bound non-string attributes, we'll only allow a transition span to appear at the very
                        // beginning of the attribute expression. All later transitions would appear as code so that
                        // they are part of the generated output. E.g.
                        // key="@value" -> MyTagHelper.key = value
                        // key=" @value" -> MyTagHelper.key =  @value
                        // key="1 + @case" -> MyTagHelper.key = 1 + @case
                        // key="@int + @case" -> MyTagHelper.key = int + @case
                        // key="@(a + b) -> MyTagHelper.key = a + b
                        // key="4 + @(a + b)" -> MyTagHelper.key = 4 + @(a + b)
                        if (isFirstSpan && span.Kind == SpanKind.Transition)
                        {
                            // do nothing.
                        }
                        else
                        {
                            var spanBuilder = new SpanBuilder(span);

                            if (parentBlock.Type == BlockType.Expression &&
                                (spanBuilder.Kind == SpanKind.Transition ||
                                spanBuilder.Kind == SpanKind.MetaCode))
                            {
                                // Change to a MarkupChunkGenerator so that the '@' \ parenthesis is generated as part of the output.
                                spanBuilder.ChunkGenerator = new MarkupChunkGenerator();
                            }

                            spanBuilder.Kind = SpanKind.Code;
                            span = spanBuilder.Build();
                        }
                    }

                    isFirstSpan = false;

                    return span;
                });

            return result;
        }
예제 #39
0
        private static IEnumerable<TagHelperAttributeDescriptor> GetAttributeDescriptors(
            Type type,
            bool designTime,
            ErrorSink errorSink)
        {
            var attributeDescriptors = new List<TagHelperAttributeDescriptor>();

            // Keep indexer descriptors separate to avoid sorting the combined list later.
            var indexerDescriptors = new List<TagHelperAttributeDescriptor>();

            var accessibleProperties = type.GetRuntimeProperties().Where(IsAccessibleProperty);
            foreach (var property in accessibleProperties)
            {
                if (ShouldSkipDescriptorCreation(designTime, property))
                {
                    continue;
                }

                var attributeNameAttribute = property.GetCustomAttribute<HtmlAttributeNameAttribute>(inherit: false);
                var hasExplicitName =
                    attributeNameAttribute != null && !string.IsNullOrEmpty(attributeNameAttribute.Name);
                var attributeName = hasExplicitName ? attributeNameAttribute.Name : ToHtmlCase(property.Name);

                TagHelperAttributeDescriptor mainDescriptor = null;
                if (property.SetMethod?.IsPublic == true)
                {
                    mainDescriptor = ToAttributeDescriptor(property, attributeName, designTime);
                    if (!ValidateTagHelperAttributeDescriptor(mainDescriptor, type, errorSink))
                    {
                        // HtmlAttributeNameAttribute.Name is invalid. Ignore this property completely.
                        continue;
                    }
                }
                else if (hasExplicitName)
                {
                    // Specified HtmlAttributeNameAttribute.Name though property has no public setter.
                    errorSink.OnError(
                        SourceLocation.Zero,
                        Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty(
                            type.FullName,
                            property.Name,
                            typeof(HtmlAttributeNameAttribute).FullName,
                            nameof(HtmlAttributeNameAttribute.Name)));
                    continue;
                }

                bool isInvalid;
                var indexerDescriptor = ToIndexerAttributeDescriptor(
                    property,
                    attributeNameAttribute,
                    parentType: type,
                    errorSink: errorSink,
                    defaultPrefix: attributeName + "-",
                    designTime: designTime,
                    isInvalid: out isInvalid);
                if (indexerDescriptor != null &&
                    !ValidateTagHelperAttributeDescriptor(indexerDescriptor, type, errorSink))
                {
                    isInvalid = true;
                }

                if (isInvalid)
                {
                    // The property type or HtmlAttributeNameAttribute.DictionaryAttributePrefix (or perhaps the
                    // HTML-casing of the property name) is invalid. Ignore this property completely.
                    continue;
                }

                if (mainDescriptor != null)
                {
                    attributeDescriptors.Add(mainDescriptor);
                }

                if (indexerDescriptor != null)
                {
                    indexerDescriptors.Add(indexerDescriptor);
                }
            }

            attributeDescriptors.AddRange(indexerDescriptors);

            return attributeDescriptors;
        }
        private static TagHelperAttributeDescriptor ToIndexerAttributeDescriptor(
            PropertyInfo property,
            HtmlAttributeNameAttribute attributeNameAttribute,
            Type parentType,
            ErrorSink errorSink,
            string defaultPrefix,
            out bool isInvalid)
        {
            isInvalid = false;
            var dictionaryTypeArguments = ClosedGenericMatcher.ExtractGenericInterface(
                    property.PropertyType,
                    typeof(IDictionary<,>))
                ?.GenericTypeArguments;
            if (dictionaryTypeArguments?[0] != typeof(string))
            {
                if (attributeNameAttribute?.DictionaryAttributePrefix != null)
                {
                    // DictionaryAttributePrefix is not supported unless associated with an
                    // IDictionary<string, TValue> property.
                    isInvalid = true;
                    errorSink.OnError(
                        SourceLocation.Zero,
                        Resources.FormatTagHelperDescriptorFactory_InvalidAttributePrefix(
                            parentType.FullName,
                            property.Name,
                            nameof(HtmlAttributeNameAttribute),
                            nameof(HtmlAttributeNameAttribute.DictionaryAttributePrefix),
                            "IDictionary<string, TValue>"));
                }

                return null;
            }

            // Potential prefix case. Use default prefix (based on name)?
            var useDefault = attributeNameAttribute == null || !attributeNameAttribute.DictionaryAttributePrefixSet;

            var prefix = useDefault ? defaultPrefix : attributeNameAttribute.DictionaryAttributePrefix;
            if (prefix == null)
            {
                // DictionaryAttributePrefix explicitly set to null. Ignore.
                return null;
            }

            return new TagHelperAttributeDescriptor(
                name: prefix,
                propertyName: property.Name,
                typeName: dictionaryTypeArguments[1].FullName,
                isIndexer: true);
        }
예제 #41
0
 private void ValidateParentAllowsContent(Span child, ErrorSink errorSink)
 {
     if (HasAllowedChildren())
     {
         var content = child.Content;
         if (!string.IsNullOrWhiteSpace(content))
         {
             var trimmedStart = content.TrimStart();
             var whitespace = content.Substring(0, content.Length - trimmedStart.Length);
             var errorStart = SourceLocation.Advance(child.Start, whitespace);
             var length = trimmedStart.TrimEnd().Length;
             var allowedChildren = _currentTagHelperTracker.AllowedChildren;
             var allowedChildrenString = string.Join(", ", allowedChildren);
             errorSink.OnError(
                 errorStart,
                 RazorResources.FormatTagHelperParseTreeRewriter_CannotHaveNonTagContent(
                     _currentTagHelperTracker.TagName,
                     allowedChildrenString),
                 length);
         }
     }
 }
예제 #42
0
        private static TryParseResult TryParseBlock(
            string tagName,
            Block block,
            IEnumerable<TagHelperDescriptor> descriptors,
            ErrorSink errorSink)
        {
            // TODO: Accept more than just spans: https://github.com/aspnet/Razor/issues/96.
            // The first child will only ever NOT be a Span if a user is doing something like:
            // <input @checked />

            var childSpan = block.Children.First() as Span;

            if (childSpan == null || childSpan.Kind != SpanKind.Markup)
            {
                errorSink.OnError(block.Children.First().Start,
                                  RazorResources.FormatTagHelpers_CannotHaveCSharpInTagDeclaration(tagName));

                return null;
            }

            var builder = new BlockBuilder(block);

            // If there's only 1 child it means that it's plain text inside of the attribute.
            // i.e. <div class="plain text in attribute">
            if (builder.Children.Count == 1)
            {
                return TryParseSpan(childSpan, descriptors, errorSink);
            }

            var textSymbol = childSpan.Symbols.FirstHtmlSymbolAs(HtmlSymbolType.Text);
            var name = textSymbol != null ? textSymbol.Content : null;

            if (name == null)
            {
                errorSink.OnError(childSpan.Start, RazorResources.FormatTagHelpers_AttributesMustHaveAName(tagName));

                return null;
            }

            // Have a name now. Able to determine correct isBoundNonStringAttribute value.
            var result = CreateTryParseResult(name, descriptors);

            // Remove first child i.e. foo="
            builder.Children.RemoveAt(0);

            // Grabbing last child to check if the attribute value is quoted.
            var endNode = block.Children.Last();
            if (!endNode.IsBlock)
            {
                var endSpan = (Span)endNode;

                // In some malformed cases e.g. <p bar="false', the last Span (false' in the ex.) may contain more
                // than a single HTML symbol. Do not ignore those other symbols.
                var symbolCount = endSpan.Symbols.Count();
                var endSymbol = symbolCount == 1 ? (HtmlSymbol)endSpan.Symbols.First() : null;

                // Checking to see if it's a quoted attribute, if so we should remove end quote
                if (endSymbol != null && IsQuote(endSymbol))
                {
                    builder.Children.RemoveAt(builder.Children.Count - 1);
                }
            }

            // We need to rebuild the chunk generators of the builder and its children (this is needed to
            // ensure we don't do special attribute chunk generation since this is a tag helper).
            block = RebuildChunkGenerators(builder.Build());

            // If there's only 1 child at this point its value could be a simple markup span (treated differently than
            // block level elements for attributes).
            if (block.Children.Count() == 1)
            {
                var child = block.Children.First() as Span;
                if (child != null)
                {
                    // After pulling apart the block we just have a value span.
                    var spanBuilder = new SpanBuilder(child);

                    result.AttributeValueNode =
                        CreateMarkupAttribute(spanBuilder, result.IsBoundNonStringAttribute);

                    return result;
                }
            }

            result.AttributeValueNode = ConvertToMarkupAttributeBlock(block, result.IsBoundNonStringAttribute);

            return result;
        }
예제 #43
0
        private static TagHelperAttributeDescriptor ToIndexerAttributeDescriptor(
            PropertyInfo property,
            HtmlAttributeNameAttribute attributeNameAttribute,
            Type parentType,
            ErrorSink errorSink,
            string defaultPrefix,
            bool designTime,
            out bool isInvalid)
        {
            isInvalid = false;
            var hasPublicSetter = property.SetMethod?.IsPublic == true;
            var dictionaryTypeArguments = ClosedGenericMatcher.ExtractGenericInterface(
                    property.PropertyType,
                    typeof(IDictionary<,>))
                ?.GenericTypeArguments;
            if (dictionaryTypeArguments?[0] != typeof(string))
            {
                if (attributeNameAttribute?.DictionaryAttributePrefix != null)
                {
                    // DictionaryAttributePrefix is not supported unless associated with an
                    // IDictionary<string, TValue> property.
                    isInvalid = true;
                    errorSink.OnError(
                        SourceLocation.Zero,
                        Resources.FormatTagHelperDescriptorFactory_InvalidAttributePrefixNotNull(
                            parentType.FullName,
                            property.Name,
                            nameof(HtmlAttributeNameAttribute),
                            nameof(HtmlAttributeNameAttribute.DictionaryAttributePrefix),
                            "IDictionary<string, TValue>"));
                }
                else if (attributeNameAttribute != null && !hasPublicSetter)
                {
                    // Associated an HtmlAttributeNameAttribute with a non-dictionary property that lacks a public
                    // setter.
                    isInvalid = true;
                    errorSink.OnError(
                        SourceLocation.Zero,
                        Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameAttribute(
                            parentType.FullName,
                            property.Name,
                            nameof(HtmlAttributeNameAttribute),
                            "IDictionary<string, TValue>"));
                }

                return null;
            }
            else if (!hasPublicSetter &&
                attributeNameAttribute != null &&
                !attributeNameAttribute.DictionaryAttributePrefixSet)
            {
                // Must set DictionaryAttributePrefix when using HtmlAttributeNameAttribute with a dictionary property
                // that lacks a public setter.
                isInvalid = true;
                errorSink.OnError(
                    SourceLocation.Zero,
                    Resources.FormatTagHelperDescriptorFactory_InvalidAttributePrefixNull(
                        parentType.FullName,
                        property.Name,
                        nameof(HtmlAttributeNameAttribute),
                        nameof(HtmlAttributeNameAttribute.DictionaryAttributePrefix),
                        "IDictionary<string, TValue>"));

                return null;
            }

            // Potential prefix case. Use default prefix (based on name)?
            var useDefault = attributeNameAttribute == null || !attributeNameAttribute.DictionaryAttributePrefixSet;

            var prefix = useDefault ? defaultPrefix : attributeNameAttribute.DictionaryAttributePrefix;
            if (prefix == null)
            {
                // DictionaryAttributePrefix explicitly set to null. Ignore.
                return null;
            }

            return ToAttributeDescriptor(
                property,
                attributeName: prefix,
                typeName: dictionaryTypeArguments[1].FullName,
                isIndexer: true,
                designTime: designTime);
        }
        private static LookupInfo GetLookupInfo(
            TagHelperDirectiveDescriptor directiveDescriptor,
            ErrorSink errorSink)
        {
            var lookupText = directiveDescriptor.DirectiveText;
            var lookupStrings = lookupText?.Split(new[] { ',' });

            // Ensure that we have valid lookupStrings to work with. The valid format is "typeName, assemblyName"
            if (lookupStrings == null ||
                lookupStrings.Any(string.IsNullOrWhiteSpace) ||
                lookupStrings.Length != 2)
            {
                errorSink.OnError(
                    directiveDescriptor.Location,
                    Resources.FormatTagHelperDescriptorResolver_InvalidTagHelperLookupText(lookupText),
                    GetErrorLength(lookupText));

                return null;
            }

            var trimmedAssemblyName = lookupStrings[1].Trim();

            // + 1 is for the comma separator in the lookup text.
            var assemblyNameIndex = lookupStrings[0].Length + 1 + lookupStrings[1].IndexOf(trimmedAssemblyName);
            var assemblyNamePrefix = directiveDescriptor.DirectiveText.Substring(0, assemblyNameIndex);
            var assemblyNameLocation = SourceLocation.Advance(directiveDescriptor.Location, assemblyNamePrefix);

            return new LookupInfo
            {
                TypePattern = lookupStrings[0].Trim(),
                AssemblyName = trimmedAssemblyName,
                AssemblyNameLocation = assemblyNameLocation,
            };
        }
예제 #45
0
        private static IList<KeyValuePair<string, SyntaxTreeNode>> GetTagAttributes(
            string tagName,
            bool validStructure,
            Block tagBlock,
            IEnumerable<TagHelperDescriptor> descriptors,
            ErrorSink errorSink)
        {
            // Ignore all but one descriptor per type since this method uses the TagHelperDescriptors only to get the
            // contained TagHelperAttributeDescriptor's.
            descriptors = descriptors.Distinct(TypeBasedTagHelperDescriptorComparer.Default);

            var attributes = new List<KeyValuePair<string, SyntaxTreeNode>>();

            // We skip the first child "<tagname" and take everything up to the ending portion of the tag ">" or "/>".
            // The -2 accounts for both the start and end tags. If the tag does not have a valid structure then there's
            // no end tag to ignore.
            var symbolOffset = validStructure ? 2 : 1;
            var attributeChildren = tagBlock.Children.Skip(1).Take(tagBlock.Children.Count() - symbolOffset);

            foreach (var child in attributeChildren)
            {
                TryParseResult result;
                if (child.IsBlock)
                {
                    result = TryParseBlock(tagName, (Block)child, descriptors, errorSink);
                }
                else
                {
                    result = TryParseSpan((Span)child, descriptors, errorSink);
                }

                // Only want to track the attribute if we succeeded in parsing its corresponding Block/Span.
                if (result != null)
                {
                    SourceLocation? errorLocation = null;

                    // Check if it's a bound attribute that is minimized or if it's a bound non-string attribute that
                    // is null or whitespace.
                    if ((result.IsBoundAttribute && result.AttributeValueNode == null) ||
                        (result.IsBoundNonStringAttribute &&
                         IsNullOrWhitespaceAttributeValue(result.AttributeValueNode)))
                    {
                        errorLocation = GetAttributeNameStartLocation(child);

                        errorSink.OnError(
                            errorLocation.Value,
                            RazorResources.FormatRewriterError_EmptyTagHelperBoundAttribute(
                                result.AttributeName,
                                tagName,
                                GetPropertyType(result.AttributeName, descriptors)),
                            result.AttributeName.Length);
                    }

                    // Check if the attribute was a prefix match for a tag helper dictionary property but the
                    // dictionary key would be the empty string.
                    if (result.IsMissingDictionaryKey)
                    {
                        if (!errorLocation.HasValue)
                        {
                            errorLocation = GetAttributeNameStartLocation(child);
                        }

                        errorSink.OnError(
                            errorLocation.Value,
                            RazorResources.FormatTagHelperBlockRewriter_IndexerAttributeNameMustIncludeKey(
                                result.AttributeName,
                                tagName),
                            result.AttributeName.Length);
                    }

                    attributes.Add(
                        new KeyValuePair<string, SyntaxTreeNode>(result.AttributeName, result.AttributeValueNode));
                }
            }

            return attributes;
        }
예제 #46
0
        private static bool TryValidateName(
            string name,
            string whitespaceError,
            Func<char, string> characterErrorBuilder,
            ErrorSink errorSink)
        {
            var validName = true;

            if (string.IsNullOrWhiteSpace(name))
            {
                errorSink.OnError(SourceLocation.Zero, whitespaceError);

                validName = false;
            }
            else
            {
                foreach (var character in name)
                {
                    if (char.IsWhiteSpace(character) ||
                        InvalidNonWhitespaceNameCharacters.Contains(character))
                    {
                        var error = characterErrorBuilder(character);
                        errorSink.OnError(SourceLocation.Zero, error);

                        validName = false;
                    }
                }
            }

            return validName;
        }
예제 #47
0
        private static void OnAllowedChildrenTagError(
            TagHelperBlockTracker tracker,
            string tagName,
            Block tagBlock,
            ErrorSink errorSink)
        {
            var allowedChildrenString = string.Join(", ", tracker.AllowedChildren);
            var errorMessage = RazorResources.FormatTagHelperParseTreeRewriter_InvalidNestedTag(
                tagName,
                tracker.TagName,
                allowedChildrenString);
            var errorStart = GetTagDeclarationErrorStart(tagBlock);

            errorSink.OnError(errorStart, errorMessage, tagName.Length);
        }
        private static LookupInfo GetLookupInfo(TagHelperDirectiveDescriptor directiveDescriptor,
                                                ErrorSink errorSink)
        {
            var lookupText = directiveDescriptor.DirectiveText;
            var lookupStrings = lookupText?.Split(new[] { ',' });

            // Ensure that we have valid lookupStrings to work with. Valid formats are:
            // "assemblyName"
            // "typeName, assemblyName"
            if (lookupStrings == null ||
                lookupStrings.Any(string.IsNullOrWhiteSpace) ||
                lookupStrings.Length != 2)
            {
                errorSink.OnError(
                    directiveDescriptor.Location,
                    Resources.FormatTagHelperDescriptorResolver_InvalidTagHelperLookupText(lookupText));

                return null;
            }

            return new LookupInfo
            {
                TypePattern = lookupStrings[0].Trim(),
                AssemblyName = lookupStrings[1].Trim()
            };
        }
        private static bool ValidateName(
            string name,
            bool targetingAttributes,
            ErrorSink errorSink)
        {
            if (!targetingAttributes &&
                string.Equals(
                    name,
                    TagHelperDescriptorProvider.ElementCatchAllTarget,
                    StringComparison.OrdinalIgnoreCase))
            {
                // '*' as the entire name is OK in the TargetElement catch-all case.
                return true;
            }
            else if (targetingAttributes &&
                name.EndsWith(
                    TagHelperDescriptorProvider.RequiredAttributeWildcardSuffix,
                    StringComparison.OrdinalIgnoreCase))
            {
                // A single '*' at the end of a required attribute is valid; everywhere else is invalid. Strip it from
                // the end so we can validate the rest of the name.
                name = name.Substring(0, name.Length - 1);
            }

            var targetName = targetingAttributes ?
                Resources.TagHelperDescriptorFactory_Attribute :
                Resources.TagHelperDescriptorFactory_Tag;
            var validName = true;

            if (string.IsNullOrWhiteSpace(name))
            {
                errorSink.OnError(
                    SourceLocation.Zero,
                    Resources.FormatTargetElementAttribute_NameCannotBeNullOrWhitespace(targetName));

                validName = false;
            }
            else
            {
                foreach (var character in name)
                {
                    if (char.IsWhiteSpace(character) ||
                        InvalidNonWhitespaceNameCharacters.Contains(character))
                    {
                        errorSink.OnError(
                            SourceLocation.Zero,
                            Resources.FormatTargetElementAttribute_InvalidName(
                                targetName.ToLower(),
                                name,
                                character));

                        validName = false;
                    }
                }
            }

            return validName;
        }
        private static TagHelperAttributeDescriptor ToIndexerAttributeDescriptor(
            IPropertyInfo property,
            HtmlAttributeNameAttribute attributeNameAttribute,
            ITypeInfo parentType,
            ErrorSink errorSink,
            string defaultPrefix,
            bool designTime,
            out bool isInvalid)
        {
            isInvalid = false;
            var hasPublicSetter         = property.HasPublicSetter;
            var dictionaryTypeArguments = property.PropertyType.GetGenericDictionaryParameters();

            if (!StringTypeInfo.Equals(dictionaryTypeArguments?[0]))
            {
                if (attributeNameAttribute?.DictionaryAttributePrefix != null)
                {
                    // DictionaryAttributePrefix is not supported unless associated with an
                    // IDictionary<string, TValue> property.
                    isInvalid = true;
                    errorSink.OnError(
                        SourceLocation.Zero,
                        Resources.FormatTagHelperDescriptorFactory_InvalidAttributePrefixNotNull(
                            parentType.FullName,
                            property.Name,
                            nameof(HtmlAttributeNameAttribute),
                            nameof(HtmlAttributeNameAttribute.DictionaryAttributePrefix),
                            "IDictionary<string, TValue>"),
                        length: 0);
                }
                else if (attributeNameAttribute != null && !hasPublicSetter)
                {
                    // Associated an HtmlAttributeNameAttribute with a non-dictionary property that lacks a public
                    // setter.
                    isInvalid = true;
                    errorSink.OnError(
                        SourceLocation.Zero,
                        Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameAttribute(
                            parentType.FullName,
                            property.Name,
                            nameof(HtmlAttributeNameAttribute),
                            "IDictionary<string, TValue>"),
                        length: 0);
                }

                return(null);
            }
            else if (!hasPublicSetter &&
                     attributeNameAttribute != null &&
                     !attributeNameAttribute.DictionaryAttributePrefixSet)
            {
                // Must set DictionaryAttributePrefix when using HtmlAttributeNameAttribute with a dictionary property
                // that lacks a public setter.
                isInvalid = true;
                errorSink.OnError(
                    SourceLocation.Zero,
                    Resources.FormatTagHelperDescriptorFactory_InvalidAttributePrefixNull(
                        parentType.FullName,
                        property.Name,
                        nameof(HtmlAttributeNameAttribute),
                        nameof(HtmlAttributeNameAttribute.DictionaryAttributePrefix),
                        "IDictionary<string, TValue>"),
                    length: 0);

                return(null);
            }

            // Potential prefix case. Use default prefix (based on name)?
            var useDefault = attributeNameAttribute == null || !attributeNameAttribute.DictionaryAttributePrefixSet;

            var prefix = useDefault ? defaultPrefix : attributeNameAttribute.DictionaryAttributePrefix;

            if (prefix == null)
            {
                // DictionaryAttributePrefix explicitly set to null. Ignore.
                return(null);
            }

            return(ToAttributeDescriptor(
                       property,
                       attributeName: prefix,
                       typeName: dictionaryTypeArguments[1].FullName,
                       isIndexer: true,
                       isStringProperty: StringTypeInfo.Equals(dictionaryTypeArguments[1]),
                       designTime: designTime));
        }
예제 #51
0
        private TagHelperAttributeDescriptor ToIndexerAttributeDescriptor(
            IPropertyInfo property,
            HtmlAttributeNameAttribute attributeNameAttribute,
            ITypeInfo parentType,
            ErrorSink errorSink,
            string defaultPrefix,
            out bool isInvalid)
        {
            isInvalid = false;
            var hasPublicSetter = property.HasPublicSetter;
            var dictionaryTypeArguments = property.PropertyType.GetGenericDictionaryParameters();
            if (!StringTypeInfo.Equals(dictionaryTypeArguments?[0]))
            {
                if (attributeNameAttribute?.DictionaryAttributePrefix != null)
                {
                    // DictionaryAttributePrefix is not supported unless associated with an
                    // IDictionary<string, TValue> property.
                    isInvalid = true;
                    errorSink.OnError(
                        SourceLocation.Zero,
                        Resources.FormatTagHelperDescriptorFactory_InvalidAttributePrefixNotNull(
                            parentType.FullName,
                            property.Name,
                            nameof(HtmlAttributeNameAttribute),
                            nameof(HtmlAttributeNameAttribute.DictionaryAttributePrefix),
                            "IDictionary<string, TValue>"),
                        length: 0);
                }
                else if (attributeNameAttribute != null && !hasPublicSetter)
                {
                    // Associated an HtmlAttributeNameAttribute with a non-dictionary property that lacks a public
                    // setter.
                    isInvalid = true;
                    errorSink.OnError(
                        SourceLocation.Zero,
                        Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameAttribute(
                            parentType.FullName,
                            property.Name,
                            nameof(HtmlAttributeNameAttribute),
                            "IDictionary<string, TValue>"),
                        length: 0);
                }

                return null;
            }
            else if (!hasPublicSetter &&
                attributeNameAttribute != null &&
                !attributeNameAttribute.DictionaryAttributePrefixSet)
            {
                // Must set DictionaryAttributePrefix when using HtmlAttributeNameAttribute with a dictionary property
                // that lacks a public setter.
                isInvalid = true;
                errorSink.OnError(
                    SourceLocation.Zero,
                    Resources.FormatTagHelperDescriptorFactory_InvalidAttributePrefixNull(
                        parentType.FullName,
                        property.Name,
                        nameof(HtmlAttributeNameAttribute),
                        nameof(HtmlAttributeNameAttribute.DictionaryAttributePrefix),
                        "IDictionary<string, TValue>"),
                    length: 0);

                return null;
            }

            // Potential prefix case. Use default prefix (based on name)?
            var useDefault = attributeNameAttribute == null || !attributeNameAttribute.DictionaryAttributePrefixSet;

            var prefix = useDefault ? defaultPrefix : attributeNameAttribute.DictionaryAttributePrefix;
            if (prefix == null)
            {
                // DictionaryAttributePrefix explicitly set to null. Ignore.
                return null;
            }

            return ToAttributeDescriptor(
                property,
                attributeName: prefix,
                typeName: dictionaryTypeArguments[1].FullName,
                isIndexer: true,
                isStringProperty: StringTypeInfo.Equals(dictionaryTypeArguments[1]));
        }
예제 #52
0
        private static void ValidateDescriptors(
            IEnumerable<TagHelperDescriptor> descriptors,
            string tagName,
            Block tagBlock,
            ErrorSink errorSink)
        {
            // Ensure that all descriptors associated with this tag have appropriate TagStructures. Cannot have
            // multiple descriptors that expect different TagStructures (other than TagStructure.Unspecified).
            TagHelperDescriptor baseDescriptor = null;
            foreach (var descriptor in descriptors)
            {
                if (descriptor.TagStructure != TagStructure.Unspecified)
                {
                    // Can't have a set of TagHelpers that expect different structures.
                    if (baseDescriptor != null && baseDescriptor.TagStructure != descriptor.TagStructure)
                    {
                        errorSink.OnError(
                            tagBlock.Start,
                            RazorResources.FormatTagHelperParseTreeRewriter_InconsistentTagStructure(
                                baseDescriptor.TypeName,
                                descriptor.TypeName,
                                tagName,
                                nameof(TagHelperDescriptor.TagStructure)),
                            tagBlock.Length);
                    }

                    baseDescriptor = descriptor;
                }
            }
        }
예제 #53
0
        // Internal for testing.
        internal static bool ValidateTagHelperAttributeDescriptor(
            TagHelperAttributeDescriptor attributeDescriptor,
            Type parentType,
            ErrorSink errorSink)
        {
            string nameOrPrefix;
            if (attributeDescriptor.IsIndexer)
            {
                nameOrPrefix = Resources.TagHelperDescriptorFactory_Prefix;
            }
            else if (string.IsNullOrEmpty(attributeDescriptor.Name))
            {
                errorSink.OnError(
                    SourceLocation.Zero,
                    Resources.FormatTagHelperDescriptorFactory_InvalidAttributeNameNullOrEmpty(
                        parentType.FullName,
                        attributeDescriptor.PropertyName));

                return false;
            }
            else
            {
                nameOrPrefix = Resources.TagHelperDescriptorFactory_Name;
            }

            return ValidateTagHelperAttributeNameOrPrefix(
                attributeDescriptor.Name,
                parentType,
                attributeDescriptor.PropertyName,
                errorSink,
                nameOrPrefix);
        }