Add Visual Studio specific RC1 binaries.
- This is needed for Visual Studio RC1 backwards compatibility.
This commit is contained in:
parent
4212b7e713
commit
197d6a579f
|
|
@ -5,7 +5,9 @@
|
|||
],
|
||||
"packages": {
|
||||
"Microsoft.AspNetCore.Razor": { },
|
||||
"Microsoft.AspNetCore.Razor.Runtime": { }
|
||||
"Microsoft.AspNet.Razor.VSRC1": { },
|
||||
"Microsoft.AspNetCore.Razor.Runtime": { },
|
||||
"Microsoft.AspNet.Razor.Runtime.VSRC1": { }
|
||||
}
|
||||
},
|
||||
"adx-nonshipping": { // Packages written by the ADX team but that don't ship on NuGet.org (thus, no need to scan anything in them)
|
||||
|
|
|
|||
17
Razor.sln
17
Razor.sln
|
|
@ -1,7 +1,6 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 14
|
||||
VisualStudioVersion = 14.0.24720.0
|
||||
VisualStudioVersion = 14.0.25123.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{3C0D6505-79B3-49D0-B4C3-176F0F1836ED}"
|
||||
EndProject
|
||||
|
|
@ -17,6 +16,10 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Razor.
|
|||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Razor.Test.Sources", "src\Microsoft.AspNetCore.Razor.Test.Sources\Microsoft.AspNetCore.Razor.Test.Sources.xproj", "{E3A2A305-634D-4BA6-95DB-AA06D6C442B0}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Razor.Runtime.VSRC1", "src\Microsoft.AspNet.Razor.Runtime.VSRC1\Microsoft.AspNet.Razor.Runtime.VSRC1.xproj", "{5E8EC8BB-69B9-43D4-A095-7A06F65838E2}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Razor.VSRC1", "src\Microsoft.AspNet.Razor.VSRC1\Microsoft.AspNet.Razor.VSRC1.xproj", "{AB5ABC37-201B-41FF-9FAF-E948B0D33F5A}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -43,6 +46,14 @@ Global
|
|||
{E3A2A305-634D-4BA6-95DB-AA06D6C442B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E3A2A305-634D-4BA6-95DB-AA06D6C442B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E3A2A305-634D-4BA6-95DB-AA06D6C442B0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5E8EC8BB-69B9-43D4-A095-7A06F65838E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5E8EC8BB-69B9-43D4-A095-7A06F65838E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5E8EC8BB-69B9-43D4-A095-7A06F65838E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5E8EC8BB-69B9-43D4-A095-7A06F65838E2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{AB5ABC37-201B-41FF-9FAF-E948B0D33F5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{AB5ABC37-201B-41FF-9FAF-E948B0D33F5A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{AB5ABC37-201B-41FF-9FAF-E948B0D33F5A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{AB5ABC37-201B-41FF-9FAF-E948B0D33F5A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -53,5 +64,7 @@ Global
|
|||
{D0196096-1B01-4133-AACE-1A10A0F7247C} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
|
||||
{0535998A-E32C-4D1A-80D1-0B15A513C471} = {92463391-81BE-462B-AC3C-78C6C760741F}
|
||||
{E3A2A305-634D-4BA6-95DB-AA06D6C442B0} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
|
||||
{5E8EC8BB-69B9-43D4-A095-7A06F65838E2} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
|
||||
{AB5ABC37-201B-41FF-9FAF-E948B0D33F5A} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
|
|
@ -0,0 +1,125 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using Microsoft.AspNet.Html.Abstractions;
|
||||
using Microsoft.Extensions.WebEncoders;
|
||||
|
||||
namespace Microsoft.Extensions.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumerable object collection which knows how to write itself.
|
||||
/// </summary>
|
||||
[DebuggerDisplay("{DebuggerToString()}")]
|
||||
internal class BufferedHtmlContent : IHtmlContentBuilder
|
||||
{
|
||||
// This is not List<IHtmlContent> because that would lead to wrapping all strings to IHtmlContent
|
||||
// which is not space performant.
|
||||
// internal for testing.
|
||||
internal List<object> Entries { get; } = new List<object>();
|
||||
|
||||
/// <summary>
|
||||
/// Appends the <see cref="string"/> to the collection.
|
||||
/// </summary>
|
||||
/// <param name="unencoded">The <c>string</c> to be appended.</param>
|
||||
/// <returns>A reference to this instance after the Append operation has completed.</returns>
|
||||
public IHtmlContentBuilder Append(string unencoded)
|
||||
{
|
||||
Entries.Add(unencoded);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends a <see cref="IHtmlContent"/> to the collection.
|
||||
/// </summary>
|
||||
/// <param name="htmlContent">The <see cref="IHtmlContent"/> to be appended.</param>
|
||||
/// <returns>A reference to this instance after the Append operation has completed.</returns>
|
||||
public IHtmlContentBuilder Append(IHtmlContent htmlContent)
|
||||
{
|
||||
Entries.Add(htmlContent);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends the HTML encoded <see cref="string"/> to the collection.
|
||||
/// </summary>
|
||||
/// <param name="encoded">The HTML encoded <c>string</c> to be appended.</param>
|
||||
/// <returns>A reference to this instance after the Append operation has completed.</returns>
|
||||
public IHtmlContentBuilder AppendHtml(string encoded)
|
||||
{
|
||||
Entries.Add(new HtmlEncodedString(encoded));
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Removes all the entries from the collection.
|
||||
/// </summary>
|
||||
/// <returns>A reference to this instance after the Clear operation has completed.</returns>
|
||||
public IHtmlContentBuilder Clear()
|
||||
{
|
||||
Entries.Clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteTo(TextWriter writer, IHtmlEncoder encoder)
|
||||
{
|
||||
if (writer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(writer));
|
||||
}
|
||||
|
||||
if (encoder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(encoder));
|
||||
}
|
||||
|
||||
foreach (var entry in Entries)
|
||||
{
|
||||
if (entry == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var entryAsString = entry as string;
|
||||
if (entryAsString != null)
|
||||
{
|
||||
encoder.HtmlEncode(entryAsString, writer);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only string, IHtmlContent values can be added to the buffer.
|
||||
((IHtmlContent)entry).WriteTo(writer, encoder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string DebuggerToString()
|
||||
{
|
||||
using (var writer = new StringWriter())
|
||||
{
|
||||
WriteTo(writer, HtmlEncoder.Default);
|
||||
return writer.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private class HtmlEncodedString : IHtmlContent
|
||||
{
|
||||
public static readonly IHtmlContent NewLine = new HtmlEncodedString(Environment.NewLine);
|
||||
|
||||
private readonly string _value;
|
||||
|
||||
public HtmlEncodedString(string value)
|
||||
{
|
||||
_value = value;
|
||||
}
|
||||
|
||||
public void WriteTo(TextWriter writer, IHtmlEncoder encoder)
|
||||
{
|
||||
writer.Write(_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.Extensions.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper related to generic interface definitions and implementing classes.
|
||||
/// </summary>
|
||||
internal static class ClosedGenericMatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// Determine whether <paramref name="queryType"/> is or implements a closed generic <see cref="Type"/>
|
||||
/// created from <paramref name="interfaceType"/>.
|
||||
/// </summary>
|
||||
/// <param name="queryType">The <see cref="Type"/> of interest.</param>
|
||||
/// <param name="interfaceType">The open generic <see cref="Type"/> to match. Usually an interface.</param>
|
||||
/// <returns>
|
||||
/// The closed generic <see cref="Type"/> created from <paramref name="interfaceType"/> that
|
||||
/// <paramref name="queryType"/> is or implements. <c>null</c> if the two <see cref="Type"/>s have no such
|
||||
/// relationship.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// This method will return <paramref name="queryType"/> if <paramref name="interfaceType"/> is
|
||||
/// <c>typeof(KeyValuePair{,})</c>, and <paramref name="queryType"/> is
|
||||
/// <c>typeof(KeyValuePair{string, object})</c>.
|
||||
/// </remarks>
|
||||
public static Type ExtractGenericInterface(Type queryType, Type interfaceType)
|
||||
{
|
||||
if (queryType == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(queryType));
|
||||
}
|
||||
|
||||
if (interfaceType == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(interfaceType));
|
||||
}
|
||||
|
||||
Func<Type, bool> matchesInterface =
|
||||
type => type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == interfaceType;
|
||||
if (matchesInterface(queryType))
|
||||
{
|
||||
// Checked type matches (i.e. is a closed generic type created from) the open generic type.
|
||||
return queryType;
|
||||
}
|
||||
|
||||
// Otherwise check all interfaces the type implements for a match.
|
||||
return queryType.GetTypeInfo().ImplementedInterfaces.FirstOrDefault(matchesInterface);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Extensions.Internal
|
||||
{
|
||||
internal class CopyOnWriteDictionary<TKey, TValue> : IDictionary<TKey, TValue>
|
||||
{
|
||||
private readonly IDictionary<TKey, TValue> _sourceDictionary;
|
||||
private readonly IEqualityComparer<TKey> _comparer;
|
||||
private IDictionary<TKey, TValue> _innerDictionary;
|
||||
|
||||
public CopyOnWriteDictionary(
|
||||
IDictionary<TKey, TValue> sourceDictionary,
|
||||
IEqualityComparer<TKey> comparer)
|
||||
{
|
||||
if (sourceDictionary == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(sourceDictionary));
|
||||
}
|
||||
|
||||
if (comparer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(comparer));
|
||||
}
|
||||
|
||||
_sourceDictionary = sourceDictionary;
|
||||
_comparer = comparer;
|
||||
}
|
||||
|
||||
private IDictionary<TKey, TValue> ReadDictionary
|
||||
{
|
||||
get
|
||||
{
|
||||
return _innerDictionary ?? _sourceDictionary;
|
||||
}
|
||||
}
|
||||
|
||||
private IDictionary<TKey, TValue> WriteDictionary
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_innerDictionary == null)
|
||||
{
|
||||
_innerDictionary = new Dictionary<TKey, TValue>(_sourceDictionary,
|
||||
_comparer);
|
||||
}
|
||||
|
||||
return _innerDictionary;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual ICollection<TKey> Keys
|
||||
{
|
||||
get
|
||||
{
|
||||
return ReadDictionary.Keys;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual ICollection<TValue> Values
|
||||
{
|
||||
get
|
||||
{
|
||||
return ReadDictionary.Values;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
return ReadDictionary.Count;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool IsReadOnly
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual TValue this[TKey key]
|
||||
{
|
||||
get
|
||||
{
|
||||
return ReadDictionary[key];
|
||||
}
|
||||
set
|
||||
{
|
||||
WriteDictionary[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool ContainsKey(TKey key)
|
||||
{
|
||||
return ReadDictionary.ContainsKey(key);
|
||||
}
|
||||
|
||||
public virtual void Add(TKey key, TValue value)
|
||||
{
|
||||
WriteDictionary.Add(key, value);
|
||||
}
|
||||
|
||||
public virtual bool Remove(TKey key)
|
||||
{
|
||||
return WriteDictionary.Remove(key);
|
||||
}
|
||||
|
||||
public virtual bool TryGetValue(TKey key, out TValue value)
|
||||
{
|
||||
return ReadDictionary.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
public virtual void Add(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
WriteDictionary.Add(item);
|
||||
}
|
||||
|
||||
public virtual void Clear()
|
||||
{
|
||||
WriteDictionary.Clear();
|
||||
}
|
||||
|
||||
public virtual bool Contains(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
return ReadDictionary.Contains(item);
|
||||
}
|
||||
|
||||
public virtual void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
|
||||
{
|
||||
ReadDictionary.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public bool Remove(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
return WriteDictionary.Remove(item);
|
||||
}
|
||||
|
||||
public virtual IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
|
||||
{
|
||||
return ReadDictionary.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Extensions.Internal
|
||||
{
|
||||
internal struct CopyOnWriteDictionaryHolder<TKey, TValue>
|
||||
{
|
||||
private readonly Dictionary<TKey, TValue> _source;
|
||||
private Dictionary<TKey, TValue> _copy;
|
||||
|
||||
public CopyOnWriteDictionaryHolder(Dictionary<TKey, TValue> source)
|
||||
{
|
||||
if (source == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
}
|
||||
|
||||
_source = source;
|
||||
_copy = null;
|
||||
}
|
||||
|
||||
public CopyOnWriteDictionaryHolder(CopyOnWriteDictionaryHolder<TKey, TValue> source)
|
||||
{
|
||||
_source = source._copy ?? source._source;
|
||||
_copy = null;
|
||||
}
|
||||
|
||||
public bool HasBeenCopied => _copy != null;
|
||||
|
||||
public Dictionary<TKey, TValue> ReadDictionary
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_copy != null)
|
||||
{
|
||||
return _copy;
|
||||
}
|
||||
else if (_source != null)
|
||||
{
|
||||
return _source;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default-Constructor case
|
||||
_copy = new Dictionary<TKey, TValue>();
|
||||
return _copy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<TKey, TValue> WriteDictionary
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_copy == null && _source == null)
|
||||
{
|
||||
// Default-Constructor case
|
||||
_copy = new Dictionary<TKey, TValue>();
|
||||
}
|
||||
else if (_copy == null)
|
||||
{
|
||||
_copy = new Dictionary<TKey, TValue>(_source, _source.Comparer);
|
||||
}
|
||||
|
||||
return _copy;
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<TKey, TValue>.KeyCollection Keys
|
||||
{
|
||||
get
|
||||
{
|
||||
return ReadDictionary.Keys;
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<TKey, TValue>.ValueCollection Values
|
||||
{
|
||||
get
|
||||
{
|
||||
return ReadDictionary.Values;
|
||||
}
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
return ReadDictionary.Count;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsReadOnly
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public TValue this[TKey key]
|
||||
{
|
||||
get
|
||||
{
|
||||
return ReadDictionary[key];
|
||||
}
|
||||
set
|
||||
{
|
||||
WriteDictionary[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool ContainsKey(TKey key)
|
||||
{
|
||||
return ReadDictionary.ContainsKey(key);
|
||||
}
|
||||
|
||||
public void Add(TKey key, TValue value)
|
||||
{
|
||||
WriteDictionary.Add(key, value);
|
||||
}
|
||||
|
||||
public bool Remove(TKey key)
|
||||
{
|
||||
return WriteDictionary.Remove(key);
|
||||
}
|
||||
|
||||
public bool TryGetValue(TKey key, out TValue value)
|
||||
{
|
||||
return ReadDictionary.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
((ICollection<KeyValuePair<TKey, TValue>>)WriteDictionary).Add(item);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
WriteDictionary.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
return ((ICollection<KeyValuePair<TKey, TValue>>)ReadDictionary).Contains(item);
|
||||
}
|
||||
|
||||
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
|
||||
{
|
||||
((ICollection<KeyValuePair<TKey, TValue>>)ReadDictionary).CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public bool Remove(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
return ((ICollection<KeyValuePair<TKey, TValue>>)WriteDictionary).Remove(item);
|
||||
}
|
||||
|
||||
public Dictionary<TKey, TValue>.Enumerator GetEnumerator()
|
||||
{
|
||||
return ReadDictionary.GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>5e8ec8bb-69b9-43d4-a095-7a06f65838e2</ProjectGuid>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Reflection;
|
||||
using System.Resources;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: AssemblyMetadata("Serviceable", "True")]
|
||||
[assembly: NeutralResourcesLanguage("en-us")]
|
||||
|
|
@ -0,0 +1,478 @@
|
|||
// <auto-generated />
|
||||
namespace Microsoft.AspNet.Razor.Runtime
|
||||
{
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Resources;
|
||||
|
||||
internal static class Resources
|
||||
{
|
||||
private static readonly ResourceManager _resourceManager
|
||||
= new ResourceManager("Microsoft.AspNet.Razor.Runtime.Resources", typeof(Resources).GetTypeInfo().Assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Invalid tag helper directive look up text '{0}'. The correct look up text format is: "typeName, assemblyName".
|
||||
/// </summary>
|
||||
internal static string TagHelperDescriptorResolver_InvalidTagHelperLookupText
|
||||
{
|
||||
get { return GetString("TagHelperDescriptorResolver_InvalidTagHelperLookupText"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid tag helper directive look up text '{0}'. The correct look up text format is: "typeName, assemblyName".
|
||||
/// </summary>
|
||||
internal static string FormatTagHelperDescriptorResolver_InvalidTagHelperLookupText(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorResolver_InvalidTagHelperLookupText"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cannot resolve TagHelper containing assembly '{0}'. Error: {1}
|
||||
/// </summary>
|
||||
internal static string TagHelperTypeResolver_CannotResolveTagHelperAssembly
|
||||
{
|
||||
get { return GetString("TagHelperTypeResolver_CannotResolveTagHelperAssembly"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cannot resolve TagHelper containing assembly '{0}'. Error: {1}
|
||||
/// </summary>
|
||||
internal static string FormatTagHelperTypeResolver_CannotResolveTagHelperAssembly(object p0, object p1)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperTypeResolver_CannotResolveTagHelperAssembly"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tag helper directive assembly name cannot be null or empty.
|
||||
/// </summary>
|
||||
internal static string TagHelperTypeResolver_TagHelperAssemblyNameCannotBeEmptyOrNull
|
||||
{
|
||||
get { return GetString("TagHelperTypeResolver_TagHelperAssemblyNameCannotBeEmptyOrNull"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tag helper directive assembly name cannot be null or empty.
|
||||
/// </summary>
|
||||
internal static string FormatTagHelperTypeResolver_TagHelperAssemblyNameCannotBeEmptyOrNull()
|
||||
{
|
||||
return GetString("TagHelperTypeResolver_TagHelperAssemblyNameCannotBeEmptyOrNull");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Must call '{2}.{1}' before calling '{2}.{0}'.
|
||||
/// </summary>
|
||||
internal static string ScopeManager_EndCannotBeCalledWithoutACallToBegin
|
||||
{
|
||||
get { return GetString("ScopeManager_EndCannotBeCalledWithoutACallToBegin"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Must call '{2}.{1}' before calling '{2}.{0}'.
|
||||
/// </summary>
|
||||
internal static string FormatScopeManager_EndCannotBeCalledWithoutACallToBegin(object p0, object p1, object p2)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("ScopeManager_EndCannotBeCalledWithoutACallToBegin"), p0, p1, p2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// {0} name cannot be null or whitespace.
|
||||
/// </summary>
|
||||
internal static string HtmlTargetElementAttribute_NameCannotBeNullOrWhitespace
|
||||
{
|
||||
get { return GetString("HtmlTargetElementAttribute_NameCannotBeNullOrWhitespace"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// {0} name cannot be null or whitespace.
|
||||
/// </summary>
|
||||
internal static string FormatHtmlTargetElementAttribute_NameCannotBeNullOrWhitespace(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("HtmlTargetElementAttribute_NameCannotBeNullOrWhitespace"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The value cannot be null or empty.
|
||||
/// </summary>
|
||||
internal static string ArgumentCannotBeNullOrEmpty
|
||||
{
|
||||
get { return GetString("ArgumentCannotBeNullOrEmpty"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The value cannot be null or empty.
|
||||
/// </summary>
|
||||
internal static string FormatArgumentCannotBeNullOrEmpty()
|
||||
{
|
||||
return GetString("ArgumentCannotBeNullOrEmpty");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encountered an unexpected error when attempting to resolve tag helper directive '{0}' with value '{1}'. Error: {2}
|
||||
/// </summary>
|
||||
internal static string TagHelperDescriptorResolver_EncounteredUnexpectedError
|
||||
{
|
||||
get { return GetString("TagHelperDescriptorResolver_EncounteredUnexpectedError"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encountered an unexpected error when attempting to resolve tag helper directive '{0}' with value '{1}'. Error: {2}
|
||||
/// </summary>
|
||||
internal static string FormatTagHelperDescriptorResolver_EncounteredUnexpectedError(object p0, object p1, object p2)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorResolver_EncounteredUnexpectedError"), p0, p1, p2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tag helpers cannot target {0} name '{1}' because it contains a '{2}' character.
|
||||
/// </summary>
|
||||
internal static string HtmlTargetElementAttribute_InvalidName
|
||||
{
|
||||
get { return GetString("HtmlTargetElementAttribute_InvalidName"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tag helpers cannot target {0} name '{1}' because it contains a '{2}' character.
|
||||
/// </summary>
|
||||
internal static string FormatHtmlTargetElementAttribute_InvalidName(object p0, object p1, object p2)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("HtmlTargetElementAttribute_InvalidName"), p0, p1, p2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid tag helper directive '{0}'. Cannot have multiple '{0}' directives on a page.
|
||||
/// </summary>
|
||||
internal static string TagHelperDescriptorResolver_InvalidTagHelperDirective
|
||||
{
|
||||
get { return GetString("TagHelperDescriptorResolver_InvalidTagHelperDirective"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid tag helper directive '{0}'. Cannot have multiple '{0}' directives on a page.
|
||||
/// </summary>
|
||||
internal static string FormatTagHelperDescriptorResolver_InvalidTagHelperDirective(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorResolver_InvalidTagHelperDirective"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid tag helper directive '{0}' value. '{1}' is not allowed in prefix '{2}'.
|
||||
/// </summary>
|
||||
internal static string TagHelperDescriptorResolver_InvalidTagHelperPrefixValue
|
||||
{
|
||||
get { return GetString("TagHelperDescriptorResolver_InvalidTagHelperPrefixValue"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid tag helper directive '{0}' value. '{1}' is not allowed in prefix '{2}'.
|
||||
/// </summary>
|
||||
internal static string FormatTagHelperDescriptorResolver_InvalidTagHelperPrefixValue(object p0, object p1, object p2)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorResolver_InvalidTagHelperPrefixValue"), p0, p1, p2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute
|
||||
/// </summary>
|
||||
internal static string TagHelperDescriptorFactory_Attribute
|
||||
{
|
||||
get { return GetString("TagHelperDescriptorFactory_Attribute"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute
|
||||
/// </summary>
|
||||
internal static string FormatTagHelperDescriptorFactory_Attribute()
|
||||
{
|
||||
return GetString("TagHelperDescriptorFactory_Attribute");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// name
|
||||
/// </summary>
|
||||
internal static string TagHelperDescriptorFactory_Name
|
||||
{
|
||||
get { return GetString("TagHelperDescriptorFactory_Name"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// name
|
||||
/// </summary>
|
||||
internal static string FormatTagHelperDescriptorFactory_Name()
|
||||
{
|
||||
return GetString("TagHelperDescriptorFactory_Name");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// prefix
|
||||
/// </summary>
|
||||
internal static string TagHelperDescriptorFactory_Prefix
|
||||
{
|
||||
get { return GetString("TagHelperDescriptorFactory_Prefix"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// prefix
|
||||
/// </summary>
|
||||
internal static string FormatTagHelperDescriptorFactory_Prefix()
|
||||
{
|
||||
return GetString("TagHelperDescriptorFactory_Prefix");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tag
|
||||
/// </summary>
|
||||
internal static string TagHelperDescriptorFactory_Tag
|
||||
{
|
||||
get { return GetString("TagHelperDescriptorFactory_Tag"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tag
|
||||
/// </summary>
|
||||
internal static string FormatTagHelperDescriptorFactory_Tag()
|
||||
{
|
||||
return GetString("TagHelperDescriptorFactory_Tag");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid tag helper bound property '{0}.{1}'. An '{2}' must not be associated with a property with no public setter unless its type implements '{3}'.
|
||||
/// </summary>
|
||||
internal static string TagHelperDescriptorFactory_InvalidAttributeNameAttribute
|
||||
{
|
||||
get { return GetString("TagHelperDescriptorFactory_InvalidAttributeNameAttribute"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid tag helper bound property '{0}.{1}'. An '{2}' must not be associated with a property with no public setter unless its type implements '{3}'.
|
||||
/// </summary>
|
||||
internal static string FormatTagHelperDescriptorFactory_InvalidAttributeNameAttribute(object p0, object p1, object p2, object p3)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributeNameAttribute"), p0, p1, p2, p3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with {2} '{3}' because {2} contains a '{4}' character.
|
||||
/// </summary>
|
||||
internal static string TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixCharacter
|
||||
{
|
||||
get { return GetString("TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixCharacter"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with {2} '{3}' because {2} contains a '{4}' character.
|
||||
/// </summary>
|
||||
internal static string FormatTagHelperDescriptorFactory_InvalidAttributeNameOrPrefixCharacter(object p0, object p1, object p2, object p3, object p4)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixCharacter"), p0, p1, p2, p3, p4);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with {2} '{3}' because {2} starts with '{4}'.
|
||||
/// </summary>
|
||||
internal static string TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixStart
|
||||
{
|
||||
get { return GetString("TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixStart"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with {2} '{3}' because {2} starts with '{4}'.
|
||||
/// </summary>
|
||||
internal static string FormatTagHelperDescriptorFactory_InvalidAttributeNameOrPrefixStart(object p0, object p1, object p2, object p3, object p4)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixStart"), p0, p1, p2, p3, p4);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a whitespace {2}.
|
||||
/// </summary>
|
||||
internal static string TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixWhitespace
|
||||
{
|
||||
get { return GetString("TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixWhitespace"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a whitespace {2}.
|
||||
/// </summary>
|
||||
internal static string FormatTagHelperDescriptorFactory_InvalidAttributeNameOrPrefixWhitespace(object p0, object p1, object p2)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixWhitespace"), p0, p1, p2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a null or empty name.
|
||||
/// </summary>
|
||||
internal static string TagHelperDescriptorFactory_InvalidAttributeNameNullOrEmpty
|
||||
{
|
||||
get { return GetString("TagHelperDescriptorFactory_InvalidAttributeNameNullOrEmpty"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a null or empty name.
|
||||
/// </summary>
|
||||
internal static string FormatTagHelperDescriptorFactory_InvalidAttributeNameNullOrEmpty(object p0, object p1)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributeNameNullOrEmpty"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null or empty if property has no public setter.
|
||||
/// </summary>
|
||||
internal static string TagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty
|
||||
{
|
||||
get { return GetString("TagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null or empty if property has no public setter.
|
||||
/// </summary>
|
||||
internal static string FormatTagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty(object p0, object p1, object p2, object p3)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty"), p0, p1, p2, p3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must not be null if property has no public setter and its type implements '{4}'.
|
||||
/// </summary>
|
||||
internal static string TagHelperDescriptorFactory_InvalidAttributePrefixNull
|
||||
{
|
||||
get { return GetString("TagHelperDescriptorFactory_InvalidAttributePrefixNull"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must not be null if property has no public setter and its type implements '{4}'.
|
||||
/// </summary>
|
||||
internal static string FormatTagHelperDescriptorFactory_InvalidAttributePrefixNull(object p0, object p1, object p2, object p3, object p4)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributePrefixNull"), p0, p1, p2, p3, p4);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null unless property type implements '{4}'.
|
||||
/// </summary>
|
||||
internal static string TagHelperDescriptorFactory_InvalidAttributePrefixNotNull
|
||||
{
|
||||
get { return GetString("TagHelperDescriptorFactory_InvalidAttributePrefixNotNull"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null unless property type implements '{4}'.
|
||||
/// </summary>
|
||||
internal static string FormatTagHelperDescriptorFactory_InvalidAttributePrefixNotNull(object p0, object p1, object p2, object p3, object p4)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidAttributePrefixNotNull"), p0, p1, p2, p3, p4);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cannot add a '{0}' with a null '{1}'.
|
||||
/// </summary>
|
||||
internal static string TagHelperAttributeList_CannotAddWithNullName
|
||||
{
|
||||
get { return GetString("TagHelperAttributeList_CannotAddWithNullName"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cannot add a '{0}' with a null '{1}'.
|
||||
/// </summary>
|
||||
internal static string FormatTagHelperAttributeList_CannotAddWithNullName(object p0, object p1)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperAttributeList_CannotAddWithNullName"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cannot add a {0} with inconsistent names. The {1} property '{2}' must match the location '{3}'.
|
||||
/// </summary>
|
||||
internal static string TagHelperAttributeList_CannotAddAttribute
|
||||
{
|
||||
get { return GetString("TagHelperAttributeList_CannotAddAttribute"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cannot add a {0} with inconsistent names. The {1} property '{2}' must match the location '{3}'.
|
||||
/// </summary>
|
||||
internal static string FormatTagHelperAttributeList_CannotAddAttribute(object p0, object p1, object p2, object p3)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperAttributeList_CannotAddAttribute"), p0, p1, p2, p3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid '{0}' tag name '{1}' for tag helper '{2}'. Tag helpers cannot restrict child elements that contain a '{3}' character.
|
||||
/// </summary>
|
||||
internal static string TagHelperDescriptorFactory_InvalidRestrictChildrenAttributeName
|
||||
{
|
||||
get { return GetString("TagHelperDescriptorFactory_InvalidRestrictChildrenAttributeName"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid '{0}' tag name '{1}' for tag helper '{2}'. Tag helpers cannot restrict child elements that contain a '{3}' character.
|
||||
/// </summary>
|
||||
internal static string FormatTagHelperDescriptorFactory_InvalidRestrictChildrenAttributeName(object p0, object p1, object p2, object p3)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidRestrictChildrenAttributeName"), p0, p1, p2, p3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid '{0}' tag name for tag helper '{1}'. Name cannot be null or whitespace.
|
||||
/// </summary>
|
||||
internal static string TagHelperDescriptorFactory_InvalidRestrictChildrenAttributeNameNullWhitespace
|
||||
{
|
||||
get { return GetString("TagHelperDescriptorFactory_InvalidRestrictChildrenAttributeNameNullWhitespace"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid '{0}' tag name for tag helper '{1}'. Name cannot be null or whitespace.
|
||||
/// </summary>
|
||||
internal static string FormatTagHelperDescriptorFactory_InvalidRestrictChildrenAttributeNameNullWhitespace(object p0, object p1)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidRestrictChildrenAttributeNameNullWhitespace"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parent Tag
|
||||
/// </summary>
|
||||
internal static string TagHelperDescriptorFactory_ParentTag
|
||||
{
|
||||
get { return GetString("TagHelperDescriptorFactory_ParentTag"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parent Tag
|
||||
/// </summary>
|
||||
internal static string FormatTagHelperDescriptorFactory_ParentTag()
|
||||
{
|
||||
return GetString("TagHelperDescriptorFactory_ParentTag");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Argument must be an instance of '{0}'.
|
||||
/// </summary>
|
||||
internal static string ArgumentMustBeAnInstanceOf
|
||||
{
|
||||
get { return GetString("ArgumentMustBeAnInstanceOf"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Argument must be an instance of '{0}'.
|
||||
/// </summary>
|
||||
internal static string FormatArgumentMustBeAnInstanceOf(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("ArgumentMustBeAnInstanceOf"), p0);
|
||||
}
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
||||
System.Diagnostics.Debug.Assert(value != null);
|
||||
|
||||
if (formatterNames != null)
|
||||
{
|
||||
for (var i = 0; i < formatterNames.Length; i++)
|
||||
{
|
||||
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="TagHelperDescriptorResolver_InvalidTagHelperLookupText" xml:space="preserve">
|
||||
<value>Invalid tag helper directive look up text '{0}'. The correct look up text format is: "typeName, assemblyName".</value>
|
||||
</data>
|
||||
<data name="TagHelperTypeResolver_CannotResolveTagHelperAssembly" xml:space="preserve">
|
||||
<value>Cannot resolve TagHelper containing assembly '{0}'. Error: {1}</value>
|
||||
</data>
|
||||
<data name="TagHelperTypeResolver_TagHelperAssemblyNameCannotBeEmptyOrNull" xml:space="preserve">
|
||||
<value>Tag helper directive assembly name cannot be null or empty.</value>
|
||||
</data>
|
||||
<data name="ScopeManager_EndCannotBeCalledWithoutACallToBegin" xml:space="preserve">
|
||||
<value>Must call '{2}.{1}' before calling '{2}.{0}'.</value>
|
||||
</data>
|
||||
<data name="HtmlTargetElementAttribute_NameCannotBeNullOrWhitespace" xml:space="preserve">
|
||||
<value>{0} name cannot be null or whitespace.</value>
|
||||
</data>
|
||||
<data name="ArgumentCannotBeNullOrEmpty" xml:space="preserve">
|
||||
<value>The value cannot be null or empty.</value>
|
||||
</data>
|
||||
<data name="TagHelperDescriptorResolver_EncounteredUnexpectedError" xml:space="preserve">
|
||||
<value>Encountered an unexpected error when attempting to resolve tag helper directive '{0}' with value '{1}'. Error: {2}</value>
|
||||
</data>
|
||||
<data name="HtmlTargetElementAttribute_InvalidName" xml:space="preserve">
|
||||
<value>Tag helpers cannot target {0} name '{1}' because it contains a '{2}' character.</value>
|
||||
</data>
|
||||
<data name="TagHelperDescriptorResolver_InvalidTagHelperDirective" xml:space="preserve">
|
||||
<value>Invalid tag helper directive '{0}'. Cannot have multiple '{0}' directives on a page.</value>
|
||||
</data>
|
||||
<data name="TagHelperDescriptorResolver_InvalidTagHelperPrefixValue" xml:space="preserve">
|
||||
<value>Invalid tag helper directive '{0}' value. '{1}' is not allowed in prefix '{2}'.</value>
|
||||
</data>
|
||||
<data name="TagHelperDescriptorFactory_Attribute" xml:space="preserve">
|
||||
<value>Attribute</value>
|
||||
</data>
|
||||
<data name="TagHelperDescriptorFactory_Name" xml:space="preserve">
|
||||
<value>name</value>
|
||||
</data>
|
||||
<data name="TagHelperDescriptorFactory_Prefix" xml:space="preserve">
|
||||
<value>prefix</value>
|
||||
</data>
|
||||
<data name="TagHelperDescriptorFactory_Tag" xml:space="preserve">
|
||||
<value>Tag</value>
|
||||
</data>
|
||||
<data name="TagHelperDescriptorFactory_InvalidAttributeNameAttribute" xml:space="preserve">
|
||||
<value>Invalid tag helper bound property '{0}.{1}'. An '{2}' must not be associated with a property with no public setter unless its type implements '{3}'.</value>
|
||||
</data>
|
||||
<data name="TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixCharacter" xml:space="preserve">
|
||||
<value>Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with {2} '{3}' because {2} contains a '{4}' character.</value>
|
||||
</data>
|
||||
<data name="TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixStart" xml:space="preserve">
|
||||
<value>Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with {2} '{3}' because {2} starts with '{4}'.</value>
|
||||
</data>
|
||||
<data name="TagHelperDescriptorFactory_InvalidAttributeNameOrPrefixWhitespace" xml:space="preserve">
|
||||
<value>Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a whitespace {2}.</value>
|
||||
</data>
|
||||
<data name="TagHelperDescriptorFactory_InvalidAttributeNameNullOrEmpty" xml:space="preserve">
|
||||
<value>Invalid tag helper bound property '{0}.{1}'. Tag helpers cannot bind to HTML attributes with a null or empty name.</value>
|
||||
</data>
|
||||
<data name="TagHelperDescriptorFactory_InvalidAttributeNameNotNullOrEmpty" xml:space="preserve">
|
||||
<value>Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null or empty if property has no public setter.</value>
|
||||
</data>
|
||||
<data name="TagHelperDescriptorFactory_InvalidAttributePrefixNull" xml:space="preserve">
|
||||
<value>Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must not be null if property has no public setter and its type implements '{4}'.</value>
|
||||
</data>
|
||||
<data name="TagHelperDescriptorFactory_InvalidAttributePrefixNotNull" xml:space="preserve">
|
||||
<value>Invalid tag helper bound property '{0}.{1}'. '{2}.{3}' must be null unless property type implements '{4}'.</value>
|
||||
</data>
|
||||
<data name="TagHelperAttributeList_CannotAddWithNullName" xml:space="preserve">
|
||||
<value>Cannot add a '{0}' with a null '{1}'.</value>
|
||||
</data>
|
||||
<data name="TagHelperAttributeList_CannotAddAttribute" xml:space="preserve">
|
||||
<value>Cannot add a {0} with inconsistent names. The {1} property '{2}' must match the location '{3}'.</value>
|
||||
</data>
|
||||
<data name="TagHelperDescriptorFactory_InvalidRestrictChildrenAttributeName" xml:space="preserve">
|
||||
<value>Invalid '{0}' tag name '{1}' for tag helper '{2}'. Tag helpers cannot restrict child elements that contain a '{3}' character.</value>
|
||||
</data>
|
||||
<data name="TagHelperDescriptorFactory_InvalidRestrictChildrenAttributeNameNullWhitespace" xml:space="preserve">
|
||||
<value>Invalid '{0}' tag name for tag helper '{1}'. Name cannot be null or whitespace.</value>
|
||||
</data>
|
||||
<data name="TagHelperDescriptorFactory_ParentTag" xml:space="preserve">
|
||||
<value>Parent Tag</value>
|
||||
</data>
|
||||
<data name="ArgumentMustBeAnInstanceOf" xml:space="preserve">
|
||||
<value>Argument must be an instance of '{0}'.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||
{
|
||||
internal static class Constants
|
||||
{
|
||||
public static readonly TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(10);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Metadata common to types and properties.
|
||||
/// </summary>
|
||||
public interface IMemberInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name.
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a collection of custom <see cref="Attribute"/>s of type <typeparamref name="TAttribute"/> applied
|
||||
/// to this instance of <see cref="IMemberInfo"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TAttribute">The type of <see cref="Attribute"/> to search for.</typeparam>
|
||||
/// <returns>A sequence of custom <see cref="Attribute"/>s of type
|
||||
/// <typeparamref name="TAttribute"/>.</returns>
|
||||
/// <remarks>Result not include inherited <see cref="Attribute"/>s.</remarks>
|
||||
IEnumerable<TAttribute> GetCustomAttributes<TAttribute>()
|
||||
where TAttribute : Attribute;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains property metadata.
|
||||
/// </summary>
|
||||
public interface IPropertyInfo : IMemberInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this property has a public getter.
|
||||
/// </summary>
|
||||
bool HasPublicGetter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this property has a public setter.
|
||||
/// </summary>
|
||||
bool HasPublicSetter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="ITypeInfo"/> of the property.
|
||||
/// </summary>
|
||||
ITypeInfo PropertyType { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains type metadata.
|
||||
/// </summary>
|
||||
public interface ITypeInfo : IMemberInfo, IEquatable<ITypeInfo>
|
||||
{
|
||||
/// <summary>
|
||||
/// Fully qualified name of the type.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// On CoreCLR, some BCL types get type forwarded to the full desktop framework implementations at
|
||||
/// runtime. For e.g. we compile against System.String in System.Runtime which is type forwarded to
|
||||
/// mscorlib at runtime. Consequently for generic types where the <see cref="FullName"/> includes the assembly
|
||||
/// qualified name of generic parameters, FullNames would not match.
|
||||
/// Use <see cref="IEquatable{ITypeInfo}.Equals(ITypeInfo)"/> to compare <see cref="ITypeInfo"/>s instead.
|
||||
/// </remarks>
|
||||
string FullName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets <see cref="IPropertyInfo"/>s for all properties of the current type excluding indexers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Indexers in this context refer to the CLR notion of an indexer (<c>this [string name]</c>
|
||||
/// and does not overlap with the semantics of
|
||||
/// <see cref="Razor.Compilation.TagHelpers.TagHelperAttributeDescriptor.IsIndexer"/>.
|
||||
/// </remarks>
|
||||
IEnumerable<IPropertyInfo> Properties { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the type is public.
|
||||
/// </summary>
|
||||
bool IsPublic { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the type is abstract or an interface.
|
||||
/// </summary>
|
||||
bool IsAbstract { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the type is generic.
|
||||
/// </summary>
|
||||
bool IsGenericType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the type implements the <param name="interfaceTypeInfo"/> interface.
|
||||
/// </summary>
|
||||
bool ImplementsInterface(ITypeInfo interfaceTypeInfo);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="ITypeInfo[]"/> for the <c>TKey</c> and <c>TValue</c> parameters of
|
||||
/// <see cref="IDictionary{TKey, TValue}"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The <see cref="ITypeInfo"/> of <c>TKey</c> and <c>TValue</c>
|
||||
/// parameters if the type implements <see cref="IDictionary{TKey, TValue}"/>, otherwise <c>null</c>.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// For open generic types, <see cref="ITypeInfo" /> for generic type parameters is <c>null</c>.
|
||||
/// </remarks>
|
||||
ITypeInfo[] GetGenericDictionaryParameters();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="IPropertyInfo"/> adapter for <see cref="PropertyInfo"/> instances.
|
||||
/// </summary>
|
||||
public class RuntimePropertyInfo : IPropertyInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="RuntimePropertyInfo"/>.
|
||||
/// </summary>
|
||||
/// <param name="propertyInfo">The <see cref="PropertyInfo"/> instance to adapt.</param>
|
||||
public RuntimePropertyInfo(PropertyInfo propertyInfo)
|
||||
{
|
||||
if (propertyInfo == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(propertyInfo));
|
||||
}
|
||||
|
||||
Property = propertyInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="PropertyInfo"/> instance.
|
||||
/// </summary>
|
||||
public PropertyInfo Property { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasPublicGetter => Property.GetMethod != null && Property.GetMethod.IsPublic;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasPublicSetter => Property.SetMethod != null && Property.SetMethod.IsPublic;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => Property.Name;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ITypeInfo PropertyType => new RuntimeTypeInfo(Property.PropertyType.GetTypeInfo());
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TAttribute> GetCustomAttributes<TAttribute>() where TAttribute : Attribute
|
||||
=> Property.GetCustomAttributes<TAttribute>(inherit: false);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString() =>
|
||||
Property.ToString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,179 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.AspNet.Razor.TagHelpers;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="ITypeInfo"/> adapter for <see cref="System.Reflection.TypeInfo"/> instances.
|
||||
/// </summary>
|
||||
public class RuntimeTypeInfo : ITypeInfo
|
||||
{
|
||||
private static readonly Regex _fullNameSanitizer = new Regex(
|
||||
@", [A-Za-z\.]+, Version=\d+\.\d+\.\d+\.\d+, Culture=neutral, PublicKeyToken=\w+",
|
||||
RegexOptions.ExplicitCapture,
|
||||
Constants.RegexMatchTimeout);
|
||||
|
||||
private static readonly TypeInfo TagHelperTypeInfo = typeof(ITagHelper).GetTypeInfo();
|
||||
private IEnumerable<IPropertyInfo> _properties;
|
||||
private string _sanitizedFullName;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="RuntimeTypeInfo"/>
|
||||
/// </summary>
|
||||
/// <param name="propertyInfo">The <see cref="System.Reflection.TypeInfo"/> instance to adapt.</param>
|
||||
public RuntimeTypeInfo(TypeInfo typeInfo)
|
||||
{
|
||||
if (typeInfo == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(typeInfo));
|
||||
}
|
||||
|
||||
TypeInfo = typeInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="System.Reflection.TypeInfo"/> instance.
|
||||
/// </summary>
|
||||
public TypeInfo TypeInfo { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => TypeInfo.Name;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string FullName => TypeInfo.FullName;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsAbstract => TypeInfo.IsAbstract;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsGenericType => TypeInfo.IsGenericType;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsPublic => TypeInfo.IsPublic;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IPropertyInfo> Properties
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_properties == null)
|
||||
{
|
||||
_properties = TypeInfo
|
||||
.AsType()
|
||||
.GetRuntimeProperties()
|
||||
.Where(property => property.GetIndexParameters().Length == 0)
|
||||
.Select(property => new RuntimePropertyInfo(property));
|
||||
}
|
||||
|
||||
return _properties;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ImplementsInterface(ITypeInfo interfaceTypeInfo)
|
||||
{
|
||||
if (interfaceTypeInfo == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(interfaceTypeInfo));
|
||||
}
|
||||
|
||||
var runtimeTypeInfo = interfaceTypeInfo as RuntimeTypeInfo;
|
||||
if (runtimeTypeInfo == null)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
Resources.FormatArgumentMustBeAnInstanceOf(typeof(RuntimeTypeInfo).FullName),
|
||||
nameof(interfaceTypeInfo));
|
||||
}
|
||||
|
||||
return runtimeTypeInfo.TypeInfo.IsInterface && runtimeTypeInfo.TypeInfo.IsAssignableFrom(TypeInfo);
|
||||
}
|
||||
|
||||
private string SanitizedFullName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_sanitizedFullName == null)
|
||||
{
|
||||
_sanitizedFullName = SanitizeFullName(FullName);
|
||||
}
|
||||
|
||||
return _sanitizedFullName;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TAttribute> GetCustomAttributes<TAttribute>() where TAttribute : Attribute =>
|
||||
TypeInfo.GetCustomAttributes<TAttribute>(inherit: false);
|
||||
|
||||
/// <inheritdoc />
|
||||
public ITypeInfo[] GetGenericDictionaryParameters()
|
||||
{
|
||||
return ClosedGenericMatcher.ExtractGenericInterface(
|
||||
TypeInfo.AsType(),
|
||||
typeof(IDictionary<,>))
|
||||
?.GenericTypeArguments
|
||||
.Select(type => type.IsGenericParameter ? null : new RuntimeTypeInfo(type.GetTypeInfo()))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString() => TypeInfo.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return Equals(obj as ITypeInfo);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Equals(ITypeInfo other)
|
||||
{
|
||||
if (other == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var otherRuntimeType = other as RuntimeTypeInfo;
|
||||
if (otherRuntimeType != null)
|
||||
{
|
||||
return otherRuntimeType.TypeInfo == TypeInfo;
|
||||
}
|
||||
|
||||
return string.Equals(
|
||||
SanitizedFullName,
|
||||
SanitizeFullName(other.FullName),
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode() => SanitizedFullName.GetHashCode();
|
||||
|
||||
/// <summary>
|
||||
/// Removes assembly qualification from generic type parameters for the specified <paramref name="fullName"/>.
|
||||
/// </summary>
|
||||
/// <param name="fullName">Full name.</param>
|
||||
/// <returns>Full name without fully qualified generic parameters.</returns>
|
||||
/// <example>
|
||||
/// <c>typeof(<see cref="List{string}"/>).FullName</c> is
|
||||
/// List`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]
|
||||
/// <c>Sanitize(typeof(<see cref="List{string}"/>.FullName</c> returns
|
||||
/// List`1[[System.String]
|
||||
/// </example>
|
||||
public static string SanitizeFullName(string fullName)
|
||||
{
|
||||
// In CoreCLR, some types (such as System.String) are type forwarded from System.Runtime
|
||||
// to mscorlib at runtime. Type names of generic type parameters includes the assembly qualified name;
|
||||
// consequently the type name generated at precompilation differs from the one at runtime. We'll
|
||||
// avoid dealing with these inconsistencies by removing assembly information from TypeInfo.FullName.
|
||||
return _fullNameSanitizer.Replace(fullName, string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,753 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.AspNet.Razor.Compilation.TagHelpers;
|
||||
using Microsoft.AspNet.Razor.TagHelpers;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Factory for <see cref="TagHelperDescriptor"/>s from <see cref="ITypeInfo"/>s.
|
||||
/// </summary>
|
||||
public static class TagHelperDescriptorFactory
|
||||
{
|
||||
private const string DataDashPrefix = "data-";
|
||||
private const string TagHelperNameEnding = "TagHelper";
|
||||
private const string HtmlCaseRegexReplacement = "-$1$2";
|
||||
|
||||
// This matches the following AFTER the start of the input string (MATCH).
|
||||
// Any letter/number followed by an uppercase letter then lowercase letter: 1(Aa), a(Aa), A(Aa)
|
||||
// Any lowercase letter followed by an uppercase letter: a(A)
|
||||
// Each match is then prefixed by a "-" via the ToHtmlCase method.
|
||||
private static readonly Regex HtmlCaseRegex =
|
||||
new Regex(
|
||||
"(?<!^)((?<=[a-zA-Z0-9])[A-Z][a-z])|((?<=[a-z])[A-Z])",
|
||||
RegexOptions.None,
|
||||
Constants.RegexMatchTimeout);
|
||||
|
||||
private static readonly ITypeInfo StringTypeInfo = new RuntimeTypeInfo(typeof(string).GetTypeInfo());
|
||||
|
||||
// TODO: Investigate if we should cache TagHelperDescriptors for types:
|
||||
// https://github.com/aspnet/Razor/issues/165
|
||||
|
||||
public static ICollection<char> InvalidNonWhitespaceNameCharacters { get; } = new HashSet<char>(
|
||||
new[] { '@', '!', '<', '/', '?', '[', '>', ']', '=', '"', '\'', '*' });
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="TagHelperDescriptor"/> from the given <paramref name="typeInfo"/>.
|
||||
/// </summary>
|
||||
/// <param name="assemblyName">The assembly name that contains <paramref name="type"/>.</param>
|
||||
/// <param name="typeInfo">The <see cref="ITypeInfo"/> to create a <see cref="TagHelperDescriptor"/> from.
|
||||
/// </param>
|
||||
/// <param name="designTime">Indicates if the returned <see cref="TagHelperDescriptor"/>s should include
|
||||
/// design time specific information.</param>
|
||||
/// <param name="errorSink">The <see cref="ErrorSink"/> used to collect <see cref="RazorError"/>s encountered
|
||||
/// when creating <see cref="TagHelperDescriptor"/>s for the given <paramref name="typeInfo"/>.</param>
|
||||
/// <returns>
|
||||
/// A collection of <see cref="TagHelperDescriptor"/>s that describe the given <paramref name="typeInfo"/>.
|
||||
/// </returns>
|
||||
public static IEnumerable<TagHelperDescriptor> CreateDescriptors(
|
||||
string assemblyName,
|
||||
ITypeInfo typeInfo,
|
||||
bool designTime,
|
||||
ErrorSink errorSink)
|
||||
{
|
||||
if (typeInfo == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(typeInfo));
|
||||
}
|
||||
|
||||
if (errorSink == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(errorSink));
|
||||
}
|
||||
|
||||
if (ShouldSkipDescriptorCreation(designTime, typeInfo))
|
||||
{
|
||||
return Enumerable.Empty<TagHelperDescriptor>();
|
||||
}
|
||||
|
||||
var attributeDescriptors = GetAttributeDescriptors(typeInfo, designTime, errorSink);
|
||||
var targetElementAttributes = GetValidHtmlTargetElementAttributes(typeInfo, errorSink);
|
||||
var allowedChildren = GetAllowedChildren(typeInfo, errorSink);
|
||||
|
||||
var tagHelperDescriptors =
|
||||
BuildTagHelperDescriptors(
|
||||
typeInfo,
|
||||
assemblyName,
|
||||
attributeDescriptors,
|
||||
targetElementAttributes,
|
||||
allowedChildren,
|
||||
designTime);
|
||||
|
||||
return tagHelperDescriptors.Distinct(TagHelperDescriptorComparer.Default);
|
||||
}
|
||||
|
||||
private static IEnumerable<HtmlTargetElementAttribute> GetValidHtmlTargetElementAttributes(
|
||||
ITypeInfo typeInfo,
|
||||
ErrorSink errorSink)
|
||||
{
|
||||
var targetElementAttributes = typeInfo.GetCustomAttributes<HtmlTargetElementAttribute>();
|
||||
|
||||
return targetElementAttributes.Where(
|
||||
attribute => ValidHtmlTargetElementAttributeNames(attribute, errorSink));
|
||||
}
|
||||
|
||||
private static IEnumerable<TagHelperDescriptor> BuildTagHelperDescriptors(
|
||||
ITypeInfo typeInfo,
|
||||
string assemblyName,
|
||||
IEnumerable<TagHelperAttributeDescriptor> attributeDescriptors,
|
||||
IEnumerable<HtmlTargetElementAttribute> targetElementAttributes,
|
||||
IEnumerable<string> allowedChildren,
|
||||
bool designTime)
|
||||
{
|
||||
TagHelperDesignTimeDescriptor typeDesignTimeDescriptor = null;
|
||||
|
||||
#if !DOTNET5_4
|
||||
if (designTime)
|
||||
{
|
||||
var runtimeTypeInfo = typeInfo as RuntimeTypeInfo;
|
||||
if (runtimeTypeInfo != null)
|
||||
{
|
||||
typeDesignTimeDescriptor =
|
||||
TagHelperDesignTimeDescriptorFactory.CreateDescriptor(runtimeTypeInfo.TypeInfo.AsType());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
var typeName = typeInfo.FullName;
|
||||
|
||||
// If there isn't an attribute specifying the tag name derive it from the name
|
||||
if (!targetElementAttributes.Any())
|
||||
{
|
||||
var name = typeInfo.Name;
|
||||
|
||||
if (name.EndsWith(TagHelperNameEnding, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
name = name.Substring(0, name.Length - TagHelperNameEnding.Length);
|
||||
}
|
||||
|
||||
return new[]
|
||||
{
|
||||
BuildTagHelperDescriptor(
|
||||
ToHtmlCase(name),
|
||||
typeName,
|
||||
assemblyName,
|
||||
attributeDescriptors,
|
||||
requiredAttributes: Enumerable.Empty<string>(),
|
||||
allowedChildren: allowedChildren,
|
||||
tagStructure: default(TagStructure),
|
||||
parentTag: null,
|
||||
designTimeDescriptor: typeDesignTimeDescriptor)
|
||||
};
|
||||
}
|
||||
|
||||
return targetElementAttributes.Select(
|
||||
attribute =>
|
||||
BuildTagHelperDescriptor(
|
||||
typeName,
|
||||
assemblyName,
|
||||
attributeDescriptors,
|
||||
attribute,
|
||||
allowedChildren,
|
||||
typeDesignTimeDescriptor));
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetAllowedChildren(ITypeInfo typeInfo, ErrorSink errorSink)
|
||||
{
|
||||
var restrictChildrenAttribute = typeInfo
|
||||
.GetCustomAttributes<RestrictChildrenAttribute>()
|
||||
.FirstOrDefault();
|
||||
if (restrictChildrenAttribute == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var allowedChildren = restrictChildrenAttribute.ChildTags;
|
||||
var validAllowedChildren = GetValidAllowedChildren(allowedChildren, typeInfo.FullName, errorSink);
|
||||
|
||||
if (validAllowedChildren.Any())
|
||||
{
|
||||
return validAllowedChildren;
|
||||
}
|
||||
else
|
||||
{
|
||||
// All allowed children were invalid, return null to indicate that any child is acceptable.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
var valid = TryValidateName(
|
||||
name,
|
||||
whitespaceError:
|
||||
Resources.FormatTagHelperDescriptorFactory_InvalidRestrictChildrenAttributeNameNullWhitespace(
|
||||
nameof(RestrictChildrenAttribute),
|
||||
tagHelperName),
|
||||
characterErrorBuilder: (invalidCharacter) =>
|
||||
Resources.FormatTagHelperDescriptorFactory_InvalidRestrictChildrenAttributeName(
|
||||
nameof(RestrictChildrenAttribute),
|
||||
name,
|
||||
tagHelperName,
|
||||
invalidCharacter),
|
||||
errorSink: errorSink);
|
||||
|
||||
if (valid)
|
||||
{
|
||||
validAllowedChildren.Add(name);
|
||||
}
|
||||
}
|
||||
|
||||
return validAllowedChildren;
|
||||
}
|
||||
|
||||
private static TagHelperDescriptor BuildTagHelperDescriptor(
|
||||
string typeName,
|
||||
string assemblyName,
|
||||
IEnumerable<TagHelperAttributeDescriptor> attributeDescriptors,
|
||||
HtmlTargetElementAttribute targetElementAttribute,
|
||||
IEnumerable<string> allowedChildren,
|
||||
TagHelperDesignTimeDescriptor designTimeDescriptor)
|
||||
{
|
||||
var requiredAttributes = GetCommaSeparatedValues(targetElementAttribute.Attributes);
|
||||
|
||||
return BuildTagHelperDescriptor(
|
||||
targetElementAttribute.Tag,
|
||||
typeName,
|
||||
assemblyName,
|
||||
attributeDescriptors,
|
||||
requiredAttributes,
|
||||
allowedChildren,
|
||||
targetElementAttribute.ParentTag,
|
||||
targetElementAttribute.TagStructure,
|
||||
designTimeDescriptor);
|
||||
}
|
||||
|
||||
private static TagHelperDescriptor BuildTagHelperDescriptor(
|
||||
string tagName,
|
||||
string typeName,
|
||||
string assemblyName,
|
||||
IEnumerable<TagHelperAttributeDescriptor> attributeDescriptors,
|
||||
IEnumerable<string> requiredAttributes,
|
||||
IEnumerable<string> allowedChildren,
|
||||
string parentTag,
|
||||
TagStructure tagStructure,
|
||||
TagHelperDesignTimeDescriptor designTimeDescriptor)
|
||||
{
|
||||
return new TagHelperDescriptor
|
||||
{
|
||||
TagName = tagName,
|
||||
TypeName = typeName,
|
||||
AssemblyName = assemblyName,
|
||||
Attributes = attributeDescriptors,
|
||||
RequiredAttributes = requiredAttributes,
|
||||
AllowedChildren = allowedChildren,
|
||||
RequiredParent = parentTag,
|
||||
TagStructure = tagStructure,
|
||||
DesignTimeDescriptor = designTimeDescriptor
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal for testing.
|
||||
/// </summary>
|
||||
internal static IEnumerable<string> GetCommaSeparatedValues(string text)
|
||||
{
|
||||
// We don't want to remove empty entries, need to notify users of invalid values.
|
||||
return text?.Split(',').Select(tagName => tagName.Trim()) ?? Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal for testing.
|
||||
/// </summary>
|
||||
internal static bool ValidHtmlTargetElementAttributeNames(
|
||||
HtmlTargetElementAttribute attribute,
|
||||
ErrorSink errorSink)
|
||||
{
|
||||
var validTagName = ValidateName(attribute.Tag, targetingAttributes: false, errorSink: errorSink);
|
||||
var validAttributeNames = true;
|
||||
var attributeNames = GetCommaSeparatedValues(attribute.Attributes);
|
||||
|
||||
foreach (var attributeName in attributeNames)
|
||||
{
|
||||
if (!ValidateName(attributeName, targetingAttributes: true, errorSink: errorSink))
|
||||
{
|
||||
validAttributeNames = false;
|
||||
}
|
||||
}
|
||||
|
||||
var validParentTagName = ValidateParentTagName(attribute.ParentTag, errorSink);
|
||||
|
||||
return validTagName && validAttributeNames && validParentTagName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal for unit testing.
|
||||
/// </summary>
|
||||
internal static bool ValidateParentTagName(string parentTag, ErrorSink errorSink)
|
||||
{
|
||||
return parentTag == null ||
|
||||
TryValidateName(
|
||||
parentTag,
|
||||
Resources.FormatHtmlTargetElementAttribute_NameCannotBeNullOrWhitespace(
|
||||
Resources.TagHelperDescriptorFactory_ParentTag),
|
||||
characterErrorBuilder: (invalidCharacter) =>
|
||||
Resources.FormatHtmlTargetElementAttribute_InvalidName(
|
||||
Resources.TagHelperDescriptorFactory_ParentTag.ToLower(),
|
||||
parentTag,
|
||||
invalidCharacter),
|
||||
errorSink: errorSink);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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 = TryValidateName(
|
||||
name,
|
||||
whitespaceError: Resources.FormatHtmlTargetElementAttribute_NameCannotBeNullOrWhitespace(targetName),
|
||||
characterErrorBuilder: (invalidCharacter) =>
|
||||
Resources.FormatHtmlTargetElementAttribute_InvalidName(
|
||||
targetName.ToLower(),
|
||||
name,
|
||||
invalidCharacter),
|
||||
errorSink: errorSink);
|
||||
|
||||
return validName;
|
||||
}
|
||||
|
||||
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 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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
private static bool ShouldSkipDescriptorCreation(bool designTime, IMemberInfo memberInfo)
|
||||
{
|
||||
if (designTime)
|
||||
{
|
||||
var editorBrowsableAttribute = memberInfo
|
||||
.GetCustomAttributes<EditorBrowsableAttribute>()
|
||||
.FirstOrDefault();
|
||||
|
||||
return editorBrowsableAttribute != null &&
|
||||
editorBrowsableAttribute.State == EditorBrowsableState.Never;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private static TagHelperAttributeDescriptor ToAttributeDescriptor(
|
||||
IPropertyInfo property,
|
||||
string attributeName,
|
||||
bool designTime)
|
||||
{
|
||||
return ToAttributeDescriptor(
|
||||
property,
|
||||
attributeName,
|
||||
property.PropertyType.FullName,
|
||||
isIndexer: false,
|
||||
isStringProperty: StringTypeInfo.Equals(property.PropertyType),
|
||||
designTime: designTime);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private static TagHelperAttributeDescriptor ToAttributeDescriptor(
|
||||
IPropertyInfo property,
|
||||
string attributeName,
|
||||
string typeName,
|
||||
bool isIndexer,
|
||||
bool isStringProperty,
|
||||
bool designTime)
|
||||
{
|
||||
TagHelperAttributeDesignTimeDescriptor propertyDesignTimeDescriptor = null;
|
||||
|
||||
#if !DOTNET5_4
|
||||
if (designTime)
|
||||
{
|
||||
var runtimeProperty = property as RuntimePropertyInfo;
|
||||
if (runtimeProperty != null)
|
||||
{
|
||||
propertyDesignTimeDescriptor =
|
||||
TagHelperDesignTimeDescriptorFactory.CreateAttributeDescriptor(runtimeProperty.Property);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return new TagHelperAttributeDescriptor
|
||||
{
|
||||
Name = attributeName,
|
||||
PropertyName = property.Name,
|
||||
TypeName = typeName,
|
||||
IsStringProperty = isStringProperty,
|
||||
IsIndexer = isIndexer,
|
||||
DesignTimeDescriptor = propertyDesignTimeDescriptor
|
||||
};
|
||||
}
|
||||
|
||||
private static bool IsAccessibleProperty(IPropertyInfo property)
|
||||
{
|
||||
// Accessible properties are those with public getters and without [HtmlAttributeNotBound].
|
||||
return property.HasPublicGetter &&
|
||||
property.GetCustomAttributes<HtmlAttributeNotBoundAttribute>().FirstOrDefault() == null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts from pascal/camel case to lower kebab-case.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// SomeThing => some-thing
|
||||
/// capsONInside => caps-on-inside
|
||||
/// CAPSOnOUTSIDE => caps-on-outside
|
||||
/// ALLCAPS => allcaps
|
||||
/// One1Two2Three3 => one1-two2-three3
|
||||
/// ONE1TWO2THREE3 => one1two2three3
|
||||
/// First_Second_ThirdHi => first_second_third-hi
|
||||
/// </example>
|
||||
private static string ToHtmlCase(string name)
|
||||
{
|
||||
return HtmlCaseRegex.Replace(name, HtmlCaseRegexReplacement).ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,307 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Razor.Compilation.TagHelpers;
|
||||
using Microsoft.AspNet.Razor.Parser;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to resolve <see cref="TagHelperDescriptor"/>s.
|
||||
/// </summary>
|
||||
public class TagHelperDescriptorResolver : ITagHelperDescriptorResolver
|
||||
{
|
||||
private static readonly IReadOnlyDictionary<TagHelperDirectiveType, string> _directiveNames =
|
||||
new Dictionary<TagHelperDirectiveType, string>
|
||||
{
|
||||
{ TagHelperDirectiveType.AddTagHelper, SyntaxConstants.CSharp.AddTagHelperKeyword },
|
||||
{ TagHelperDirectiveType.RemoveTagHelper, SyntaxConstants.CSharp.RemoveTagHelperKeyword },
|
||||
{ TagHelperDirectiveType.TagHelperPrefix, SyntaxConstants.CSharp.TagHelperPrefixKeyword },
|
||||
};
|
||||
|
||||
private readonly TagHelperTypeResolver _typeResolver;
|
||||
private readonly bool _designTime;
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new instance of the <see cref="TagHelperDescriptorResolver"/> class.
|
||||
/// </summary>
|
||||
/// <param name="designTime">Indicates whether resolved <see cref="TagHelperDescriptor"/>s should include
|
||||
/// design time specific information.</param>
|
||||
public TagHelperDescriptorResolver(bool designTime)
|
||||
: this(new TagHelperTypeResolver(), designTime)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new instance of <see cref="TagHelperDescriptorResolver"/> class with the
|
||||
/// specified <paramref name="typeResolver"/>.
|
||||
/// </summary>
|
||||
/// <param name="typeResolver">The <see cref="TagHelperTypeResolver"/>.</param>
|
||||
/// <param name="designTime">Indicates whether resolved <see cref="TagHelperDescriptor"/>s should include
|
||||
/// design time specific information.</param>
|
||||
public TagHelperDescriptorResolver(TagHelperTypeResolver typeResolver, bool designTime)
|
||||
{
|
||||
_typeResolver = typeResolver;
|
||||
_designTime = designTime;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TagHelperDescriptor> Resolve(TagHelperDescriptorResolutionContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var resolvedDescriptors = new HashSet<TagHelperDescriptor>(TagHelperDescriptorComparer.Default);
|
||||
|
||||
// tagHelperPrefix directives do not affect which TagHelperDescriptors are added or removed from the final
|
||||
// list, need to remove them.
|
||||
var actionableDirectiveDescriptors = context.DirectiveDescriptors.Where(
|
||||
directive => directive.DirectiveType != TagHelperDirectiveType.TagHelperPrefix);
|
||||
|
||||
foreach (var directiveDescriptor in actionableDirectiveDescriptors)
|
||||
{
|
||||
try
|
||||
{
|
||||
var lookupInfo = GetLookupInfo(directiveDescriptor, context.ErrorSink);
|
||||
|
||||
// Could not resolve the lookup info.
|
||||
if (lookupInfo == null)
|
||||
{
|
||||
return Enumerable.Empty<TagHelperDescriptor>();
|
||||
}
|
||||
|
||||
if (directiveDescriptor.DirectiveType == TagHelperDirectiveType.RemoveTagHelper)
|
||||
{
|
||||
resolvedDescriptors.RemoveWhere(descriptor => MatchesLookupInfo(descriptor, lookupInfo));
|
||||
}
|
||||
else if (directiveDescriptor.DirectiveType == TagHelperDirectiveType.AddTagHelper)
|
||||
{
|
||||
var descriptors = ResolveDescriptorsInAssembly(
|
||||
lookupInfo.AssemblyName,
|
||||
lookupInfo.AssemblyNameLocation,
|
||||
context.ErrorSink);
|
||||
|
||||
// Only use descriptors that match our lookup info
|
||||
descriptors = descriptors.Where(descriptor => MatchesLookupInfo(descriptor, lookupInfo));
|
||||
|
||||
resolvedDescriptors.UnionWith(descriptors);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
string directiveName;
|
||||
_directiveNames.TryGetValue(directiveDescriptor.DirectiveType, out directiveName);
|
||||
Debug.Assert(!string.IsNullOrEmpty(directiveName));
|
||||
|
||||
context.ErrorSink.OnError(
|
||||
directiveDescriptor.Location,
|
||||
Resources.FormatTagHelperDescriptorResolver_EncounteredUnexpectedError(
|
||||
"@" + directiveName,
|
||||
directiveDescriptor.DirectiveText,
|
||||
ex.Message),
|
||||
GetErrorLength(directiveDescriptor.DirectiveText));
|
||||
}
|
||||
}
|
||||
|
||||
var prefixedDescriptors = PrefixDescriptors(context, resolvedDescriptors);
|
||||
|
||||
return prefixedDescriptors;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves all <see cref="TagHelperDescriptor"/>s for <see cref="Razor.TagHelpers.ITagHelper"/>s from the
|
||||
/// given <paramref name="assemblyName"/>.
|
||||
/// </summary>
|
||||
/// <param name="assemblyName">
|
||||
/// The name of the assembly to resolve <see cref="TagHelperDescriptor"/>s from.
|
||||
/// </param>
|
||||
/// <param name="documentLocation">The <see cref="SourceLocation"/> of the directive.</param>
|
||||
/// <param name="errorSink">Used to record errors found when resolving <see cref="TagHelperDescriptor"/>s
|
||||
/// within the given <paramref name="assemblyName"/>.</param>
|
||||
/// <returns><see cref="TagHelperDescriptor"/>s for <see cref="Razor.TagHelpers.ITagHelper"/>s from the given
|
||||
/// <paramref name="assemblyName"/>.</returns>
|
||||
// This is meant to be overridden by tooling to enable assembly level caching.
|
||||
protected virtual IEnumerable<TagHelperDescriptor> ResolveDescriptorsInAssembly(
|
||||
string assemblyName,
|
||||
SourceLocation documentLocation,
|
||||
ErrorSink errorSink)
|
||||
{
|
||||
// Resolve valid tag helper types from the assembly.
|
||||
var tagHelperTypes = _typeResolver.Resolve(assemblyName, documentLocation, errorSink);
|
||||
|
||||
// Convert types to TagHelperDescriptors
|
||||
var descriptors = tagHelperTypes.SelectMany(
|
||||
type => TagHelperDescriptorFactory.CreateDescriptors(assemblyName, type, _designTime, errorSink));
|
||||
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
private static IEnumerable<TagHelperDescriptor> PrefixDescriptors(
|
||||
TagHelperDescriptorResolutionContext context,
|
||||
IEnumerable<TagHelperDescriptor> descriptors)
|
||||
{
|
||||
var tagHelperPrefix = ResolveTagHelperPrefix(context);
|
||||
|
||||
if (!string.IsNullOrEmpty(tagHelperPrefix))
|
||||
{
|
||||
return descriptors.Select(descriptor =>
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
Prefix = tagHelperPrefix,
|
||||
TagName = descriptor.TagName,
|
||||
TypeName = descriptor.TypeName,
|
||||
AssemblyName = descriptor.AssemblyName,
|
||||
Attributes = descriptor.Attributes,
|
||||
RequiredAttributes = descriptor.RequiredAttributes,
|
||||
AllowedChildren = descriptor.AllowedChildren,
|
||||
RequiredParent = descriptor.RequiredParent,
|
||||
TagStructure = descriptor.TagStructure,
|
||||
DesignTimeDescriptor = descriptor.DesignTimeDescriptor
|
||||
});
|
||||
}
|
||||
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
private static string ResolveTagHelperPrefix(TagHelperDescriptorResolutionContext context)
|
||||
{
|
||||
var prefixDirectiveDescriptors = context.DirectiveDescriptors.Where(
|
||||
descriptor => descriptor.DirectiveType == TagHelperDirectiveType.TagHelperPrefix);
|
||||
|
||||
TagHelperDirectiveDescriptor prefixDirective = null;
|
||||
|
||||
foreach (var directive in prefixDirectiveDescriptors)
|
||||
{
|
||||
if (prefixDirective == null)
|
||||
{
|
||||
prefixDirective = directive;
|
||||
}
|
||||
else
|
||||
{
|
||||
// For each invalid @tagHelperPrefix we need to create an error.
|
||||
context.ErrorSink.OnError(
|
||||
directive.Location,
|
||||
Resources.FormatTagHelperDescriptorResolver_InvalidTagHelperDirective(
|
||||
SyntaxConstants.CSharp.TagHelperPrefixKeyword),
|
||||
GetErrorLength(directive.DirectiveText));
|
||||
}
|
||||
}
|
||||
|
||||
var prefix = prefixDirective?.DirectiveText;
|
||||
|
||||
if (prefix != null && !EnsureValidPrefix(prefix, prefixDirective.Location, context.ErrorSink))
|
||||
{
|
||||
prefix = null;
|
||||
}
|
||||
|
||||
return prefix;
|
||||
}
|
||||
|
||||
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 bool MatchesLookupInfo(TagHelperDescriptor descriptor, LookupInfo lookupInfo)
|
||||
{
|
||||
if (!string.Equals(descriptor.AssemblyName, lookupInfo.AssemblyName, StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (lookupInfo.TypePattern.EndsWith("*", StringComparison.Ordinal))
|
||||
{
|
||||
if (lookupInfo.TypePattern.Length == 1)
|
||||
{
|
||||
// TypePattern is "*".
|
||||
return true;
|
||||
}
|
||||
|
||||
var lookupTypeName = lookupInfo.TypePattern.Substring(0, lookupInfo.TypePattern.Length - 1);
|
||||
|
||||
return descriptor.TypeName.StartsWith(lookupTypeName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
return string.Equals(descriptor.TypeName, lookupInfo.TypePattern, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
private static int GetErrorLength(string directiveText)
|
||||
{
|
||||
var nonNullLength = directiveText == null ? 1 : directiveText.Length;
|
||||
var normalizeEmptyStringLength = Math.Max(nonNullLength, 1);
|
||||
|
||||
return normalizeEmptyStringLength;
|
||||
}
|
||||
|
||||
private class LookupInfo
|
||||
{
|
||||
public string AssemblyName { get; set; }
|
||||
|
||||
public string TypePattern { get; set; }
|
||||
|
||||
public SourceLocation AssemblyNameLocation { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,215 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
#if !DOTNET5_4 // Cannot accurately resolve the location of the documentation XML file in coreclr.
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNet.Razor.Compilation.TagHelpers;
|
||||
using Microsoft.AspNet.Razor.TagHelpers;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Factory for providing <see cref="TagHelperDesignTimeDescriptor"/>s from <see cref="Type"/>s and
|
||||
/// <see cref="TagHelperAttributeDesignTimeDescriptor"/>s from <see cref="PropertyInfo"/>s.
|
||||
/// </summary>
|
||||
public static class TagHelperDesignTimeDescriptorFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a <see cref="TagHelperDesignTimeDescriptor"/> from the given <paramref name="type"/>.
|
||||
/// </summary>
|
||||
/// <param name="type">
|
||||
/// The <see cref="Type"/> to create a <see cref="TagHelperDesignTimeDescriptor"/> from.
|
||||
/// </param>
|
||||
/// <returns>A <see cref="TagHelperDesignTimeDescriptor"/> that describes design time specific information
|
||||
/// for the given <paramref name="type"/>.</returns>
|
||||
public static TagHelperDesignTimeDescriptor CreateDescriptor(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(type));
|
||||
}
|
||||
|
||||
var id = XmlDocumentationProvider.GetId(type);
|
||||
var documentationDescriptor = CreateDocumentationDescriptor(type.Assembly, id);
|
||||
|
||||
// Purposefully not using the TypeInfo.GetCustomAttributes method here to make it easier to mock the Type.
|
||||
var outputElementHintAttribute = type
|
||||
.GetCustomAttributes(inherit: false)
|
||||
?.OfType<OutputElementHintAttribute>()
|
||||
.FirstOrDefault();
|
||||
var outputElementHint = outputElementHintAttribute?.OutputElement;
|
||||
|
||||
if (documentationDescriptor != null || outputElementHint != null)
|
||||
{
|
||||
return new TagHelperDesignTimeDescriptor
|
||||
{
|
||||
Summary = documentationDescriptor?.Summary,
|
||||
Remarks = documentationDescriptor?.Remarks,
|
||||
OutputElementHint = outputElementHint
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="TagHelperAttributeDesignTimeDescriptor"/> from the given
|
||||
/// <paramref name="propertyInfo"/>.
|
||||
/// </summary>
|
||||
/// <param name="propertyInfo">
|
||||
/// The <see cref="PropertyInfo"/> to create a <see cref="TagHelperAttributeDesignTimeDescriptor"/> from.
|
||||
/// </param>
|
||||
/// <returns>A <see cref="TagHelperAttributeDesignTimeDescriptor"/> that describes design time specific
|
||||
/// information for the given <paramref name="propertyInfo"/>.</returns>
|
||||
public static TagHelperAttributeDesignTimeDescriptor CreateAttributeDescriptor(
|
||||
PropertyInfo propertyInfo)
|
||||
{
|
||||
if (propertyInfo == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(propertyInfo));
|
||||
}
|
||||
|
||||
var id = XmlDocumentationProvider.GetId(propertyInfo);
|
||||
var declaringAssembly = propertyInfo.DeclaringType.Assembly;
|
||||
var documentationDescriptor = CreateDocumentationDescriptor(declaringAssembly, id);
|
||||
|
||||
if (documentationDescriptor != null)
|
||||
{
|
||||
return new TagHelperAttributeDesignTimeDescriptor
|
||||
{
|
||||
Summary = documentationDescriptor.Summary,
|
||||
Remarks = documentationDescriptor.Remarks
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static DocumentationDescriptor CreateDocumentationDescriptor(Assembly assembly, string id)
|
||||
{
|
||||
var assemblyLocation = assembly.Location;
|
||||
|
||||
if (string.IsNullOrEmpty(assemblyLocation) && !string.IsNullOrEmpty(assembly.CodeBase))
|
||||
{
|
||||
var uri = new UriBuilder(assembly.CodeBase);
|
||||
|
||||
// Normalize the path to a UNC path. This will remove things like file:// from start of the uri.Path.
|
||||
assemblyLocation = Uri.UnescapeDataString(uri.Path);
|
||||
}
|
||||
|
||||
// Couldn't resolve a valid assemblyLocation.
|
||||
if (string.IsNullOrEmpty(assemblyLocation))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var xmlDocumentationFile = GetXmlDocumentationFile(assembly, assemblyLocation);
|
||||
|
||||
// Only want to process the file if it exists.
|
||||
if (xmlDocumentationFile != null)
|
||||
{
|
||||
var documentationProvider = new XmlDocumentationProvider(xmlDocumentationFile.FullName);
|
||||
|
||||
var summary = documentationProvider.GetSummary(id);
|
||||
var remarks = documentationProvider.GetRemarks(id);
|
||||
|
||||
if (!string.IsNullOrEmpty(summary) || !string.IsNullOrEmpty(remarks))
|
||||
{
|
||||
return new DocumentationDescriptor
|
||||
{
|
||||
Summary = summary,
|
||||
Remarks = remarks
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static FileInfo GetXmlDocumentationFile(Assembly assembly, string assemblyLocation)
|
||||
{
|
||||
try
|
||||
{
|
||||
var assemblyDirectory = Path.GetDirectoryName(assemblyLocation);
|
||||
var assemblyName = Path.GetFileName(assemblyLocation);
|
||||
var assemblyXmlDocumentationName = Path.ChangeExtension(assemblyName, ".xml");
|
||||
|
||||
// Check for a localized XML file for the current culture.
|
||||
var xmlDocumentationFile = GetLocalizedXmlDocumentationFile(
|
||||
CultureInfo.CurrentCulture,
|
||||
assemblyDirectory,
|
||||
assemblyXmlDocumentationName);
|
||||
|
||||
if (xmlDocumentationFile == null)
|
||||
{
|
||||
// Check for a culture-neutral XML file next to the assembly
|
||||
xmlDocumentationFile = new FileInfo(
|
||||
Path.Combine(assemblyDirectory, assemblyXmlDocumentationName));
|
||||
|
||||
if (!xmlDocumentationFile.Exists)
|
||||
{
|
||||
xmlDocumentationFile = null;
|
||||
}
|
||||
}
|
||||
|
||||
return xmlDocumentationFile;
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
// Could not resolve XML file.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<string> ExpandPaths(
|
||||
CultureInfo culture,
|
||||
string assemblyDirectory,
|
||||
string assemblyXmlDocumentationName)
|
||||
{
|
||||
// Following the fall-back process defined by:
|
||||
// https://msdn.microsoft.com/en-us/library/sb6a8618.aspx#cpconpackagingdeployingresourcesanchor1
|
||||
do
|
||||
{
|
||||
var cultureName = culture.Name;
|
||||
var cultureSpecificFileName =
|
||||
Path.ChangeExtension(assemblyXmlDocumentationName, cultureName + ".xml");
|
||||
|
||||
// Look for a culture specific XML file next to the assembly.
|
||||
yield return Path.Combine(assemblyDirectory, cultureSpecificFileName);
|
||||
|
||||
// Look for an XML file with the same name as the assembly in a culture specific directory.
|
||||
yield return Path.Combine(assemblyDirectory, cultureName, assemblyXmlDocumentationName);
|
||||
|
||||
// Look for a culture specific XML file in a culture specific directory.
|
||||
yield return Path.Combine(assemblyDirectory, cultureName, cultureSpecificFileName);
|
||||
|
||||
culture = culture.Parent;
|
||||
} while (culture != null && culture != CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private static FileInfo GetLocalizedXmlDocumentationFile(
|
||||
CultureInfo culture,
|
||||
string assemblyDirectory,
|
||||
string assemblyXmlDocumentationName)
|
||||
{
|
||||
var localizedXmlPaths = ExpandPaths(culture, assemblyDirectory, assemblyXmlDocumentationName);
|
||||
var xmlDocumentationFile = localizedXmlPaths
|
||||
.Select(path => new FileInfo(path))
|
||||
.FirstOrDefault(file => file.Exists);
|
||||
|
||||
return xmlDocumentationFile;
|
||||
}
|
||||
|
||||
private class DocumentationDescriptor
|
||||
{
|
||||
public string Summary { get; set; }
|
||||
public string Remarks { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,255 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Razor.TagHelpers;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Class used to store information about a <see cref="ITagHelper"/>'s execution lifetime.
|
||||
/// </summary>
|
||||
public class TagHelperExecutionContext
|
||||
{
|
||||
private readonly List<ITagHelper> _tagHelpers;
|
||||
private readonly Func<Task> _executeChildContentAsync;
|
||||
private readonly Action _startTagHelperWritingScope;
|
||||
private readonly Func<TagHelperContent> _endTagHelperWritingScope;
|
||||
private TagHelperContent _childContent;
|
||||
|
||||
/// <summary>
|
||||
/// Internal for testing purposes only.
|
||||
/// </summary>
|
||||
internal TagHelperExecutionContext(string tagName, TagMode tagMode)
|
||||
: this(tagName,
|
||||
tagMode,
|
||||
items: new Dictionary<object, object>(),
|
||||
uniqueId: string.Empty,
|
||||
executeChildContentAsync: async () => await Task.FromResult(result: true),
|
||||
startTagHelperWritingScope: () => { },
|
||||
endTagHelperWritingScope: () => new DefaultTagHelperContent())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="TagHelperExecutionContext"/>.
|
||||
/// </summary>
|
||||
/// <param name="tagName">The HTML tag name in the Razor source.</param>
|
||||
/// <param name="tagMode">HTML syntax of the element in the Razor source.</param>
|
||||
/// <param name="items">The collection of items used to communicate with other
|
||||
/// <see cref="ITagHelper"/>s</param>
|
||||
/// <param name="uniqueId">An identifier unique to the HTML element this context is for.</param>
|
||||
/// <param name="executeChildContentAsync">A delegate used to execute the child content asynchronously.</param>
|
||||
/// <param name="startTagHelperWritingScope">A delegate used to start a writing scope in a Razor page.</param>
|
||||
/// <param name="endTagHelperWritingScope">A delegate used to end a writing scope in a Razor page.</param>
|
||||
public TagHelperExecutionContext(
|
||||
string tagName,
|
||||
TagMode tagMode,
|
||||
IDictionary<object, object> items,
|
||||
string uniqueId,
|
||||
Func<Task> executeChildContentAsync,
|
||||
Action startTagHelperWritingScope,
|
||||
Func<TagHelperContent> endTagHelperWritingScope)
|
||||
{
|
||||
if (tagName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tagName));
|
||||
}
|
||||
|
||||
if (items == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(items));
|
||||
}
|
||||
|
||||
if (uniqueId == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(uniqueId));
|
||||
}
|
||||
|
||||
if (executeChildContentAsync == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(executeChildContentAsync));
|
||||
}
|
||||
|
||||
if (startTagHelperWritingScope == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(startTagHelperWritingScope));
|
||||
}
|
||||
|
||||
if (endTagHelperWritingScope == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(endTagHelperWritingScope));
|
||||
}
|
||||
|
||||
_tagHelpers = new List<ITagHelper>();
|
||||
_executeChildContentAsync = executeChildContentAsync;
|
||||
_startTagHelperWritingScope = startTagHelperWritingScope;
|
||||
_endTagHelperWritingScope = endTagHelperWritingScope;
|
||||
|
||||
TagMode = tagMode;
|
||||
HTMLAttributes = new TagHelperAttributeList();
|
||||
AllAttributes = new TagHelperAttributeList();
|
||||
TagName = tagName;
|
||||
Items = items;
|
||||
UniqueId = uniqueId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the HTML syntax of the element in the Razor source.
|
||||
/// </summary>
|
||||
public TagMode TagMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if <see cref="GetChildContentAsync"/> has been called.
|
||||
/// </summary>
|
||||
public bool ChildContentRetrieved
|
||||
{
|
||||
get
|
||||
{
|
||||
return _childContent != null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of items used to communicate with other <see cref="ITagHelper"/>s.
|
||||
/// </summary>
|
||||
public IDictionary<object, object> Items { get; }
|
||||
|
||||
/// <summary>
|
||||
/// HTML attributes.
|
||||
/// </summary>
|
||||
public TagHelperAttributeList HTMLAttributes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="ITagHelper"/> bound attributes and HTML attributes.
|
||||
/// </summary>
|
||||
public TagHelperAttributeList AllAttributes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// An identifier unique to the HTML element this context is for.
|
||||
/// </summary>
|
||||
public string UniqueId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="ITagHelper"/>s that should be run.
|
||||
/// </summary>
|
||||
public IEnumerable<ITagHelper> TagHelpers
|
||||
{
|
||||
get
|
||||
{
|
||||
return _tagHelpers;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The HTML tag name in the Razor source.
|
||||
/// </summary>
|
||||
public string TagName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ITagHelper"/>s' output.
|
||||
/// </summary>
|
||||
public TagHelperOutput Output { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Tracks the given <paramref name="tagHelper"/>.
|
||||
/// </summary>
|
||||
/// <param name="tagHelper">The tag helper to track.</param>
|
||||
public void Add(ITagHelper tagHelper)
|
||||
{
|
||||
if (tagHelper == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tagHelper));
|
||||
}
|
||||
|
||||
_tagHelpers.Add(tagHelper);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tracks the minimized HTML attribute in <see cref="AllAttributes"/> and <see cref="HTMLAttributes"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The minimized HTML attribute name.</param>
|
||||
public void AddMinimizedHtmlAttribute(string name)
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
HTMLAttributes.Add(
|
||||
new TagHelperAttribute
|
||||
{
|
||||
Name = name,
|
||||
Minimized = true
|
||||
});
|
||||
AllAttributes.Add(
|
||||
new TagHelperAttribute
|
||||
{
|
||||
Name = name,
|
||||
Minimized = true
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tracks the HTML attribute in <see cref="AllAttributes"/> and <see cref="HTMLAttributes"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The HTML attribute name.</param>
|
||||
/// <param name="value">The HTML attribute value.</param>
|
||||
public void AddHtmlAttribute(string name, object value)
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
HTMLAttributes.Add(name, value);
|
||||
AllAttributes.Add(name, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tracks the <see cref="ITagHelper"/> bound attribute in <see cref="AllAttributes"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The bound attribute name.</param>
|
||||
/// <param name="value">The attribute value.</param>
|
||||
public void AddTagHelperAttribute(string name, object value)
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
AllAttributes.Add(name, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the child content asynchronously.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Task"/> which on completion executes all child content.</returns>
|
||||
public Task ExecuteChildContentAsync()
|
||||
{
|
||||
return _executeChildContentAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute and retrieve the rendered child content asynchronously.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Task"/> that on completion returns the rendered child content.</returns>
|
||||
/// <remarks>
|
||||
/// Child content is only executed once. Successive calls to this method or successive executions of the
|
||||
/// returned <see cref="Task{TagHelperContent}"/> return a cached result.
|
||||
/// </remarks>
|
||||
public async Task<TagHelperContent> GetChildContentAsync(bool useCachedResult)
|
||||
{
|
||||
if (!useCachedResult || _childContent == null)
|
||||
{
|
||||
_startTagHelperWritingScope();
|
||||
await _executeChildContentAsync();
|
||||
_childContent = _endTagHelperWritingScope();
|
||||
}
|
||||
|
||||
return new DefaultTagHelperContent().SetContent(_childContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Razor.TagHelpers;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// A class used to run <see cref="ITagHelper"/>s.
|
||||
/// </summary>
|
||||
public class TagHelperRunner
|
||||
{
|
||||
/// <summary>
|
||||
/// Calls the <see cref="ITagHelper.ProcessAsync"/> method on <see cref="ITagHelper"/>s.
|
||||
/// </summary>
|
||||
/// <param name="executionContext">Contains information associated with running <see cref="ITagHelper"/>s.
|
||||
/// </param>
|
||||
/// <returns>Resulting <see cref="TagHelperOutput"/> from processing all of the
|
||||
/// <paramref name="executionContext"/>'s <see cref="ITagHelper"/>s.</returns>
|
||||
public async Task<TagHelperOutput> RunAsync(TagHelperExecutionContext executionContext)
|
||||
{
|
||||
if (executionContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(executionContext));
|
||||
}
|
||||
|
||||
var tagHelperContext = new TagHelperContext(
|
||||
executionContext.AllAttributes,
|
||||
executionContext.Items,
|
||||
executionContext.UniqueId);
|
||||
var orderedTagHelpers = executionContext.TagHelpers.OrderBy(tagHelper => tagHelper.Order);
|
||||
|
||||
foreach (var tagHelper in orderedTagHelpers)
|
||||
{
|
||||
tagHelper.Init(tagHelperContext);
|
||||
}
|
||||
|
||||
var tagHelperOutput = new TagHelperOutput(
|
||||
executionContext.TagName,
|
||||
executionContext.HTMLAttributes,
|
||||
executionContext.GetChildContentAsync)
|
||||
{
|
||||
TagMode = executionContext.TagMode,
|
||||
};
|
||||
|
||||
foreach (var tagHelper in orderedTagHelpers)
|
||||
{
|
||||
await tagHelper.ProcessAsync(tagHelperContext, tagHelperOutput);
|
||||
}
|
||||
|
||||
return tagHelperOutput;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Razor.TagHelpers;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Class that manages <see cref="TagHelperExecutionContext"/> scopes.
|
||||
/// </summary>
|
||||
public class TagHelperScopeManager
|
||||
{
|
||||
private readonly Stack<TagHelperExecutionContext> _executionScopes;
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="TagHelperScopeManager"/>.
|
||||
/// </summary>
|
||||
public TagHelperScopeManager()
|
||||
{
|
||||
_executionScopes = new Stack<TagHelperExecutionContext>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts a <see cref="TagHelperExecutionContext"/> scope.
|
||||
/// </summary>
|
||||
/// <param name="tagName">The HTML tag name that the scope is associated with.</param>
|
||||
/// <param name="tagMode">HTML syntax of the element in the Razor source.</param>
|
||||
/// <param name="uniqueId">An identifier unique to the HTML element this scope is for.</param>
|
||||
/// <param name="executeChildContentAsync">A delegate used to execute the child content asynchronously.</param>
|
||||
/// <param name="startTagHelperWritingScope">A delegate used to start a writing scope in a Razor page.</param>
|
||||
/// <param name="endTagHelperWritingScope">A delegate used to end a writing scope in a Razor page.</param>
|
||||
/// <returns>A <see cref="TagHelperExecutionContext"/> to use.</returns>
|
||||
public TagHelperExecutionContext Begin(
|
||||
string tagName,
|
||||
TagMode tagMode,
|
||||
string uniqueId,
|
||||
Func<Task> executeChildContentAsync,
|
||||
Action startTagHelperWritingScope,
|
||||
Func<TagHelperContent> endTagHelperWritingScope)
|
||||
{
|
||||
if (tagName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tagName));
|
||||
}
|
||||
|
||||
if (uniqueId == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(uniqueId));
|
||||
}
|
||||
|
||||
if (executeChildContentAsync == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(executeChildContentAsync));
|
||||
}
|
||||
|
||||
if (startTagHelperWritingScope == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(startTagHelperWritingScope));
|
||||
}
|
||||
|
||||
if (endTagHelperWritingScope == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(endTagHelperWritingScope));
|
||||
}
|
||||
|
||||
IDictionary<object, object> items;
|
||||
|
||||
// If we're not wrapped by another TagHelper, then there will not be a parentExecutionContext.
|
||||
if (_executionScopes.Count > 0)
|
||||
{
|
||||
items = new CopyOnWriteDictionary<object, object>(
|
||||
_executionScopes.Peek().Items,
|
||||
comparer: EqualityComparer<object>.Default);
|
||||
}
|
||||
else
|
||||
{
|
||||
items = new Dictionary<object, object>();
|
||||
}
|
||||
|
||||
var executionContext = new TagHelperExecutionContext(
|
||||
tagName,
|
||||
tagMode,
|
||||
items,
|
||||
uniqueId,
|
||||
executeChildContentAsync,
|
||||
startTagHelperWritingScope,
|
||||
endTagHelperWritingScope);
|
||||
|
||||
_executionScopes.Push(executionContext);
|
||||
|
||||
return executionContext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends a <see cref="TagHelperExecutionContext"/> scope.
|
||||
/// </summary>
|
||||
/// <returns>If the current scope is nested, the parent <see cref="TagHelperExecutionContext"/>.
|
||||
/// <c>null</c> otherwise.</returns>
|
||||
public TagHelperExecutionContext End()
|
||||
{
|
||||
if (_executionScopes.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatScopeManager_EndCannotBeCalledWithoutACallToBegin(
|
||||
nameof(End),
|
||||
nameof(Begin),
|
||||
nameof(TagHelperScopeManager)));
|
||||
}
|
||||
|
||||
_executionScopes.Pop();
|
||||
|
||||
if (_executionScopes.Count != 0)
|
||||
{
|
||||
return _executionScopes.Peek();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNet.Razor.TagHelpers;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Class that locates valid <see cref="ITagHelper"/>s within an assembly.
|
||||
/// </summary>
|
||||
public class TagHelperTypeResolver
|
||||
{
|
||||
private static readonly ITypeInfo ITagHelperTypeInfo = new RuntimeTypeInfo(typeof(ITagHelper).GetTypeInfo());
|
||||
|
||||
/// <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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all non-nested exported types from the given <paramref name="assemblyName"/>
|
||||
/// </summary>
|
||||
/// <param name="assemblyName">The <see cref="AssemblyName"/> to get <see cref="ITypeInfo"/>s from.</param>
|
||||
/// <returns>
|
||||
/// An <see cref="IEnumerable{ITypeInfo}"/> of types exported from the given <paramref name="assemblyName"/>.
|
||||
/// </returns>
|
||||
protected virtual IEnumerable<ITypeInfo> GetTopLevelExportedTypes(AssemblyName assemblyName)
|
||||
{
|
||||
if (assemblyName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(assemblyName));
|
||||
}
|
||||
|
||||
var exportedTypeInfos = GetExportedTypes(assemblyName);
|
||||
|
||||
return exportedTypeInfos
|
||||
.Where(typeInfo => !typeInfo.IsNested)
|
||||
.Select(typeInfo => new RuntimeTypeInfo(typeInfo));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all exported types from the given <paramref name="assemblyName"/>
|
||||
/// </summary>
|
||||
/// <param name="assemblyName">The <see cref="AssemblyName"/> to get <see cref="TypeInfo"/>s from.</param>
|
||||
/// <returns>
|
||||
/// An <see cref="IEnumerable{TypeInfo}"/> of types exported from the given <paramref name="assemblyName"/>.
|
||||
/// </returns>
|
||||
protected virtual IEnumerable<TypeInfo> GetExportedTypes(AssemblyName assemblyName)
|
||||
{
|
||||
var assembly = Assembly.Load(assemblyName);
|
||||
|
||||
return assembly.ExportedTypes.Select(type => type.GetTypeInfo());
|
||||
}
|
||||
|
||||
// Internal for testing.
|
||||
internal virtual bool IsTagHelper(ITypeInfo typeInfo)
|
||||
{
|
||||
return typeInfo.IsPublic &&
|
||||
!typeInfo.IsAbstract &&
|
||||
!typeInfo.IsGenericType &&
|
||||
typeInfo.ImplementsInterface(ITagHelperTypeInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
#if !DOTNET5_4
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Extracts summary and remarks XML documentation from an XML documentation file.
|
||||
/// </summary>
|
||||
public class XmlDocumentationProvider
|
||||
{
|
||||
private readonly IEnumerable<XElement> _members;
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new instance of the <see cref="XmlDocumentationProvider"/>.
|
||||
/// </summary>
|
||||
/// <param name="xmlFileLocation">Path to the XML documentation file to read.</param>
|
||||
public XmlDocumentationProvider(string xmlFileLocation)
|
||||
{
|
||||
// XML file processing is defined by: https://msdn.microsoft.com/en-us/library/fsbx0t7x.aspx
|
||||
var xmlDocumentation = XDocument.Load(xmlFileLocation);
|
||||
var documentationRootMembers = xmlDocumentation.Root.Element("members");
|
||||
_members = documentationRootMembers.Elements("member");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the <c><summary></c> documentation for the given <paramref name="id"/>.
|
||||
/// </summary>
|
||||
/// <param name="id">The id to lookup.</param>
|
||||
/// <returns><c><summary></c> documentation for the given <paramref name="id"/>.</returns>
|
||||
public string GetSummary(string id)
|
||||
{
|
||||
var associatedMemeber = GetMember(id);
|
||||
var summaryElement = associatedMemeber?.Element("summary");
|
||||
|
||||
if (summaryElement != null)
|
||||
{
|
||||
var summaryValue = GetElementValue(summaryElement);
|
||||
|
||||
return summaryValue;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the <c><remarks></c> documentation for the given <paramref name="id"/>.
|
||||
/// </summary>
|
||||
/// <param name="id">The id to lookup.</param>
|
||||
/// <returns><c><remarks></c> documentation for the given <paramref name="id"/>.</returns>
|
||||
public string GetRemarks(string id)
|
||||
{
|
||||
var associatedMemeber = GetMember(id);
|
||||
var remarksElement = associatedMemeber?.Element("remarks");
|
||||
|
||||
if (remarksElement != null)
|
||||
{
|
||||
var remarksValue = GetElementValue(remarksElement);
|
||||
|
||||
return remarksValue;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates the <see cref="string"/> identifier for the given <paramref name="type"/>.
|
||||
/// </summary>
|
||||
/// <param name="type">The <see cref="Type"/> to get the identifier for.</param>
|
||||
/// <returns>The <see cref="string"/> identifier for the given <paramref name="type"/>.</returns>
|
||||
public static string GetId(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(type));
|
||||
}
|
||||
|
||||
return $"T:{type.FullName}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates the <see cref="string"/> identifier for the given <paramref name="propertyInfo"/>.
|
||||
/// </summary>
|
||||
/// <param name="propertyInfo">The <see cref="PropertyInfo"/> to get the identifier for.</param>
|
||||
/// <returns>The <see cref="string"/> identifier for the given <paramref name="propertyInfo"/>.</returns>
|
||||
public static string GetId(PropertyInfo propertyInfo)
|
||||
{
|
||||
if (propertyInfo == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(propertyInfo));
|
||||
}
|
||||
|
||||
var declaringTypeInfo = propertyInfo.DeclaringType;
|
||||
return $"P:{declaringTypeInfo.FullName}.{propertyInfo.Name}";
|
||||
}
|
||||
|
||||
private XElement GetMember(string id)
|
||||
{
|
||||
var associatedMemeber = _members
|
||||
.FirstOrDefault(element =>
|
||||
string.Equals(element.Attribute("name").Value, id, StringComparison.Ordinal));
|
||||
|
||||
return associatedMemeber;
|
||||
}
|
||||
|
||||
private static string GetElementValue(XElement element)
|
||||
{
|
||||
var stringBuilder = new StringBuilder();
|
||||
var node = element.FirstNode;
|
||||
|
||||
while (node != null)
|
||||
{
|
||||
stringBuilder.Append(node.ToString(SaveOptions.DisableFormatting));
|
||||
|
||||
node = node.NextNode;
|
||||
}
|
||||
|
||||
return stringBuilder.ToString().Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,231 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Microsoft.AspNet.Html.Abstractions;
|
||||
using Microsoft.AspNet.Razor.TagHelpers;
|
||||
using Microsoft.Extensions.Internal;
|
||||
using Microsoft.Extensions.WebEncoders;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Default concrete <see cref="TagHelperContent"/>.
|
||||
/// </summary>
|
||||
[DebuggerDisplay("{DebuggerToString(),nq}")]
|
||||
public class DefaultTagHelperContent : TagHelperContent
|
||||
{
|
||||
private BufferedHtmlContent _buffer;
|
||||
|
||||
private BufferedHtmlContent Buffer
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_buffer == null)
|
||||
{
|
||||
_buffer = new BufferedHtmlContent();
|
||||
}
|
||||
|
||||
return _buffer;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsModified => _buffer != null;
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>Returns <c>true</c> for a cleared <see cref="TagHelperContent"/>.</remarks>
|
||||
public override bool IsWhiteSpace
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_buffer == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
using (var writer = new EmptyOrWhiteSpaceWriter())
|
||||
{
|
||||
foreach (var entry in _buffer.Entries)
|
||||
{
|
||||
if (entry == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var stringValue = entry as string;
|
||||
if (stringValue != null)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(stringValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
((IHtmlContent)entry).WriteTo(writer, HtmlEncoder.Default);
|
||||
if (!writer.IsWhiteSpace)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsEmpty
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_buffer == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
using (var writer = new EmptyOrWhiteSpaceWriter())
|
||||
{
|
||||
foreach (var entry in _buffer.Entries)
|
||||
{
|
||||
if (entry == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var stringValue = entry as string;
|
||||
if (stringValue != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(stringValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
((IHtmlContent)entry).WriteTo(writer, HtmlEncoder.Default);
|
||||
if (!writer.IsEmpty)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override TagHelperContent Append(string unencoded)
|
||||
{
|
||||
Buffer.Append(unencoded);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override TagHelperContent AppendHtml(string encoded)
|
||||
{
|
||||
Buffer.AppendHtml(encoded);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override TagHelperContent Append(IHtmlContent htmlContent)
|
||||
{
|
||||
Buffer.Append(htmlContent);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override TagHelperContent Clear()
|
||||
{
|
||||
Buffer.Clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string GetContent()
|
||||
{
|
||||
return GetContent(HtmlEncoder.Default);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string GetContent(IHtmlEncoder encoder)
|
||||
{
|
||||
if (_buffer == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
using (var writer = new StringWriter())
|
||||
{
|
||||
WriteTo(writer, encoder);
|
||||
return writer.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void WriteTo(TextWriter writer, IHtmlEncoder encoder)
|
||||
{
|
||||
if (writer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(writer));
|
||||
}
|
||||
|
||||
if (encoder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(encoder));
|
||||
}
|
||||
|
||||
Buffer.WriteTo(writer, encoder);
|
||||
}
|
||||
|
||||
private string DebuggerToString()
|
||||
{
|
||||
return GetContent();
|
||||
}
|
||||
|
||||
// Overrides Write(string) to find if the content written is empty/whitespace.
|
||||
private class EmptyOrWhiteSpaceWriter : TextWriter
|
||||
{
|
||||
public override Encoding Encoding
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEmpty { get; private set; } = true;
|
||||
|
||||
public bool IsWhiteSpace { get; private set; } = true;
|
||||
|
||||
#if DOTNET5_4
|
||||
// This is an abstract method in DNXCore
|
||||
public override void Write(char value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
#endif
|
||||
|
||||
public override void Write(string value)
|
||||
{
|
||||
if (IsEmpty && !string.IsNullOrEmpty(value))
|
||||
{
|
||||
IsEmpty = false;
|
||||
}
|
||||
|
||||
if (IsWhiteSpace && !string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
IsWhiteSpace = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to override an <see cref="ITagHelper"/> property's HTML attribute name.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
|
||||
public sealed class HtmlAttributeNameAttribute : Attribute
|
||||
{
|
||||
private string _dictionaryAttributePrefix;
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new instance of the <see cref="HtmlAttributeNameAttribute"/> class with <see cref="Name"/>
|
||||
/// equal to <c>null</c>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Associated property must not have a public setter and must be compatible with
|
||||
/// <see cref="System.Collections.Generic.IDictionary{TKey, TValue}"/> where <c>TKey</c> is
|
||||
/// <see cref="string"/>.
|
||||
/// </remarks>
|
||||
public HtmlAttributeNameAttribute()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new instance of the <see cref="HtmlAttributeNameAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">
|
||||
/// HTML attribute name for the associated property. Must be <c>null</c> or empty if associated property does
|
||||
/// not have a public setter and is compatible with
|
||||
/// <see cref="System.Collections.Generic.IDictionary{TKey, TValue}"/> where <c>TKey</c> is
|
||||
/// <see cref="string"/>. Otherwise must not be <c>null</c> or empty.
|
||||
/// </param>
|
||||
public HtmlAttributeNameAttribute(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// HTML attribute name of the associated property.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>null</c> or empty if and only if associated property does not have a public setter and is compatible
|
||||
/// with <see cref="System.Collections.Generic.IDictionary{TKey, TValue}"/> where <c>TKey</c> is
|
||||
/// <see cref="string"/>.
|
||||
/// </value>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the prefix used to match HTML attribute names. Matching attributes are added to the
|
||||
/// associated property (an <see cref="System.Collections.Generic.IDictionary{TKey, TValue}"/>).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If non-<c>null</c> associated property must be compatible with
|
||||
/// <see cref="System.Collections.Generic.IDictionary{TKey, TValue}"/> where <c>TKey</c> is
|
||||
/// <see cref="string"/>.
|
||||
/// </remarks>
|
||||
/// <value>
|
||||
/// <para>
|
||||
/// If associated property is compatible with
|
||||
/// <see cref="System.Collections.Generic.IDictionary{TKey, TValue}"/>, default value is <c>Name + "-"</c>.
|
||||
/// <see cref="Name"/> must not be <c>null</c> or empty in this case.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Otherwise default value is <c>null</c>.
|
||||
/// </para>
|
||||
/// </value>
|
||||
public string DictionaryAttributePrefix
|
||||
{
|
||||
get
|
||||
{
|
||||
return _dictionaryAttributePrefix;
|
||||
}
|
||||
set
|
||||
{
|
||||
_dictionaryAttributePrefix = value;
|
||||
DictionaryAttributePrefixSet = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an indication whether <see cref="DictionaryAttributePrefix"/> has been set. Used to distinguish an
|
||||
/// uninitialized <see cref="DictionaryAttributePrefix"/> value from an explicit <c>null</c> setting.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if <see cref="DictionaryAttributePrefix"/> was set. <c>false</c> otherwise.</value>
|
||||
public bool DictionaryAttributePrefixSet { get; private set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates the associated <see cref="ITagHelper"/> property should not be bound to HTML attributes.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
|
||||
public sealed class HtmlAttributeNotBoundAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Instantiates a new instance of the <see cref="HtmlAttributeNotBoundAttribute"/> class.
|
||||
/// </summary>
|
||||
public HtmlAttributeNotBoundAttribute()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNet.Razor.Compilation.TagHelpers;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides an <see cref="ITagHelper"/>'s target.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
|
||||
public sealed class HtmlTargetElementAttribute : Attribute
|
||||
{
|
||||
public const string ElementCatchAllTarget = TagHelperDescriptorProvider.ElementCatchAllTarget;
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new instance of the <see cref="HtmlTargetElementAttribute"/> class that targets all HTML
|
||||
/// elements with the required <see cref="Attributes"/>.
|
||||
/// </summary>
|
||||
/// <remarks><see cref="Tag"/> is set to <c>*</c>.</remarks>
|
||||
public HtmlTargetElementAttribute()
|
||||
: this(ElementCatchAllTarget)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new instance of the <see cref="HtmlTargetElementAttribute"/> class with the given
|
||||
/// <paramref name="tag"/> as its <see cref="Tag"/> value.
|
||||
/// </summary>
|
||||
/// <param name="tag">
|
||||
/// The HTML tag the <see cref="ITagHelper"/> targets.
|
||||
/// </param>
|
||||
/// <remarks>A <c>*</c> <paramref name="tag"/> value indicates this <see cref="ITagHelper"/>
|
||||
/// targets all HTML elements with the required <see cref="Attributes"/>.</remarks>
|
||||
public HtmlTargetElementAttribute(string tag)
|
||||
{
|
||||
Tag = tag;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The HTML tag the <see cref="ITagHelper"/> targets. A <c>*</c> value indicates this <see cref="ITagHelper"/>
|
||||
/// targets all HTML elements with the required <see cref="Attributes"/>.
|
||||
/// </summary>
|
||||
public string Tag { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A comma-separated <see cref="string"/> of attribute names the HTML element must contain for the
|
||||
/// <see cref="ITagHelper"/> to run. <c>*</c> at the end of an attribute name acts as a prefix match.
|
||||
/// </summary>
|
||||
public string Attributes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The expected tag structure. Defaults to <see cref="TagStructure.Unspecified"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If <see cref="TagStructure.Unspecified"/> and no other tag helpers applying to the same element specify
|
||||
/// their <see cref="TagStructure"/> the <see cref="TagStructure.NormalOrSelfClosing"/> behavior is used:
|
||||
/// <para>
|
||||
/// <code>
|
||||
/// <my-tag-helper></my-tag-helper>
|
||||
/// <!-- OR -->
|
||||
/// <my-tag-helper />
|
||||
/// </code>
|
||||
/// Otherwise, if another tag helper applying to the same element does specify their behavior, that behavior
|
||||
/// is used.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If <see cref="TagStructure.WithoutEndTag"/> HTML elements can be written in the following formats:
|
||||
/// <code>
|
||||
/// <my-tag-helper>
|
||||
/// <!-- OR -->
|
||||
/// <my-tag-helper />
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public TagStructure TagStructure { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The required HTML element name of the direct parent. A <c>null</c> value indicates any HTML element name is
|
||||
/// allowed.
|
||||
/// </summary>
|
||||
public string ParentTag { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// A read-only HTML tag helper attribute.
|
||||
/// </summary>
|
||||
public interface IReadOnlyTagHelperAttribute : IEquatable<IReadOnlyTagHelperAttribute>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of the attribute.
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the attribute.
|
||||
/// </summary>
|
||||
object Value { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an indication whether the attribute is minimized or not.
|
||||
/// </summary>
|
||||
/// <remarks>If <c>true</c>, <see cref="Value"/> will be ignored.</remarks>
|
||||
bool Minimized { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Contract used to filter matching HTML elements.
|
||||
/// </summary>
|
||||
public interface ITagHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// When a set of<see cref= "ITagHelper" /> s are executed, their<see cref="Init(TagHelperContext)"/>'s
|
||||
/// are first invoked in the specified <see cref="Order"/>; then their
|
||||
/// <see cref="ProcessAsync(TagHelperContext, TagHelperOutput)"/>'s are invoked in the specified
|
||||
/// <see cref="Order"/>. Lower values are executed first.
|
||||
/// </summary>
|
||||
int Order { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the <see cref="ITagHelper"/> with the given <paramref name="context"/>. Additions to
|
||||
/// <see cref="TagHelperContext.Items"/> should be done within this method to ensure they're added prior to
|
||||
/// executing the children.
|
||||
/// </summary>
|
||||
/// <param name="context">Contains information associated with the current HTML tag.</param>
|
||||
/// <remarks>When more than one <see cref="ITagHelper"/> runs on the same element,
|
||||
/// <see cref="TagHelperOutput.GetChildContentAsync"/> may be invoked prior to <see cref="ProcessAsync"/>.
|
||||
/// </remarks>
|
||||
void Init(TagHelperContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously executes the <see cref="ITagHelper"/> with the given <paramref name="context"/> and
|
||||
/// <paramref name="output"/>.
|
||||
/// </summary>
|
||||
/// <param name="context">Contains information associated with the current HTML tag.</param>
|
||||
/// <param name="output">A stateful HTML element used to generate an HTML tag.</param>
|
||||
/// <returns>A <see cref="Task"/> that on completion updates the <paramref name="output"/>.</returns>
|
||||
Task ProcessAsync(TagHelperContext context, TagHelperOutput output);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a hint of the <see cref="ITagHelper"/>'s output element.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public sealed class OutputElementHintAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Instantiates a new instance of the <see cref="OutputElementHintAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="outputElement">
|
||||
/// The HTML element the <see cref="ITagHelper"/> may output.
|
||||
/// </param>
|
||||
public OutputElementHintAttribute(string outputElement)
|
||||
{
|
||||
if (outputElement == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(outputElement));
|
||||
}
|
||||
|
||||
OutputElement = outputElement;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The HTML element the <see cref="ITagHelper"/> may output.
|
||||
/// </summary>
|
||||
public string OutputElement { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,235 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// A read-only collection of <typeparamref name="TAttribute"/>s.
|
||||
/// </summary>
|
||||
/// <typeparam name="TAttribute">
|
||||
/// The type of <see cref="IReadOnlyTagHelperAttribute"/>s in the collection.
|
||||
/// </typeparam>
|
||||
public class ReadOnlyTagHelperAttributeList<TAttribute> : IReadOnlyList<TAttribute>
|
||||
where TAttribute : IReadOnlyTagHelperAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Instantiates a new instance of <see cref="ReadOnlyTagHelperAttributeList{TAttribute}"/> with an empty
|
||||
/// collection.
|
||||
/// </summary>
|
||||
protected ReadOnlyTagHelperAttributeList()
|
||||
{
|
||||
Attributes = new List<TAttribute>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new instance of <see cref="ReadOnlyTagHelperAttributeList{TAttribute}"/> with the specified
|
||||
/// <paramref name="attributes"/>.
|
||||
/// </summary>
|
||||
/// <param name="attributes">The collection to wrap.</param>
|
||||
public ReadOnlyTagHelperAttributeList(IEnumerable<TAttribute> attributes)
|
||||
{
|
||||
if (attributes == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(attributes));
|
||||
}
|
||||
|
||||
Attributes = new List<TAttribute>(attributes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The underlying collection of <typeparamref name="TAttribute"/>s.
|
||||
/// </summary>
|
||||
/// <remarks>Intended for use in a non-read-only subclass. Changes to this <see cref="List{TAttribute}"/> will
|
||||
/// affect all getters that <see cref="ReadOnlyTagHelperAttributeList{TAttribute}"/> provides.</remarks>
|
||||
protected List<TAttribute> Attributes { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public TAttribute this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return Attributes[index];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the first <typeparamref name="TAttribute"/> with <see cref="IReadOnlyTagHelperAttribute.Name"/>
|
||||
/// matching <paramref name="name"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">
|
||||
/// The <see cref="IReadOnlyTagHelperAttribute.Name"/> of the <typeparamref name="TAttribute"/> to get.
|
||||
/// </param>
|
||||
/// <returns>The first <typeparamref name="TAttribute"/> with <see cref="IReadOnlyTagHelperAttribute.Name"/>
|
||||
/// matching <paramref name="name"/>.
|
||||
/// </returns>
|
||||
/// <remarks><paramref name="name"/> is compared case-insensitively.</remarks>
|
||||
public TAttribute this[string name]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
return Attributes.FirstOrDefault(attribute => NameEquals(name, attribute));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
return Attributes.Count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a <typeparamref name="TAttribute"/> matching <paramref name="item"/> exists in the
|
||||
/// collection.
|
||||
/// </summary>
|
||||
/// <param name="item">The <typeparamref name="TAttribute"/> to locate.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if an <typeparamref name="TAttribute"/> matching <paramref name="item"/> exists in the
|
||||
/// collection; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// <paramref name="item"/>s <see cref="IReadOnlyTagHelperAttribute.Name"/> is compared case-insensitively.
|
||||
/// </remarks>
|
||||
public bool Contains(TAttribute item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
}
|
||||
|
||||
return Attributes.Contains(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a <typeparamref name="TAttribute"/> with the same
|
||||
/// <see cref="IReadOnlyTagHelperAttribute.Name"/> exists in the collection.
|
||||
/// </summary>
|
||||
/// <param name="name">The <see cref="IReadOnlyTagHelperAttribute.Name"/> of the
|
||||
/// <typeparamref name="TAttribute"/> to get.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if a <typeparamref name="TAttribute"/> with the same
|
||||
/// <see cref="IReadOnlyTagHelperAttribute.Name"/> exists in the collection; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <remarks><paramref name="name"/> is compared case-insensitively.</remarks>
|
||||
public bool ContainsName(string name)
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
return Attributes.Any(attribute => NameEquals(name, attribute));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches for a <typeparamref name="TAttribute"/> matching <paramref name="item"/> in the collection and
|
||||
/// returns the zero-based index of the first occurrence.
|
||||
/// </summary>
|
||||
/// <param name="item">The <typeparamref name="TAttribute"/> to locate.</param>
|
||||
/// <returns>The zero-based index of the first occurrence of a <typeparamref name="TAttribute"/> matching
|
||||
/// <paramref name="item"/> in the collection, if found; otherwise, –1.</returns>
|
||||
/// <remarks>
|
||||
/// <paramref name="item"/>s <see cref="IReadOnlyTagHelperAttribute.Name"/> is compared case-insensitively.
|
||||
/// </remarks>
|
||||
public int IndexOf(TAttribute item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
}
|
||||
|
||||
return Attributes.IndexOf(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the first <typeparamref name="TAttribute"/> with <see cref="IReadOnlyTagHelperAttribute.Name"/>
|
||||
/// matching <paramref name="name"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The <see cref="IReadOnlyTagHelperAttribute.Name"/> of the
|
||||
/// <typeparamref name="TAttribute"/> to get.</param>
|
||||
/// <param name="attribute">When this method returns, the first <typeparamref name="TAttribute"/> with
|
||||
/// <see cref="IReadOnlyTagHelperAttribute.Name"/> matching <paramref name="name"/>, if found; otherwise,
|
||||
/// <c>null</c>.</param>
|
||||
/// <returns><c>true</c> if a <typeparamref name="TAttribute"/> with the same
|
||||
/// <see cref="IReadOnlyTagHelperAttribute.Name"/> exists in the collection; otherwise, <c>false</c>.</returns>
|
||||
/// <remarks><paramref name="name"/> is compared case-insensitively.</remarks>
|
||||
public bool TryGetAttribute(string name, out TAttribute attribute)
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
attribute = Attributes.FirstOrDefault(attr => NameEquals(name, attr));
|
||||
|
||||
return attribute != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves <typeparamref name="TAttribute"/>s in the collection with
|
||||
/// <see cref="IReadOnlyTagHelperAttribute.Name"/> matching <paramref name="name"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The <see cref="IReadOnlyTagHelperAttribute.Name"/> of the
|
||||
/// <typeparamref name="TAttribute"/>s to get.</param>
|
||||
/// <param name="attributes">When this method returns, the <typeparamref name="TAttribute"/>s with
|
||||
/// <see cref="IReadOnlyTagHelperAttribute.Name"/> matching <paramref name="name"/>, if at least one is
|
||||
/// found; otherwise, <c>null</c>.</param>
|
||||
/// <returns><c>true</c> if at least one <typeparamref name="TAttribute"/> with the same
|
||||
/// <see cref="IReadOnlyTagHelperAttribute.Name"/> exists in the collection; otherwise, <c>false</c>.</returns>
|
||||
/// <remarks><paramref name="name"/> is compared case-insensitively.</remarks>
|
||||
public bool TryGetAttributes(string name, out IEnumerable<TAttribute> attributes)
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
attributes = Attributes.Where(attribute => NameEquals(name, attribute));
|
||||
|
||||
return attributes.Any();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerator<TAttribute> GetEnumerator()
|
||||
{
|
||||
return Attributes.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the specified <paramref name="attribute"/> has the same name as <paramref name="name"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The value to compare against <paramref name="attribute"/>s
|
||||
/// <see cref="TagHelperAttribute.Name"/>.</param>
|
||||
/// <param name="attribute">The attribute to compare against.</param>
|
||||
/// <returns><c>true</c> if <paramref name="name"/> case-insensitively matches <paramref name="attribute"/>s
|
||||
/// <see cref="TagHelperAttribute.Name"/>.</returns>
|
||||
protected static bool NameEquals(string name, TAttribute attribute)
|
||||
{
|
||||
if (attribute == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(attribute));
|
||||
}
|
||||
|
||||
return string.Equals(name, attribute.Name, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Restricts children of the <see cref="ITagHelper"/>'s element.
|
||||
/// </summary>
|
||||
/// <remarks>Combining this attribute with a <see cref="HtmlTargetElementAttribute"/> that specifies its
|
||||
/// <see cref="HtmlTargetElementAttribute.TagStructure"/> as <see cref="TagStructure.WithoutEndTag"/> will result
|
||||
/// in this attribute being ignored.</remarks>
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public class RestrictChildrenAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Instantiates a new instance of the <see cref="RestrictChildrenAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="childTag">
|
||||
/// The tag name of an element allowed as a child.
|
||||
/// </param>
|
||||
/// <param name="childTags">
|
||||
/// Additional names of elements allowed as children.
|
||||
/// </param>
|
||||
public RestrictChildrenAttribute(string childTag, params string[] childTags)
|
||||
{
|
||||
var concatenatedNames = new string[1 + childTags.Length];
|
||||
concatenatedNames[0] = childTag;
|
||||
|
||||
childTags.CopyTo(concatenatedNames, 1);
|
||||
|
||||
ChildTags = concatenatedNames;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the names of elements allowed as children.
|
||||
/// </summary>
|
||||
public IEnumerable<string> ChildTags { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Class used to filter matching HTML elements.
|
||||
/// </summary>
|
||||
public abstract class TagHelper : ITagHelper
|
||||
{
|
||||
/// <inheritdoc />
|
||||
/// <remarks>Default order is <c>0</c>.</remarks>
|
||||
public virtual int Order { get; } = 0;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Init(TagHelperContext context)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronously executes the <see cref="TagHelper"/> with the given <paramref name="context"/> and
|
||||
/// <paramref name="output"/>.
|
||||
/// </summary>
|
||||
/// <param name="context">Contains information associated with the current HTML tag.</param>
|
||||
/// <param name="output">A stateful HTML element used to generate an HTML tag.</param>
|
||||
public virtual void Process(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously executes the <see cref="TagHelper"/> with the given <paramref name="context"/> and
|
||||
/// <paramref name="output"/>.
|
||||
/// </summary>
|
||||
/// <param name="context">Contains information associated with the current HTML tag.</param>
|
||||
/// <param name="output">A stateful HTML element used to generate an HTML tag.</param>
|
||||
/// <returns>A <see cref="Task"/> that on completion updates the <paramref name="output"/>.</returns>
|
||||
/// <remarks>By default this calls into <see cref="Process"/>.</remarks>.
|
||||
#pragma warning disable 1998
|
||||
public virtual async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
Process(context, output);
|
||||
}
|
||||
#pragma warning restore 1998
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// An HTML tag helper attribute.
|
||||
/// </summary>
|
||||
public class TagHelperAttribute : IReadOnlyTagHelperAttribute
|
||||
{
|
||||
private static readonly int TypeHashCode = typeof(TagHelperAttribute).GetHashCode();
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new instance of <see cref="TagHelperAttribute"/>.
|
||||
/// </summary>
|
||||
public TagHelperAttribute()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new instance of <see cref="TagHelperAttribute" /> with values provided by the given
|
||||
/// <paramref name="attribute"/>.
|
||||
/// </summary>
|
||||
/// <param name="attribute">A <see cref="IReadOnlyTagHelperAttribute"/> whose values should be copied.</param>
|
||||
public TagHelperAttribute(IReadOnlyTagHelperAttribute attribute)
|
||||
: this(attribute?.Name, attribute?.Value)
|
||||
{
|
||||
if (attribute == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(attribute));
|
||||
}
|
||||
|
||||
Minimized = attribute.Minimized;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new instance of <see cref="TagHelperAttribute"/> with the specified <paramref name="name"/>
|
||||
/// and <paramref name="value"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The <see cref="Name"/> of the attribute.</param>
|
||||
/// <param name="value">The <see cref="Value"/> of the attribute.</param>
|
||||
public TagHelperAttribute(string name, object value)
|
||||
{
|
||||
Name = name;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the attribute.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value of the attribute.
|
||||
/// </summary>
|
||||
public object Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an indication whether the attribute is minimized or not.
|
||||
/// </summary>
|
||||
/// <remarks>If <c>true</c>, <see cref="Value"/> will be ignored.</remarks>
|
||||
public bool Minimized { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Converts the specified <paramref name="value"/> into a <see cref="TagHelperAttribute"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The <see cref="Value"/> of the created <see cref="TagHelperAttribute"/>.</param>
|
||||
/// <remarks>Created <see cref="TagHelperAttribute"/>s <see cref="Name"/> is set to <c>null</c>.</remarks>
|
||||
public static implicit operator TagHelperAttribute(string value)
|
||||
{
|
||||
return new TagHelperAttribute
|
||||
{
|
||||
Value = value
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks><see cref="Name"/> is compared case-insensitively.</remarks>
|
||||
public bool Equals(IReadOnlyTagHelperAttribute other)
|
||||
{
|
||||
return
|
||||
other != null &&
|
||||
string.Equals(Name, other.Name, StringComparison.OrdinalIgnoreCase) &&
|
||||
Minimized == other.Minimized &&
|
||||
(Minimized || Equals(Value, other.Value));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var other = obj as IReadOnlyTagHelperAttribute;
|
||||
|
||||
return Equals(other);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return TypeHashCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,292 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNet.Razor.Runtime;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of <see cref="TagHelperAttribute"/>s.
|
||||
/// </summary>
|
||||
public class TagHelperAttributeList : ReadOnlyTagHelperAttributeList<TagHelperAttribute>, IList<TagHelperAttribute>
|
||||
{
|
||||
/// <summary>
|
||||
/// Instantiates a new instance of <see cref="TagHelperAttributeList"/> with an empty collection.
|
||||
/// </summary>
|
||||
public TagHelperAttributeList()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new instance of <see cref="TagHelperAttributeList"/> with the specified
|
||||
/// <paramref name="attributes"/>.
|
||||
/// </summary>
|
||||
/// <param name="attributes">The collection to wrap.</param>
|
||||
public TagHelperAttributeList(IEnumerable<TagHelperAttribute> attributes)
|
||||
: base(attributes)
|
||||
{
|
||||
if (attributes == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(attributes));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
/// <paramref name="value"/>'s <see cref="TagHelperAttribute.Name"/> must not be <c>null</c>.
|
||||
/// </remarks>
|
||||
public new TagHelperAttribute this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return base[index];
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
if (value.Name == null)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
Resources.FormatTagHelperAttributeList_CannotAddWithNullName(
|
||||
typeof(TagHelperAttribute).FullName,
|
||||
nameof(TagHelperAttribute.Name)),
|
||||
nameof(value));
|
||||
}
|
||||
|
||||
Attributes[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the first <see cref="TagHelperAttribute"/> with <see cref="TagHelperAttribute.Name"/> matching
|
||||
/// <paramref name="name"/>. When setting, replaces the first matching
|
||||
/// <see cref="TagHelperAttribute"/> with the specified <paramref name="value"/> and removes any additional
|
||||
/// matching <see cref="TagHelperAttribute"/>s. If a matching <see cref="TagHelperAttribute"/> is not found,
|
||||
/// adds the specified <paramref name="value"/> to the end of the collection.
|
||||
/// </summary>
|
||||
/// <param name="name">
|
||||
/// The <see cref="TagHelperAttribute.Name"/> of the <see cref="TagHelperAttribute"/> to get or set.
|
||||
/// </param>
|
||||
/// <returns>The first <see cref="TagHelperAttribute"/> with <see cref="TagHelperAttribute.Name"/> matching
|
||||
/// <paramref name="name"/>.
|
||||
/// </returns>
|
||||
/// <remarks><paramref name="name"/> is compared case-insensitively. When setting,
|
||||
/// <see cref="TagHelperAttribute"/>s <see cref="TagHelperAttribute.Name"/> must be <c>null</c> or
|
||||
/// case-insensitively match the specified <paramref name="name"/>.</remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var attributes = new TagHelperAttributeList();
|
||||
///
|
||||
/// // Will "value" be converted to a TagHelperAttribute with a null Name
|
||||
/// attributes["name"] = "value";
|
||||
///
|
||||
/// // TagHelperAttribute.Name must match the specified name.
|
||||
/// attributes["name"] = new TagHelperAttribute("name", "value");
|
||||
/// </code>
|
||||
/// </example>
|
||||
public new TagHelperAttribute this[string name]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
return base[name];
|
||||
}
|
||||
set
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
// Name will be null if user attempts to set the attribute via an implicit conversion:
|
||||
// output.Attributes["someName"] = "someValue"
|
||||
if (value.Name == null)
|
||||
{
|
||||
value.Name = name;
|
||||
}
|
||||
else if (!NameEquals(name, value))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
Resources.FormatTagHelperAttributeList_CannotAddAttribute(
|
||||
nameof(TagHelperAttribute),
|
||||
nameof(TagHelperAttribute.Name),
|
||||
value.Name,
|
||||
name),
|
||||
nameof(name));
|
||||
}
|
||||
|
||||
var attributeReplaced = false;
|
||||
|
||||
for (var i = 0; i < Attributes.Count; i++)
|
||||
{
|
||||
if (NameEquals(name, Attributes[i]))
|
||||
{
|
||||
// We replace the first attribute with the provided value, remove all the rest.
|
||||
if (!attributeReplaced)
|
||||
{
|
||||
// We replace the first attribute we find with the same name.
|
||||
Attributes[i] = value;
|
||||
attributeReplaced = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Attributes.RemoveAt(i--);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't replace an attribute value we should add value to the end of the collection.
|
||||
if (!attributeReplaced)
|
||||
{
|
||||
Add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
bool ICollection<TagHelperAttribute>.IsReadOnly
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="TagHelperAttribute"/> to the end of the collection with the specified
|
||||
/// <paramref name="name"/> and <paramref name="value"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The <see cref="TagHelperAttribute.Name"/> of the attribute to add.</param>
|
||||
/// <param name="value">The <see cref="TagHelperAttribute.Value"/> of the attribute to add.</param>
|
||||
public void Add(string name, object value)
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
Attributes.Add(new TagHelperAttribute(name, value));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
/// <paramref name="attribute"/>'s <see cref="TagHelperAttribute.Name"/> must not be <c>null</c>.
|
||||
/// </remarks>
|
||||
public void Add(TagHelperAttribute attribute)
|
||||
{
|
||||
if (attribute == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(attribute));
|
||||
}
|
||||
|
||||
if (attribute.Name == null)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
Resources.FormatTagHelperAttributeList_CannotAddWithNullName(
|
||||
typeof(TagHelperAttribute).FullName,
|
||||
nameof(TagHelperAttribute.Name)),
|
||||
nameof(attribute));
|
||||
}
|
||||
|
||||
Attributes.Add(attribute);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
/// <paramref name="attribute"/>'s <see cref="TagHelperAttribute.Name"/> must not be <c>null</c>.
|
||||
/// </remarks>
|
||||
public void Insert(int index, TagHelperAttribute attribute)
|
||||
{
|
||||
if (attribute == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(attribute));
|
||||
}
|
||||
|
||||
if (attribute.Name == null)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
Resources.FormatTagHelperAttributeList_CannotAddWithNullName(
|
||||
typeof(TagHelperAttribute).FullName,
|
||||
nameof(TagHelperAttribute.Name)),
|
||||
nameof(attribute));
|
||||
}
|
||||
|
||||
Attributes.Insert(index, attribute);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CopyTo(TagHelperAttribute[] array, int index)
|
||||
{
|
||||
if (array == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(array));
|
||||
}
|
||||
|
||||
Attributes.CopyTo(array, index);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
/// <paramref name="attribute"/>s <see cref="TagHelperAttribute.Name"/> is compared case-insensitively.
|
||||
/// </remarks>
|
||||
public bool Remove(TagHelperAttribute attribute)
|
||||
{
|
||||
if (attribute == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(attribute));
|
||||
}
|
||||
|
||||
return Attributes.Remove(attribute);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
Attributes.RemoveAt(index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all <see cref="TagHelperAttribute"/>s with <see cref="TagHelperAttribute.Name"/> matching
|
||||
/// <paramref name="name"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">
|
||||
/// The <see cref="TagHelperAttribute.Name"/> of <see cref="TagHelperAttribute"/>s to remove.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if at least 1 <see cref="TagHelperAttribute"/> was removed; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <remarks><paramref name="name"/> is compared case-insensitively.</remarks>
|
||||
public bool RemoveAll(string name)
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
return Attributes.RemoveAll(attribute => NameEquals(name, attribute)) > 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Clear()
|
||||
{
|
||||
Attributes.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.AspNet.Html.Abstractions;
|
||||
using Microsoft.Extensions.WebEncoders;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract class used to buffer content returned by <see cref="ITagHelper"/>s.
|
||||
/// </summary>
|
||||
public abstract class TagHelperContent : IHtmlContentBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the content was modifed.
|
||||
/// </summary>
|
||||
public abstract bool IsModified { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the content is empty.
|
||||
/// </summary>
|
||||
public abstract bool IsEmpty { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the content is whitespace.
|
||||
/// </summary>
|
||||
public abstract bool IsWhiteSpace { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the content.
|
||||
/// </summary>
|
||||
/// <param name="htmlContent">The <see cref="IHtmlContent"/> that replaces the content.</param>
|
||||
/// <returns>A reference to this instance after the set operation has completed.</returns>
|
||||
public TagHelperContent SetContent(IHtmlContent htmlContent)
|
||||
{
|
||||
HtmlContentBuilderExtensions.SetContent(this, htmlContent);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the content.
|
||||
/// </summary>
|
||||
/// <param name="unencoded">
|
||||
/// The <see cref="string"/> that replaces the content. The value is assume to be unencoded
|
||||
/// as-provided and will be HTML encoded before being written.
|
||||
/// </param>
|
||||
/// <returns>A reference to this instance after the set operation has completed.</returns>
|
||||
public TagHelperContent SetContent(string unencoded)
|
||||
{
|
||||
HtmlContentBuilderExtensions.SetContent(this, unencoded);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the content.
|
||||
/// </summary>
|
||||
/// <param name="encoded">
|
||||
/// The <see cref="string"/> that replaces the content. The value is assume to be HTML encoded
|
||||
/// as-provided and no further encoding will be performed.
|
||||
/// </param>
|
||||
/// <returns>A reference to this instance after the set operation has completed.</returns>
|
||||
public TagHelperContent SetHtmlContent(string encoded)
|
||||
{
|
||||
HtmlContentBuilderExtensions.SetHtmlContent(this, encoded);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends <paramref name="unencoded"/> to the existing content.
|
||||
/// </summary>
|
||||
/// <param name="unencoded">The <see cref="string"/> to be appended.</param>
|
||||
/// <returns>A reference to this instance after the append operation has completed.</returns>
|
||||
public abstract TagHelperContent Append(string unencoded);
|
||||
|
||||
/// <summary>
|
||||
/// Appends <paramref name="htmlContent"/> to the existing content.
|
||||
/// </summary>
|
||||
/// <param name="htmlContent">The <see cref="IHtmlContent"/> to be appended.</param>
|
||||
/// <returns>A reference to this instance after the append operation has completed.</returns>
|
||||
public abstract TagHelperContent Append(IHtmlContent htmlContent);
|
||||
|
||||
/// <summary>
|
||||
/// Appends <paramref name="encoded"/> to the existing content. <paramref name="encoded"/> is assumed
|
||||
/// to be an HTML encoded <see cref="string"/> and no further encoding will be performed.
|
||||
/// </summary>
|
||||
/// <param name="encoded">The <see cref="string"/> to be appended.</param>
|
||||
/// <returns>A reference to this instance after the append operation has completed.</returns>
|
||||
public abstract TagHelperContent AppendHtml(string encoded);
|
||||
|
||||
/// <summary>
|
||||
/// Appends the specified <paramref name="format"/> to the existing content after
|
||||
/// replacing each format item with the HTML encoded <see cref="string"/> representation of the
|
||||
/// corresponding item in the <paramref name="args"/> array.
|
||||
/// </summary>
|
||||
/// <param name="format">
|
||||
/// The composite format <see cref="string"/> (see http://msdn.microsoft.com/en-us/library/txafckwd.aspx).
|
||||
/// </param>
|
||||
/// <param name="args">The object array to format.</param>
|
||||
/// <returns>A reference to this instance after the append operation has completed.</returns>
|
||||
public TagHelperContent AppendFormat(string format, params object[] args)
|
||||
{
|
||||
HtmlContentBuilderExtensions.AppendFormat(this, null, format, args);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends the specified <paramref name="format"/> to the existing content with information from the
|
||||
/// <paramref name="provider"/> after replacing each format item with the HTML encoded <see cref="string"/>
|
||||
/// representation of the corresponding item in the <paramref name="args"/> array.
|
||||
/// </summary>
|
||||
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
|
||||
/// <param name="format">
|
||||
/// The composite format <see cref="string"/> (see http://msdn.microsoft.com/en-us/library/txafckwd.aspx).
|
||||
/// </param>
|
||||
/// <param name="args">The object array to format.</param>
|
||||
/// <returns>A reference to this instance after the append operation has completed.</returns>
|
||||
public TagHelperContent AppendFormat(IFormatProvider provider, string format, params object[] args)
|
||||
{
|
||||
HtmlContentBuilderExtensions.AppendFormat(this, provider, format, args);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the content.
|
||||
/// </summary>
|
||||
/// <returns>A reference to this instance after the clear operation has completed.</returns>
|
||||
public abstract TagHelperContent Clear();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="string"/> containing the content.</returns>
|
||||
public abstract string GetContent();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content.
|
||||
/// </summary>
|
||||
/// <param name="encoder">The <see cref="IHtmlEncoder"/>.</param>
|
||||
/// <returns>A <see cref="string"/> containing the content.</returns>
|
||||
public abstract string GetContent(IHtmlEncoder encoder);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract void WriteTo(TextWriter writer, IHtmlEncoder encoder);
|
||||
|
||||
/// <inheritdoc />
|
||||
IHtmlContentBuilder IHtmlContentBuilder.Append(IHtmlContent content)
|
||||
{
|
||||
return Append(content);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
IHtmlContentBuilder IHtmlContentBuilder.Append(string unencoded)
|
||||
{
|
||||
return Append(unencoded);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
IHtmlContentBuilder IHtmlContentBuilder.AppendHtml(string encoded)
|
||||
{
|
||||
return AppendHtml(encoded);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
IHtmlContentBuilder IHtmlContentBuilder.Clear()
|
||||
{
|
||||
return Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains information related to the execution of <see cref="ITagHelper"/>s.
|
||||
/// </summary>
|
||||
public class TagHelperContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="TagHelperContext"/>.
|
||||
/// </summary>
|
||||
/// <param name="allAttributes">Every attribute associated with the current HTML element.</param>
|
||||
/// <param name="items">Collection of items used to communicate with other <see cref="ITagHelper"/>s.</param>
|
||||
/// <param name="uniqueId">The unique identifier for the source element this <see cref="TagHelperContext" />
|
||||
/// applies to.</param>
|
||||
public TagHelperContext(
|
||||
IEnumerable<IReadOnlyTagHelperAttribute> allAttributes,
|
||||
IDictionary<object, object> items,
|
||||
string uniqueId)
|
||||
{
|
||||
if (allAttributes == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(allAttributes));
|
||||
}
|
||||
|
||||
if (items == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(items));
|
||||
}
|
||||
|
||||
if (uniqueId == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(uniqueId));
|
||||
}
|
||||
|
||||
AllAttributes = new ReadOnlyTagHelperAttributeList<IReadOnlyTagHelperAttribute>(
|
||||
allAttributes.Select(attribute => new TagHelperAttribute(attribute.Name, attribute.Value)));
|
||||
Items = items;
|
||||
UniqueId = uniqueId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Every attribute associated with the current HTML element.
|
||||
/// </summary>
|
||||
public ReadOnlyTagHelperAttributeList<IReadOnlyTagHelperAttribute> AllAttributes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of items used to communicate with other <see cref="ITagHelper"/>s.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This <see cref="IDictionary{object, object}"/> is copy-on-write in order to ensure items added to this
|
||||
/// collection are visible only to other <see cref="ITagHelper"/>s targeting child elements.
|
||||
/// </remarks>
|
||||
public IDictionary<object, object> Items { get; }
|
||||
|
||||
/// <summary>
|
||||
/// An identifier unique to the HTML element this context is for.
|
||||
/// </summary>
|
||||
public string UniqueId { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Class used to represent the output of an <see cref="ITagHelper"/>.
|
||||
/// </summary>
|
||||
public class TagHelperOutput
|
||||
{
|
||||
private readonly Func<bool, Task<TagHelperContent>> _getChildContentAsync;
|
||||
|
||||
// Internal for testing
|
||||
internal TagHelperOutput(string tagName)
|
||||
: this(
|
||||
tagName,
|
||||
new TagHelperAttributeList(),
|
||||
(cachedResult) => Task.FromResult<TagHelperContent>(new DefaultTagHelperContent()))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new instance of <see cref="TagHelperOutput"/>.
|
||||
/// </summary>
|
||||
/// <param name="tagName">The HTML element's tag name.</param>
|
||||
/// <param name="attributes">The HTML attributes.</param>
|
||||
/// <param name="getChildContentAsync">A delegate used to execute and retrieve the rendered child content
|
||||
/// asynchronously.</param>
|
||||
public TagHelperOutput(
|
||||
string tagName,
|
||||
TagHelperAttributeList attributes,
|
||||
Func<bool, Task<TagHelperContent>> getChildContentAsync)
|
||||
{
|
||||
if (attributes == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(attributes));
|
||||
}
|
||||
|
||||
if (getChildContentAsync == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(getChildContentAsync));
|
||||
}
|
||||
|
||||
TagName = tagName;
|
||||
Attributes = new TagHelperAttributeList(attributes);
|
||||
_getChildContentAsync = getChildContentAsync;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The HTML element's tag name.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A whitespace or <c>null</c> value results in no start or end tag being rendered.
|
||||
/// </remarks>
|
||||
public string TagName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Content that precedes the HTML element.
|
||||
/// </summary>
|
||||
/// <remarks>Value is rendered before the HTML element.</remarks>
|
||||
public TagHelperContent PreElement { get; } = new DefaultTagHelperContent();
|
||||
|
||||
/// <summary>
|
||||
/// The HTML element's pre content.
|
||||
/// </summary>
|
||||
/// <remarks>Value is prepended to the <see cref="ITagHelper"/>'s final output.</remarks>
|
||||
public TagHelperContent PreContent { get; } = new DefaultTagHelperContent();
|
||||
|
||||
/// <summary>
|
||||
/// The HTML element's main content.
|
||||
/// </summary>
|
||||
/// <remarks>Value occurs in the <see cref="ITagHelper"/>'s final output after <see cref="PreContent"/> and
|
||||
/// before <see cref="PostContent"/></remarks>
|
||||
public TagHelperContent Content { get; } = new DefaultTagHelperContent();
|
||||
|
||||
/// <summary>
|
||||
/// The HTML element's post content.
|
||||
/// </summary>
|
||||
/// <remarks>Value is appended to the <see cref="ITagHelper"/>'s final output.</remarks>
|
||||
public TagHelperContent PostContent { get; } = new DefaultTagHelperContent();
|
||||
|
||||
/// <summary>
|
||||
/// Content that follows the HTML element.
|
||||
/// </summary>
|
||||
/// <remarks>Value is rendered after the HTML element.</remarks>
|
||||
public TagHelperContent PostElement { get; } = new DefaultTagHelperContent();
|
||||
|
||||
/// <summary>
|
||||
/// <c>true</c> if <see cref="Content"/> has been set, <c>false</c> otherwise.
|
||||
/// </summary>
|
||||
public bool IsContentModified
|
||||
{
|
||||
get
|
||||
{
|
||||
return Content.IsModified;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Syntax of the element in the generated HTML.
|
||||
/// </summary>
|
||||
public TagMode TagMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The HTML element's attributes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// MVC will HTML encode <see cref="string"/> values when generating the start tag. It will not HTML encode
|
||||
/// a <c>Microsoft.AspNet.Mvc.Rendering.HtmlString</c> instance. MVC converts most other types to a
|
||||
/// <see cref="string"/>, then HTML encodes the result.
|
||||
/// </remarks>
|
||||
public TagHelperAttributeList Attributes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Changes <see cref="TagHelperOutput"/> to generate nothing.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Sets <see cref="TagName"/> to <c>null</c>, and clears <see cref="PreElement"/>, <see cref="PreContent"/>,
|
||||
/// <see cref="Content"/>, <see cref="PostContent"/>, and <see cref="PostElement"/> to suppress output.
|
||||
/// </remarks>
|
||||
public void SuppressOutput()
|
||||
{
|
||||
TagName = null;
|
||||
PreElement.Clear();
|
||||
PreContent.Clear();
|
||||
Content.Clear();
|
||||
PostContent.Clear();
|
||||
PostElement.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A delegate used to execute children asynchronously.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Task"/> that on completion returns content rendered by children.</returns>
|
||||
/// <remarks>This method is memoized.</remarks>
|
||||
public Task<TagHelperContent> GetChildContentAsync()
|
||||
{
|
||||
return GetChildContentAsync(useCachedResult: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A delegate used to execute children asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="useCachedResult">If <c>true</c> multiple calls to this method will not cause re-execution
|
||||
/// of child content; cached content will be returned.</param>
|
||||
/// <returns>A <see cref="Task"/> that on completion returns content rendered by children.</returns>
|
||||
public Task<TagHelperContent> GetChildContentAsync(bool useCachedResult)
|
||||
{
|
||||
return _getChildContentAsync(useCachedResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"description": "Runtime components for rendering Razor pages.",
|
||||
"version": "4.0.0-rc1-final",
|
||||
"compilationOptions": {
|
||||
"warningsAsErrors": true,
|
||||
"keyFile": "../../tools/Key.snk"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/aspnet/razor"
|
||||
},
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.Html.Abstractions": "1.0.0-rc1-final",
|
||||
"Microsoft.AspNet.Razor.VSRC1": "4.0.0-rc1-final"
|
||||
},
|
||||
"frameworks": {
|
||||
"net451": {
|
||||
"frameworkAssemblies": {
|
||||
"System.Xml": "4.0.0.0",
|
||||
"System.Xml.Linq": "4.0.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Razor.Chunks.Generators;
|
||||
using Microsoft.AspNet.Razor.CodeGenerators;
|
||||
using Microsoft.AspNet.Razor.Parser;
|
||||
#if NET451
|
||||
|
||||
#endif
|
||||
|
||||
namespace Microsoft.AspNet.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the C# Code Language for Razor
|
||||
/// </summary>
|
||||
public class CSharpRazorCodeLanguage : RazorCodeLanguage
|
||||
{
|
||||
private const string CSharpLanguageName = "csharp";
|
||||
|
||||
/// <summary>
|
||||
/// Returns the name of the language: "csharp"
|
||||
/// </summary>
|
||||
public override string LanguageName
|
||||
{
|
||||
get { return CSharpLanguageName; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of the code parser for this language
|
||||
/// </summary>
|
||||
public override ParserBase CreateCodeParser()
|
||||
{
|
||||
return new CSharpCodeParser();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of the chunk generator for this language with the specified settings
|
||||
/// </summary>
|
||||
public override RazorChunkGenerator CreateChunkGenerator(
|
||||
string className,
|
||||
string rootNamespaceName,
|
||||
string sourceFileName,
|
||||
RazorEngineHost host)
|
||||
{
|
||||
return new RazorChunkGenerator(className, rootNamespaceName, sourceFileName, host);
|
||||
}
|
||||
|
||||
public override CodeGenerator CreateCodeGenerator(CodeGeneratorContext context)
|
||||
{
|
||||
return new CSharpCodeGenerator(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="Chunk"/> used to look up <see cref="Compilation.TagHelpers.TagHelperDescriptor"/>s.
|
||||
/// </summary>
|
||||
public class AddTagHelperChunk : Chunk
|
||||
{
|
||||
/// <summary>
|
||||
/// Text used to look up <see cref="Compilation.TagHelpers.TagHelperDescriptor"/>s.
|
||||
/// </summary>
|
||||
public string LookupText { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks
|
||||
{
|
||||
public class Chunk
|
||||
{
|
||||
public SourceLocation Start { get; set; }
|
||||
public SyntaxTreeNode Association { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks
|
||||
{
|
||||
public class ChunkTree
|
||||
{
|
||||
public ChunkTree()
|
||||
{
|
||||
Chunks = new List<Chunk>();
|
||||
}
|
||||
|
||||
public IList<Chunk> Chunks { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks
|
||||
{
|
||||
public class ChunkTreeBuilder
|
||||
{
|
||||
private readonly Stack<ParentChunk> _parentStack;
|
||||
private Chunk _lastChunk;
|
||||
|
||||
public ChunkTreeBuilder()
|
||||
{
|
||||
ChunkTree = new ChunkTree();
|
||||
_parentStack = new Stack<ParentChunk>();
|
||||
}
|
||||
|
||||
public ChunkTree ChunkTree { get; private set; }
|
||||
|
||||
public void AddChunk(Chunk chunk, SyntaxTreeNode association, bool topLevel = false)
|
||||
{
|
||||
_lastChunk = chunk;
|
||||
|
||||
chunk.Start = association.Start;
|
||||
chunk.Association = association;
|
||||
|
||||
// If we're not in the middle of a parent chunk
|
||||
if (_parentStack.Count == 0 || topLevel == true)
|
||||
{
|
||||
ChunkTree.Chunks.Add(chunk);
|
||||
}
|
||||
else
|
||||
{
|
||||
_parentStack.Peek().Children.Add(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddTagHelperPrefixDirectiveChunk(string prefix, SyntaxTreeNode association)
|
||||
{
|
||||
AddChunk(
|
||||
new TagHelperPrefixDirectiveChunk
|
||||
{
|
||||
Prefix = prefix
|
||||
},
|
||||
association,
|
||||
topLevel: true);
|
||||
}
|
||||
|
||||
public void AddAddTagHelperChunk(string lookupText, SyntaxTreeNode association)
|
||||
{
|
||||
AddChunk(new AddTagHelperChunk
|
||||
{
|
||||
LookupText = lookupText
|
||||
}, association, topLevel: true);
|
||||
}
|
||||
|
||||
public void AddRemoveTagHelperChunk(string lookupText, SyntaxTreeNode association)
|
||||
{
|
||||
AddChunk(new RemoveTagHelperChunk
|
||||
{
|
||||
LookupText = lookupText
|
||||
}, association, topLevel: true);
|
||||
}
|
||||
|
||||
public void AddLiteralChunk(string literal, SyntaxTreeNode association)
|
||||
{
|
||||
// If the previous chunk was also a LiteralChunk, append the content of the current node to the previous one.
|
||||
var literalChunk = _lastChunk as LiteralChunk;
|
||||
if (literalChunk != null)
|
||||
{
|
||||
// Literal chunks are always associated with Spans
|
||||
var lastSpan = (Span)literalChunk.Association;
|
||||
var currentSpan = (Span)association;
|
||||
|
||||
var builder = new SpanBuilder(lastSpan);
|
||||
foreach (var symbol in currentSpan.Symbols)
|
||||
{
|
||||
builder.Accept(symbol);
|
||||
}
|
||||
|
||||
literalChunk.Association = builder.Build();
|
||||
literalChunk.Text += literal;
|
||||
}
|
||||
else
|
||||
{
|
||||
AddChunk(new LiteralChunk
|
||||
{
|
||||
Text = literal,
|
||||
}, association);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddExpressionChunk(string expression, SyntaxTreeNode association)
|
||||
{
|
||||
AddChunk(new ExpressionChunk
|
||||
{
|
||||
Code = expression
|
||||
}, association);
|
||||
}
|
||||
|
||||
public void AddStatementChunk(string code, SyntaxTreeNode association)
|
||||
{
|
||||
AddChunk(new StatementChunk
|
||||
{
|
||||
Code = code,
|
||||
}, association);
|
||||
}
|
||||
|
||||
public void AddUsingChunk(string usingNamespace, SyntaxTreeNode association)
|
||||
{
|
||||
AddChunk(new UsingChunk
|
||||
{
|
||||
Namespace = usingNamespace,
|
||||
}, association, topLevel: true);
|
||||
}
|
||||
|
||||
public void AddTypeMemberChunk(string code, SyntaxTreeNode association)
|
||||
{
|
||||
AddChunk(new TypeMemberChunk
|
||||
{
|
||||
Code = code,
|
||||
}, association, topLevel: true);
|
||||
}
|
||||
|
||||
public void AddLiteralCodeAttributeChunk(string code, SyntaxTreeNode association)
|
||||
{
|
||||
AddChunk(new LiteralCodeAttributeChunk
|
||||
{
|
||||
Code = code,
|
||||
}, association);
|
||||
}
|
||||
|
||||
public void AddSetBaseTypeChunk(string typeName, SyntaxTreeNode association)
|
||||
{
|
||||
AddChunk(new SetBaseTypeChunk
|
||||
{
|
||||
TypeName = typeName.Trim()
|
||||
}, association, topLevel: true);
|
||||
}
|
||||
|
||||
public T StartParentChunk<T>(SyntaxTreeNode association) where T : ParentChunk, new()
|
||||
{
|
||||
return StartParentChunk<T>(association, topLevel: false);
|
||||
}
|
||||
|
||||
public T StartParentChunk<T>(SyntaxTreeNode association, bool topLevel) where T : ParentChunk, new()
|
||||
{
|
||||
var parentChunk = new T();
|
||||
|
||||
return StartParentChunk<T>(parentChunk, association, topLevel);
|
||||
}
|
||||
|
||||
public T StartParentChunk<T>(T parentChunk, SyntaxTreeNode association, bool topLevel) where T : ParentChunk
|
||||
{
|
||||
AddChunk(parentChunk, association, topLevel);
|
||||
|
||||
_parentStack.Push(parentChunk);
|
||||
|
||||
return parentChunk;
|
||||
}
|
||||
|
||||
public void EndParentChunk()
|
||||
{
|
||||
_lastChunk = _parentStack.Pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Razor.Text;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks
|
||||
{
|
||||
public class CodeAttributeChunk : ParentChunk
|
||||
{
|
||||
public string Attribute { get; set; }
|
||||
public LocationTagged<string> Prefix { get; set; }
|
||||
public LocationTagged<string> Suffix { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Razor.Text;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks
|
||||
{
|
||||
public class DynamicCodeAttributeChunk : ParentChunk
|
||||
{
|
||||
public LocationTagged<string> Prefix { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks
|
||||
{
|
||||
public class ExpressionBlockChunk : ParentChunk
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks
|
||||
{
|
||||
public class ExpressionChunk : Chunk
|
||||
{
|
||||
public string Code { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Start + " = " + Code;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||
{
|
||||
public class AddImportChunkGenerator : SpanChunkGenerator
|
||||
{
|
||||
public AddImportChunkGenerator(string ns)
|
||||
{
|
||||
Namespace = ns;
|
||||
}
|
||||
|
||||
public string Namespace { get; }
|
||||
|
||||
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
|
||||
{
|
||||
var ns = Namespace;
|
||||
|
||||
if (!string.IsNullOrEmpty(ns) && char.IsWhiteSpace(ns[0]))
|
||||
{
|
||||
ns = ns.Substring(1);
|
||||
}
|
||||
|
||||
context.ChunkTreeBuilder.AddUsingChunk(ns, target);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Import:" + Namespace + ";";
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var other = obj as AddImportChunkGenerator;
|
||||
return other != null &&
|
||||
string.Equals(Namespace, other.Namespace, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
// Hash code should include only immutable properties.
|
||||
return Namespace == null ? 0 : StringComparer.Ordinal.GetHashCode(Namespace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="SpanChunkGenerator"/> responsible for generating <see cref="AddTagHelperChunk"/>s and
|
||||
/// <see cref="RemoveTagHelperChunk"/>s.
|
||||
/// </summary>
|
||||
public class AddOrRemoveTagHelperChunkGenerator : SpanChunkGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="AddOrRemoveTagHelperChunkGenerator"/>.
|
||||
/// </summary>
|
||||
/// <param name="lookupText">
|
||||
/// Text used to look up <see cref="Compilation.TagHelpers.TagHelperDescriptor"/>s that should be added or removed.
|
||||
/// </param>
|
||||
public AddOrRemoveTagHelperChunkGenerator(bool removeTagHelperDescriptors, string lookupText)
|
||||
{
|
||||
RemoveTagHelperDescriptors = removeTagHelperDescriptors;
|
||||
LookupText = lookupText;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the text used to look up <see cref="Compilation.TagHelpers.TagHelperDescriptor"/>s that should be added to or
|
||||
/// removed from the Razor page.
|
||||
/// </summary>
|
||||
public string LookupText { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether we want to remove <see cref="Compilation.TagHelpers.TagHelperDescriptor"/>s from the Razor page.
|
||||
/// </summary>
|
||||
/// <remarks>If <c>true</c> <see cref="GenerateChunk"/> generates <see cref="AddTagHelperChunk"/>s,
|
||||
/// <see cref="RemoveTagHelperChunk"/>s otherwise.</remarks>
|
||||
public bool RemoveTagHelperDescriptors { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Generates <see cref="AddTagHelperChunk"/>s if <see cref="RemoveTagHelperDescriptors"/> is
|
||||
/// <c>true</c>, otherwise <see cref="RemoveTagHelperChunk"/>s are generated.
|
||||
/// </summary>
|
||||
/// <param name="target">
|
||||
/// The <see cref="Span"/> responsible for this <see cref="AddOrRemoveTagHelperChunkGenerator"/>.
|
||||
/// </param>
|
||||
/// <param name="context">A <see cref="ChunkGeneratorContext"/> instance that contains information about
|
||||
/// the current chunk generation process.</param>
|
||||
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
|
||||
{
|
||||
if (RemoveTagHelperDescriptors)
|
||||
{
|
||||
context.ChunkTreeBuilder.AddRemoveTagHelperChunk(LookupText, target);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.ChunkTreeBuilder.AddAddTagHelperChunk(LookupText, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
using Microsoft.AspNet.Razor.Text;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||
{
|
||||
public class AttributeBlockChunkGenerator : ParentChunkGenerator
|
||||
{
|
||||
public AttributeBlockChunkGenerator(string name, LocationTagged<string> prefix, LocationTagged<string> suffix)
|
||||
{
|
||||
Name = name;
|
||||
Prefix = prefix;
|
||||
Suffix = suffix;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public LocationTagged<string> Prefix { get; }
|
||||
|
||||
public LocationTagged<string> Suffix { get; }
|
||||
|
||||
public override void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
|
||||
{
|
||||
var chunk = context.ChunkTreeBuilder.StartParentChunk<CodeAttributeChunk>(target);
|
||||
|
||||
chunk.Attribute = Name;
|
||||
chunk.Prefix = Prefix;
|
||||
chunk.Suffix = Suffix;
|
||||
}
|
||||
|
||||
public override void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
|
||||
{
|
||||
context.ChunkTreeBuilder.EndParentChunk();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, "Attr:{0},{1:F},{2:F}", Name, Prefix, Suffix);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var other = obj as AttributeBlockChunkGenerator;
|
||||
return other != null &&
|
||||
string.Equals(other.Name, Name, StringComparison.Ordinal) &&
|
||||
Equals(other.Prefix, Prefix) &&
|
||||
Equals(other.Suffix, Suffix);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hashCodeCombiner = HashCodeCombiner.Start();
|
||||
hashCodeCombiner.Add(Name, StringComparer.Ordinal);
|
||||
hashCodeCombiner.Add(Prefix);
|
||||
hashCodeCombiner.Add(Suffix);
|
||||
|
||||
return hashCodeCombiner;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||
{
|
||||
public class ChunkGeneratorContext
|
||||
{
|
||||
protected ChunkGeneratorContext(ChunkGeneratorContext context)
|
||||
: this(
|
||||
context.Host,
|
||||
context.ClassName,
|
||||
context.RootNamespace,
|
||||
context.SourceFile,
|
||||
// True because we're pulling from the provided context's source file.
|
||||
shouldGenerateLinePragmas: true)
|
||||
{
|
||||
ChunkTreeBuilder = context.ChunkTreeBuilder;
|
||||
}
|
||||
|
||||
public ChunkGeneratorContext(
|
||||
RazorEngineHost host,
|
||||
string className,
|
||||
string rootNamespace,
|
||||
string sourceFile,
|
||||
bool shouldGenerateLinePragmas)
|
||||
{
|
||||
ChunkTreeBuilder = new ChunkTreeBuilder();
|
||||
Host = host;
|
||||
SourceFile = shouldGenerateLinePragmas ? sourceFile : null;
|
||||
RootNamespace = rootNamespace;
|
||||
ClassName = className;
|
||||
}
|
||||
|
||||
public string SourceFile { get; internal set; }
|
||||
|
||||
public string RootNamespace { get; }
|
||||
|
||||
public string ClassName { get; }
|
||||
|
||||
public RazorEngineHost Host { get; }
|
||||
|
||||
public ChunkTreeBuilder ChunkTreeBuilder { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Globalization;
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
using Microsoft.AspNet.Razor.Text;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||
{
|
||||
public class DynamicAttributeBlockChunkGenerator : ParentChunkGenerator
|
||||
{
|
||||
public DynamicAttributeBlockChunkGenerator(LocationTagged<string> prefix, int offset, int line, int col)
|
||||
: this(prefix, new SourceLocation(offset, line, col))
|
||||
{
|
||||
}
|
||||
|
||||
public DynamicAttributeBlockChunkGenerator(LocationTagged<string> prefix, SourceLocation valueStart)
|
||||
{
|
||||
Prefix = prefix;
|
||||
ValueStart = valueStart;
|
||||
}
|
||||
|
||||
public LocationTagged<string> Prefix { get; }
|
||||
|
||||
public SourceLocation ValueStart { get; }
|
||||
|
||||
public override void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
|
||||
{
|
||||
var chunk = context.ChunkTreeBuilder.StartParentChunk<DynamicCodeAttributeChunk>(target);
|
||||
chunk.Start = ValueStart;
|
||||
chunk.Prefix = Prefix;
|
||||
}
|
||||
|
||||
public override void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
|
||||
{
|
||||
context.ChunkTreeBuilder.EndParentChunk();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, "DynAttr:{0:F}", Prefix);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var other = obj as DynamicAttributeBlockChunkGenerator;
|
||||
return other != null &&
|
||||
Equals(other.Prefix, Prefix);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Prefix == null ? 0 : Prefix.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||
{
|
||||
public class ExpressionChunkGenerator : ISpanChunkGenerator, IParentChunkGenerator
|
||||
{
|
||||
private static readonly int TypeHashCode = typeof(ExpressionChunkGenerator).GetHashCode();
|
||||
|
||||
public void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
|
||||
{
|
||||
context.ChunkTreeBuilder.StartParentChunk<ExpressionBlockChunk>(target);
|
||||
}
|
||||
|
||||
public void GenerateChunk(Span target, ChunkGeneratorContext context)
|
||||
{
|
||||
context.ChunkTreeBuilder.AddExpressionChunk(target.Content, target);
|
||||
}
|
||||
|
||||
public void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
|
||||
{
|
||||
context.ChunkTreeBuilder.EndParentChunk();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Expr";
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj != null &&
|
||||
GetType() == obj.GetType();
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return TypeHashCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||
{
|
||||
public interface IParentChunkGenerator
|
||||
{
|
||||
void GenerateStartParentChunk(Block target, ChunkGeneratorContext context);
|
||||
void GenerateEndParentChunk(Block target, ChunkGeneratorContext context);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||
{
|
||||
public interface ISpanChunkGenerator
|
||||
{
|
||||
void GenerateChunk(Span target, ChunkGeneratorContext context);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Globalization;
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
using Microsoft.AspNet.Razor.Text;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||
{
|
||||
public class LiteralAttributeChunkGenerator : SpanChunkGenerator
|
||||
{
|
||||
public LiteralAttributeChunkGenerator(
|
||||
LocationTagged<string> prefix,
|
||||
LocationTagged<SpanChunkGenerator> valueGenerator)
|
||||
{
|
||||
Prefix = prefix;
|
||||
ValueGenerator = valueGenerator;
|
||||
}
|
||||
|
||||
public LiteralAttributeChunkGenerator(LocationTagged<string> prefix, LocationTagged<string> value)
|
||||
{
|
||||
Prefix = prefix;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public LocationTagged<string> Prefix { get; }
|
||||
|
||||
public LocationTagged<string> Value { get; }
|
||||
|
||||
public LocationTagged<SpanChunkGenerator> ValueGenerator { get; }
|
||||
|
||||
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
|
||||
{
|
||||
var chunk = context.ChunkTreeBuilder.StartParentChunk<LiteralCodeAttributeChunk>(target);
|
||||
chunk.Prefix = Prefix;
|
||||
chunk.Value = Value;
|
||||
|
||||
if (ValueGenerator != null)
|
||||
{
|
||||
chunk.ValueLocation = ValueGenerator.Location;
|
||||
|
||||
ValueGenerator.Value.GenerateChunk(target, context);
|
||||
|
||||
chunk.ValueLocation = ValueGenerator.Location;
|
||||
}
|
||||
|
||||
context.ChunkTreeBuilder.EndParentChunk();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (ValueGenerator == null)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, "LitAttr:{0:F},{1:F}", Prefix, Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, "LitAttr:{0:F},<Sub:{1:F}>", Prefix, ValueGenerator);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var other = obj as LiteralAttributeChunkGenerator;
|
||||
return other != null &&
|
||||
Equals(other.Prefix, Prefix) &&
|
||||
Equals(other.Value, Value) &&
|
||||
Equals(other.ValueGenerator, ValueGenerator);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hashCodeCombiner = HashCodeCombiner.Start();
|
||||
|
||||
hashCodeCombiner.Add(Prefix);
|
||||
hashCodeCombiner.Add(Value);
|
||||
hashCodeCombiner.Add(ValueGenerator);
|
||||
|
||||
return hashCodeCombiner;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||
{
|
||||
public class MarkupChunkGenerator : SpanChunkGenerator
|
||||
{
|
||||
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
|
||||
{
|
||||
context.ChunkTreeBuilder.AddLiteralChunk(target.Content, target);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Markup";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||
{
|
||||
public abstract class ParentChunkGenerator : IParentChunkGenerator
|
||||
{
|
||||
private static readonly int TypeHashCode = typeof(ParentChunkGenerator).GetHashCode();
|
||||
|
||||
[SuppressMessage(
|
||||
"Microsoft.Security",
|
||||
"CA2104:DoNotDeclareReadOnlyMutableReferenceTypes",
|
||||
Justification = "This class has no instance state")]
|
||||
public static readonly IParentChunkGenerator Null = new NullParentChunkGenerator();
|
||||
|
||||
public virtual void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj != null &&
|
||||
GetType() == obj.GetType();
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return TypeHashCode;
|
||||
}
|
||||
|
||||
private class NullParentChunkGenerator : IParentChunkGenerator
|
||||
{
|
||||
public void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
|
||||
{
|
||||
}
|
||||
|
||||
public void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
|
||||
{
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "None";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNet.Razor.Parser;
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||
{
|
||||
public class RazorChunkGenerator : ParserVisitor
|
||||
{
|
||||
private ChunkGeneratorContext _context;
|
||||
|
||||
public RazorChunkGenerator(
|
||||
string className,
|
||||
string rootNamespaceName,
|
||||
string sourceFileName,
|
||||
RazorEngineHost host)
|
||||
{
|
||||
if (rootNamespaceName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(rootNamespaceName));
|
||||
}
|
||||
|
||||
if (host == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(host));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(className))
|
||||
{
|
||||
throw new ArgumentException(CommonResources.Argument_Cannot_Be_Null_Or_Empty, nameof(className));
|
||||
}
|
||||
|
||||
ClassName = className;
|
||||
RootNamespaceName = rootNamespaceName;
|
||||
SourceFileName = sourceFileName;
|
||||
GenerateLinePragmas = string.IsNullOrEmpty(SourceFileName) ? false : true;
|
||||
Host = host;
|
||||
}
|
||||
|
||||
// Data pulled from constructor
|
||||
public string ClassName { get; private set; }
|
||||
public string RootNamespaceName { get; private set; }
|
||||
public string SourceFileName { get; private set; }
|
||||
public RazorEngineHost Host { get; private set; }
|
||||
|
||||
// Generation settings
|
||||
public bool GenerateLinePragmas { get; set; }
|
||||
public bool DesignTimeMode { get; set; }
|
||||
|
||||
public ChunkGeneratorContext Context
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureContextInitialized();
|
||||
return _context;
|
||||
}
|
||||
}
|
||||
|
||||
public override void VisitStartBlock(Block block)
|
||||
{
|
||||
block.ChunkGenerator.GenerateStartParentChunk(block, Context);
|
||||
}
|
||||
|
||||
public override void VisitEndBlock(Block block)
|
||||
{
|
||||
block.ChunkGenerator.GenerateEndParentChunk(block, Context);
|
||||
}
|
||||
|
||||
public override void VisitSpan(Span span)
|
||||
{
|
||||
span.ChunkGenerator.GenerateChunk(span, Context);
|
||||
}
|
||||
|
||||
private void EnsureContextInitialized()
|
||||
{
|
||||
if (_context == null)
|
||||
{
|
||||
_context = new ChunkGeneratorContext(Host,
|
||||
ClassName,
|
||||
RootNamespaceName,
|
||||
SourceFileName,
|
||||
GenerateLinePragmas);
|
||||
Initialize(_context);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Initialize(ChunkGeneratorContext context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||
{
|
||||
public class RazorCommentChunkGenerator : ParentChunkGenerator
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||
{
|
||||
public class SectionChunkGenerator : ParentChunkGenerator
|
||||
{
|
||||
public SectionChunkGenerator(string sectionName)
|
||||
{
|
||||
SectionName = sectionName;
|
||||
}
|
||||
|
||||
public string SectionName { get; }
|
||||
|
||||
public override void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
|
||||
{
|
||||
var chunk = context.ChunkTreeBuilder.StartParentChunk<SectionChunk>(target);
|
||||
|
||||
chunk.Name = SectionName;
|
||||
}
|
||||
|
||||
public override void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
|
||||
{
|
||||
context.ChunkTreeBuilder.EndParentChunk();
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var other = obj as SectionChunkGenerator;
|
||||
return base.Equals(other) &&
|
||||
string.Equals(SectionName, other.SectionName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return SectionName == null ? 0 : StringComparer.Ordinal.GetHashCode(SectionName);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Section:" + SectionName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||
{
|
||||
public class SetBaseTypeChunkGenerator : SpanChunkGenerator
|
||||
{
|
||||
public SetBaseTypeChunkGenerator(string baseType)
|
||||
{
|
||||
BaseType = baseType;
|
||||
}
|
||||
|
||||
public string BaseType { get; }
|
||||
|
||||
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
|
||||
{
|
||||
context.ChunkTreeBuilder.AddSetBaseTypeChunk(BaseType, target);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Base:" + BaseType;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var other = obj as SetBaseTypeChunkGenerator;
|
||||
return other != null &&
|
||||
string.Equals(BaseType, other.BaseType, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return BaseType.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||
{
|
||||
public abstract class SpanChunkGenerator : ISpanChunkGenerator
|
||||
{
|
||||
private static readonly int TypeHashCode = typeof(SpanChunkGenerator).GetHashCode();
|
||||
|
||||
[SuppressMessage(
|
||||
"Microsoft.Security",
|
||||
"CA2104:DoNotDeclareReadOnlyMutableReferenceTypes",
|
||||
Justification = "This class has no instance state")]
|
||||
public static readonly ISpanChunkGenerator Null = new NullSpanChunkGenerator();
|
||||
|
||||
public virtual void GenerateChunk(Span target, ChunkGeneratorContext context)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj != null &&
|
||||
GetType() == obj.GetType();
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return TypeHashCode;
|
||||
}
|
||||
|
||||
private class NullSpanChunkGenerator : ISpanChunkGenerator
|
||||
{
|
||||
public void GenerateChunk(Span target, ChunkGeneratorContext context)
|
||||
{
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "None";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||
{
|
||||
public class StatementChunkGenerator : SpanChunkGenerator
|
||||
{
|
||||
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
|
||||
{
|
||||
context.ChunkTreeBuilder.AddStatementChunk(target.Content, target);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Stmt";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
using Microsoft.AspNet.Razor.Parser.TagHelpers;
|
||||
using Microsoft.AspNet.Razor.Compilation.TagHelpers;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="ParentChunkGenerator"/> that is responsible for generating valid <see cref="TagHelperChunk"/>s.
|
||||
/// </summary>
|
||||
public class TagHelperChunkGenerator : ParentChunkGenerator
|
||||
{
|
||||
private IEnumerable<TagHelperDescriptor> _tagHelperDescriptors;
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="TagHelperChunkGenerator"/>.
|
||||
/// </summary>
|
||||
/// <param name="tagHelperDescriptors">
|
||||
/// <see cref="TagHelperDescriptor"/>s associated with the current HTML tag.
|
||||
/// </param>
|
||||
public TagHelperChunkGenerator(IEnumerable<TagHelperDescriptor> tagHelperDescriptors)
|
||||
{
|
||||
_tagHelperDescriptors = tagHelperDescriptors;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the generation of a <see cref="TagHelperChunk"/>.
|
||||
/// </summary>
|
||||
/// <param name="target">
|
||||
/// The <see cref="Block"/> responsible for this <see cref="TagHelperChunkGenerator"/>.
|
||||
/// </param>
|
||||
/// <param name="context">A <see cref="ChunkGeneratorContext"/> instance that contains information about
|
||||
/// the current chunk generation process.</param>
|
||||
public override void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
|
||||
{
|
||||
var tagHelperBlock = target as TagHelperBlock;
|
||||
|
||||
Debug.Assert(
|
||||
tagHelperBlock != null,
|
||||
$"A {nameof(TagHelperChunkGenerator)} must only be used with {nameof(TagHelperBlock)}s.");
|
||||
|
||||
var attributes = new List<KeyValuePair<string, Chunk>>();
|
||||
|
||||
// We need to create a chunk generator to create chunks for each of the attributes.
|
||||
var chunkGenerator = context.Host.CreateChunkGenerator(
|
||||
context.ClassName,
|
||||
context.RootNamespace,
|
||||
context.SourceFile);
|
||||
|
||||
foreach (var attribute in tagHelperBlock.Attributes)
|
||||
{
|
||||
ParentChunk attributeChunkValue = null;
|
||||
|
||||
if (attribute.Value != null)
|
||||
{
|
||||
// Populates the chunk tree with chunks associated with attributes
|
||||
attribute.Value.Accept(chunkGenerator);
|
||||
|
||||
var chunks = chunkGenerator.Context.ChunkTreeBuilder.ChunkTree.Chunks;
|
||||
var first = chunks.FirstOrDefault();
|
||||
|
||||
attributeChunkValue = new ParentChunk
|
||||
{
|
||||
Association = first?.Association,
|
||||
Children = chunks,
|
||||
Start = first == null ? SourceLocation.Zero : first.Start
|
||||
};
|
||||
}
|
||||
|
||||
attributes.Add(new KeyValuePair<string, Chunk>(attribute.Key, attributeChunkValue));
|
||||
|
||||
// Reset the chunk tree builder so we can build a new one for the next attribute
|
||||
chunkGenerator.Context.ChunkTreeBuilder = new ChunkTreeBuilder();
|
||||
}
|
||||
|
||||
var unprefixedTagName = tagHelperBlock.TagName.Substring(_tagHelperDescriptors.First().Prefix.Length);
|
||||
|
||||
context.ChunkTreeBuilder.StartParentChunk(
|
||||
new TagHelperChunk(
|
||||
unprefixedTagName,
|
||||
tagHelperBlock.TagMode,
|
||||
attributes,
|
||||
_tagHelperDescriptors),
|
||||
target,
|
||||
topLevel: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends the generation of a <see cref="TagHelperChunk"/> capturing all previously visited children
|
||||
/// since the <see cref="GenerateStartParentChunk"/> method was called.
|
||||
/// </summary>
|
||||
/// <param name="target">
|
||||
/// The <see cref="Block"/> responsible for this <see cref="TagHelperChunkGenerator"/>.
|
||||
/// </param>
|
||||
/// <param name="context">A <see cref="ChunkGeneratorContext"/> instance that contains information about
|
||||
/// the current chunk generation process.</param>
|
||||
public override void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
|
||||
{
|
||||
context.ChunkTreeBuilder.EndParentChunk();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="SpanChunkGenerator"/> responsible for generating
|
||||
/// <see cref="TagHelperPrefixDirectiveChunk"/>s.
|
||||
/// </summary>
|
||||
public class TagHelperPrefixDirectiveChunkGenerator : SpanChunkGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="TagHelperPrefixDirectiveChunkGenerator"/>.
|
||||
/// </summary>
|
||||
/// <param name="prefix">
|
||||
/// Text used as a required prefix when matching HTML.
|
||||
/// </param>
|
||||
public TagHelperPrefixDirectiveChunkGenerator(string prefix)
|
||||
{
|
||||
Prefix = prefix;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Text used as a required prefix when matching HTML.
|
||||
/// </summary>
|
||||
public string Prefix { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Generates <see cref="TagHelperPrefixDirectiveChunk"/>s.
|
||||
/// </summary>
|
||||
/// <param name="target">
|
||||
/// The <see cref="Span"/> responsible for this <see cref="TagHelperPrefixDirectiveChunkGenerator"/>.
|
||||
/// </param>
|
||||
/// <param name="context">A <see cref="ChunkGeneratorContext"/> instance that contains information about
|
||||
/// the current chunk generation process.</param>
|
||||
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
|
||||
{
|
||||
context.ChunkTreeBuilder.AddTagHelperPrefixDirectiveChunk(Prefix, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||
{
|
||||
public class TemplateBlockChunkGenerator : ParentChunkGenerator
|
||||
{
|
||||
public override void GenerateStartParentChunk(Block target, ChunkGeneratorContext context)
|
||||
{
|
||||
context.ChunkTreeBuilder.StartParentChunk<TemplateChunk>(target);
|
||||
}
|
||||
|
||||
public override void GenerateEndParentChunk(Block target, ChunkGeneratorContext context)
|
||||
{
|
||||
context.ChunkTreeBuilder.EndParentChunk();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks.Generators
|
||||
{
|
||||
public class TypeMemberChunkGenerator : SpanChunkGenerator
|
||||
{
|
||||
public override void GenerateChunk(Span target, ChunkGeneratorContext context)
|
||||
{
|
||||
context.ChunkTreeBuilder.AddTypeMemberChunk(target.Content, target);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "TypeMember";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks
|
||||
{
|
||||
public class LiteralChunk : Chunk
|
||||
{
|
||||
public string Text { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Start + " = " + Text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Razor.Text;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks
|
||||
{
|
||||
public class LiteralCodeAttributeChunk : ParentChunk
|
||||
{
|
||||
public string Code { get; set; }
|
||||
public LocationTagged<string> Prefix { get; set; }
|
||||
public LocationTagged<string> Value { get; set; }
|
||||
public SourceLocation ValueLocation { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks
|
||||
{
|
||||
public class ParentChunk : Chunk
|
||||
{
|
||||
public ParentChunk()
|
||||
{
|
||||
Children = new List<Chunk>();
|
||||
}
|
||||
|
||||
public IList<Chunk> Children { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="Chunk"/> used to look up <see cref="Compilation.TagHelpers.TagHelperDescriptor"/>s that should be ignored
|
||||
/// within the Razor page.
|
||||
/// </summary>
|
||||
public class RemoveTagHelperChunk : Chunk
|
||||
{
|
||||
/// <summary>
|
||||
/// Text used to look up <see cref="Compilation.TagHelpers.TagHelperDescriptor"/>s that should be ignored within the Razor
|
||||
/// page.
|
||||
/// </summary>
|
||||
public string LookupText { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks
|
||||
{
|
||||
public class SectionChunk : ParentChunk
|
||||
{
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks
|
||||
{
|
||||
public class SetBaseTypeChunk : Chunk
|
||||
{
|
||||
public string TypeName { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks
|
||||
{
|
||||
public class StatementChunk : Chunk
|
||||
{
|
||||
public string Code { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Start + " = " + Code;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNet.Razor.TagHelpers;
|
||||
using Microsoft.AspNet.Razor.Compilation.TagHelpers;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="ParentChunk"/> that represents a special HTML tag.
|
||||
/// </summary>
|
||||
public class TagHelperChunk : ParentChunk
|
||||
{
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="TagHelperChunk"/>.
|
||||
/// </summary>
|
||||
/// <param name="tagName">The tag name associated with the tag helpers HTML element.</param>
|
||||
/// <param name="tagMode">HTML syntax of the element in the Razor source.</param>
|
||||
/// <param name="attributes">The attributes associated with the tag helpers HTML element.</param>
|
||||
/// <param name="descriptors">
|
||||
/// The <see cref="TagHelperDescriptor"/>s associated with this tag helpers HTML element.
|
||||
/// </param>
|
||||
public TagHelperChunk(
|
||||
string tagName,
|
||||
TagMode tagMode,
|
||||
IList<KeyValuePair<string, Chunk>> attributes,
|
||||
IEnumerable<TagHelperDescriptor> descriptors)
|
||||
{
|
||||
TagName = tagName;
|
||||
TagMode = tagMode;
|
||||
Attributes = attributes;
|
||||
Descriptors = descriptors;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The HTML attributes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// These attributes are <see cref="string"/> => <see cref="Chunk"/> so attribute values can consist
|
||||
/// of all sorts of Razor specific pieces.
|
||||
/// </remarks>
|
||||
public IList<KeyValuePair<string, Chunk>> Attributes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="TagHelperDescriptor"/>s that are associated with the tag helpers HTML element.
|
||||
/// </summary>
|
||||
public IEnumerable<TagHelperDescriptor> Descriptors { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The HTML tag name.
|
||||
/// </summary>
|
||||
public string TagName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the HTML syntax of the element in the Razor source.
|
||||
/// </summary>
|
||||
public TagMode TagMode { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="Chunk"/> for the <c>@tagHelperPrefix</c> directive.
|
||||
/// </summary>
|
||||
public class TagHelperPrefixDirectiveChunk : Chunk
|
||||
{
|
||||
/// <summary>
|
||||
/// Text used as a required prefix when matching HTML start and end tags in the Razor source to available
|
||||
/// tag helpers.
|
||||
/// </summary>
|
||||
public string Prefix { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks
|
||||
{
|
||||
public class TemplateChunk : ParentChunk
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks
|
||||
{
|
||||
public class TypeMemberChunk : Chunk
|
||||
{
|
||||
public string Code { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.Razor.Chunks
|
||||
{
|
||||
public class UsingChunk : Chunk
|
||||
{
|
||||
public string Namespace { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Razor.Chunks;
|
||||
using Microsoft.AspNet.Razor.CodeGenerators.Visitors;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||
{
|
||||
public class CSharpCodeGenerator : CodeGenerator
|
||||
{
|
||||
// See http://msdn.microsoft.com/en-us/library/system.codedom.codechecksumpragma.checksumalgorithmid.aspx
|
||||
private const string Sha1AlgorithmId = "{ff1816ec-aa5e-4d10-87f7-6f4963833460}";
|
||||
private const int DisableAsyncWarning = 1998;
|
||||
|
||||
public CSharpCodeGenerator(CodeGeneratorContext context)
|
||||
: base(context)
|
||||
{
|
||||
}
|
||||
|
||||
private ChunkTree Tree { get { return Context.ChunkTreeBuilder.ChunkTree; } }
|
||||
public RazorEngineHost Host { get { return Context.Host; } }
|
||||
|
||||
/// <summary>
|
||||
/// Protected for testing.
|
||||
/// </summary>
|
||||
/// <returns>A new instance of <see cref="CSharpCodeWriter"/>.</returns>
|
||||
protected virtual CSharpCodeWriter CreateCodeWriter()
|
||||
{
|
||||
return new CSharpCodeWriter();
|
||||
}
|
||||
|
||||
public override CodeGeneratorResult Generate()
|
||||
{
|
||||
var writer = CreateCodeWriter();
|
||||
|
||||
if (!Host.DesignTimeMode && !string.IsNullOrEmpty(Context.Checksum))
|
||||
{
|
||||
writer.Write("#pragma checksum \"")
|
||||
.Write(Context.SourceFile)
|
||||
.Write("\" \"")
|
||||
.Write(Sha1AlgorithmId)
|
||||
.Write("\" \"")
|
||||
.Write(Context.Checksum)
|
||||
.WriteLine("\"");
|
||||
}
|
||||
|
||||
using (writer.BuildNamespace(Context.RootNamespace))
|
||||
{
|
||||
// Write out using directives
|
||||
AddImports(Tree, writer, Host.NamespaceImports);
|
||||
// Separate the usings and the class
|
||||
writer.WriteLine();
|
||||
|
||||
using (BuildClassDeclaration(writer))
|
||||
{
|
||||
if (Host.DesignTimeMode)
|
||||
{
|
||||
writer.WriteLine("private static object @__o;");
|
||||
}
|
||||
|
||||
var csharpCodeVisitor = CreateCSharpCodeVisitor(writer, Context);
|
||||
|
||||
new CSharpTypeMemberVisitor(csharpCodeVisitor, writer, Context).Accept(Tree.Chunks);
|
||||
CreateCSharpDesignTimeCodeVisitor(csharpCodeVisitor, writer, Context)
|
||||
.AcceptTree(Tree);
|
||||
new CSharpTagHelperFieldDeclarationVisitor(writer, Context).Accept(Tree.Chunks);
|
||||
|
||||
BuildConstructor(writer);
|
||||
|
||||
// Add space in-between constructor and method body
|
||||
writer.WriteLine();
|
||||
|
||||
using (writer.BuildDisableWarningScope(DisableAsyncWarning))
|
||||
{
|
||||
using (writer.BuildMethodDeclaration("public override async", "Task", Host.GeneratedClassContext.ExecuteMethodName))
|
||||
{
|
||||
new CSharpTagHelperRunnerInitializationVisitor(writer, Context).Accept(Tree.Chunks);
|
||||
csharpCodeVisitor.Accept(Tree.Chunks);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new CodeGeneratorResult(writer.GenerateCode(), writer.LineMappingManager.Mappings);
|
||||
}
|
||||
|
||||
protected virtual CSharpCodeVisitor CreateCSharpCodeVisitor(
|
||||
CSharpCodeWriter writer,
|
||||
CodeGeneratorContext context)
|
||||
{
|
||||
if (writer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(writer));
|
||||
}
|
||||
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
return new CSharpCodeVisitor(writer, context);
|
||||
}
|
||||
|
||||
protected virtual CSharpDesignTimeCodeVisitor CreateCSharpDesignTimeCodeVisitor(
|
||||
CSharpCodeVisitor csharpCodeVisitor,
|
||||
CSharpCodeWriter writer,
|
||||
CodeGeneratorContext context)
|
||||
{
|
||||
if (csharpCodeVisitor == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(csharpCodeVisitor));
|
||||
}
|
||||
|
||||
if (writer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(writer));
|
||||
}
|
||||
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
return new CSharpDesignTimeCodeVisitor(csharpCodeVisitor, writer, context);
|
||||
}
|
||||
|
||||
protected virtual CSharpCodeWritingScope BuildClassDeclaration(CSharpCodeWriter writer)
|
||||
{
|
||||
var baseTypeVisitor = new CSharpBaseTypeVisitor(writer, Context);
|
||||
baseTypeVisitor.Accept(Tree.Chunks);
|
||||
|
||||
var baseType = baseTypeVisitor.CurrentBaseType ?? Host.DefaultBaseClass;
|
||||
|
||||
var baseTypes = string.IsNullOrEmpty(baseType) ? Enumerable.Empty<string>() : new string[] { baseType };
|
||||
|
||||
return writer.BuildClassDeclaration("public", Context.ClassName, baseTypes);
|
||||
}
|
||||
|
||||
protected virtual void BuildConstructor(CSharpCodeWriter writer)
|
||||
{
|
||||
writer.WriteLineHiddenDirective();
|
||||
using (writer.BuildConstructor(Context.ClassName))
|
||||
{
|
||||
// Any constructor based logic that we need to add?
|
||||
};
|
||||
}
|
||||
|
||||
private void AddImports(ChunkTree chunkTree, CSharpCodeWriter writer, IEnumerable<string> defaultImports)
|
||||
{
|
||||
// Write out using directives
|
||||
var usingVisitor = new CSharpUsingVisitor(writer, Context);
|
||||
foreach (Chunk chunk in Tree.Chunks)
|
||||
{
|
||||
usingVisitor.Accept(chunk);
|
||||
}
|
||||
|
||||
defaultImports = defaultImports.Except(usingVisitor.ImportedUsings);
|
||||
|
||||
foreach (string import in defaultImports)
|
||||
{
|
||||
writer.WriteUsing(import);
|
||||
}
|
||||
|
||||
var taskNamespace = typeof(Task).Namespace;
|
||||
|
||||
// We need to add the task namespace but ONLY if it hasn't been added by the default imports or using imports yet.
|
||||
if (!defaultImports.Contains(taskNamespace) && !usingVisitor.ImportedUsings.Contains(taskNamespace))
|
||||
{
|
||||
writer.WriteUsing(taskNamespace);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,513 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Razor.Chunks.Generators;
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
using Microsoft.AspNet.Razor.Text;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||
{
|
||||
public class CSharpCodeWriter : CodeWriter
|
||||
{
|
||||
private const string InstanceMethodFormat = "{0}.{1}";
|
||||
|
||||
public CSharpCodeWriter()
|
||||
{
|
||||
LineMappingManager = new LineMappingManager();
|
||||
}
|
||||
|
||||
public LineMappingManager LineMappingManager { get; private set; }
|
||||
|
||||
public new CSharpCodeWriter Write(string data)
|
||||
{
|
||||
return (CSharpCodeWriter)base.Write(data);
|
||||
}
|
||||
|
||||
public new CSharpCodeWriter Indent(int size)
|
||||
{
|
||||
return (CSharpCodeWriter)base.Indent(size);
|
||||
}
|
||||
|
||||
public new CSharpCodeWriter ResetIndent()
|
||||
{
|
||||
return (CSharpCodeWriter)base.ResetIndent();
|
||||
}
|
||||
|
||||
public new CSharpCodeWriter SetIndent(int size)
|
||||
{
|
||||
return (CSharpCodeWriter)base.SetIndent(size);
|
||||
}
|
||||
|
||||
public new CSharpCodeWriter IncreaseIndent(int size)
|
||||
{
|
||||
return (CSharpCodeWriter)base.IncreaseIndent(size);
|
||||
}
|
||||
|
||||
public new CSharpCodeWriter DecreaseIndent(int size)
|
||||
{
|
||||
return (CSharpCodeWriter)base.DecreaseIndent(size);
|
||||
}
|
||||
|
||||
public new CSharpCodeWriter WriteLine(string data)
|
||||
{
|
||||
return (CSharpCodeWriter)base.WriteLine(data);
|
||||
}
|
||||
|
||||
public new CSharpCodeWriter WriteLine()
|
||||
{
|
||||
return (CSharpCodeWriter)base.WriteLine();
|
||||
}
|
||||
|
||||
public CSharpCodeWriter WriteVariableDeclaration(string type, string name, string value)
|
||||
{
|
||||
Write(type).Write(" ").Write(name);
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
{
|
||||
Write(" = ").Write(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
Write(" = null");
|
||||
}
|
||||
|
||||
WriteLine(";");
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public CSharpCodeWriter WriteComment(string comment)
|
||||
{
|
||||
return Write("// ").WriteLine(comment);
|
||||
}
|
||||
|
||||
public CSharpCodeWriter WriteBooleanLiteral(bool value)
|
||||
{
|
||||
return Write(value.ToString().ToLowerInvariant());
|
||||
}
|
||||
|
||||
public CSharpCodeWriter WriteStartAssignment(string name)
|
||||
{
|
||||
return Write(name).Write(" = ");
|
||||
}
|
||||
|
||||
public CSharpCodeWriter WriteParameterSeparator()
|
||||
{
|
||||
return Write(", ");
|
||||
}
|
||||
|
||||
public CSharpCodeWriter WriteStartNewObject(string typeName)
|
||||
{
|
||||
return Write("new ").Write(typeName).Write("(");
|
||||
}
|
||||
|
||||
public CSharpCodeWriter WriteLocationTaggedString(LocationTagged<string> value)
|
||||
{
|
||||
WriteStringLiteral(value.Value);
|
||||
WriteParameterSeparator();
|
||||
Write(value.Location.AbsoluteIndex.ToString(CultureInfo.CurrentCulture));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public CSharpCodeWriter WriteStringLiteral(string literal)
|
||||
{
|
||||
if (literal.Length >= 256 && literal.Length <= 1500 && literal.IndexOf('\0') == -1)
|
||||
{
|
||||
WriteVerbatimStringLiteral(literal);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteCStyleStringLiteral(literal);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public CSharpCodeWriter WriteLineHiddenDirective()
|
||||
{
|
||||
return WriteLine("#line hidden");
|
||||
}
|
||||
|
||||
public CSharpCodeWriter WritePragma(string value)
|
||||
{
|
||||
return Write("#pragma ").WriteLine(value);
|
||||
}
|
||||
|
||||
public CSharpCodeWriter WriteUsing(string name)
|
||||
{
|
||||
return WriteUsing(name, endLine: true);
|
||||
}
|
||||
|
||||
public CSharpCodeWriter WriteUsing(string name, bool endLine)
|
||||
{
|
||||
Write(string.Format("using {0}", name));
|
||||
|
||||
if (endLine)
|
||||
{
|
||||
WriteLine(";");
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public CSharpCodeWriter WriteLineDefaultDirective()
|
||||
{
|
||||
return WriteLine("#line default");
|
||||
}
|
||||
|
||||
public CSharpCodeWriter WriteStartReturn()
|
||||
{
|
||||
return Write("return ");
|
||||
}
|
||||
|
||||
public CSharpCodeWriter WriteReturn(string value)
|
||||
{
|
||||
return WriteReturn(value, endLine: true);
|
||||
}
|
||||
|
||||
public CSharpCodeWriter WriteReturn(string value, bool endLine)
|
||||
{
|
||||
Write("return ").Write(value);
|
||||
|
||||
if (endLine)
|
||||
{
|
||||
Write(";");
|
||||
}
|
||||
|
||||
return WriteLine();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a <c>#line</c> pragma directive for the line number at the specified <paramref name="location"/>.
|
||||
/// </summary>
|
||||
/// <param name="location">The location to generate the line pragma for.</param>
|
||||
/// <param name="file">The file to generate the line pragma for.</param>
|
||||
/// <returns>The current instance of <see cref="CSharpCodeWriter"/>.</returns>
|
||||
public CSharpCodeWriter WriteLineNumberDirective(SourceLocation location, string file)
|
||||
{
|
||||
if (location.FilePath != null)
|
||||
{
|
||||
file = location.FilePath;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(LastWrite) &&
|
||||
!LastWrite.EndsWith(NewLine, StringComparison.Ordinal))
|
||||
{
|
||||
WriteLine();
|
||||
}
|
||||
|
||||
var lineNumberAsString = (location.LineIndex + 1).ToString(CultureInfo.InvariantCulture);
|
||||
return Write("#line ").Write(lineNumberAsString).Write(" \"").Write(file).WriteLine("\"");
|
||||
}
|
||||
|
||||
public CSharpCodeWriter WriteStartMethodInvocation(string methodName)
|
||||
{
|
||||
return WriteStartMethodInvocation(methodName, new string[0]);
|
||||
}
|
||||
|
||||
public CSharpCodeWriter WriteStartMethodInvocation(string methodName, params string[] genericArguments)
|
||||
{
|
||||
Write(methodName);
|
||||
|
||||
if (genericArguments.Length > 0)
|
||||
{
|
||||
Write("<").Write(string.Join(", ", genericArguments)).Write(">");
|
||||
}
|
||||
|
||||
return Write("(");
|
||||
}
|
||||
|
||||
public CSharpCodeWriter WriteEndMethodInvocation()
|
||||
{
|
||||
return WriteEndMethodInvocation(endLine: true);
|
||||
}
|
||||
public CSharpCodeWriter WriteEndMethodInvocation(bool endLine)
|
||||
{
|
||||
Write(")");
|
||||
if (endLine)
|
||||
{
|
||||
WriteLine(";");
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// Writes a method invocation for the given instance name.
|
||||
public CSharpCodeWriter WriteInstanceMethodInvocation(string instanceName,
|
||||
string methodName,
|
||||
params string[] parameters)
|
||||
{
|
||||
if (instanceName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(instanceName));
|
||||
}
|
||||
|
||||
if (methodName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(methodName));
|
||||
}
|
||||
|
||||
return WriteInstanceMethodInvocation(instanceName, methodName, endLine: true, parameters: parameters);
|
||||
}
|
||||
|
||||
// Writes a method invocation for the given instance name.
|
||||
public CSharpCodeWriter WriteInstanceMethodInvocation(string instanceName,
|
||||
string methodName,
|
||||
bool endLine,
|
||||
params string[] parameters)
|
||||
{
|
||||
if (instanceName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(instanceName));
|
||||
}
|
||||
|
||||
if (methodName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(methodName));
|
||||
}
|
||||
|
||||
return WriteMethodInvocation(
|
||||
string.Format(CultureInfo.InvariantCulture, InstanceMethodFormat, instanceName, methodName),
|
||||
endLine,
|
||||
parameters);
|
||||
}
|
||||
|
||||
public CSharpCodeWriter WriteStartInstanceMethodInvocation(string instanceName,
|
||||
string methodName)
|
||||
{
|
||||
if (instanceName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(instanceName));
|
||||
}
|
||||
|
||||
if (methodName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(methodName));
|
||||
}
|
||||
|
||||
return WriteStartMethodInvocation(
|
||||
string.Format(CultureInfo.InvariantCulture, InstanceMethodFormat, instanceName, methodName));
|
||||
}
|
||||
|
||||
public CSharpCodeWriter WriteMethodInvocation(string methodName, params string[] parameters)
|
||||
{
|
||||
return WriteMethodInvocation(methodName, endLine: true, parameters: parameters);
|
||||
}
|
||||
|
||||
public CSharpCodeWriter WriteMethodInvocation(string methodName, bool endLine, params string[] parameters)
|
||||
{
|
||||
return WriteStartMethodInvocation(methodName).Write(string.Join(", ", parameters)).WriteEndMethodInvocation(endLine);
|
||||
}
|
||||
|
||||
public CSharpDisableWarningScope BuildDisableWarningScope(int warning)
|
||||
{
|
||||
return new CSharpDisableWarningScope(this, warning);
|
||||
}
|
||||
|
||||
public CSharpCodeWritingScope BuildScope()
|
||||
{
|
||||
return new CSharpCodeWritingScope(this);
|
||||
}
|
||||
|
||||
public CSharpCodeWritingScope BuildLambda(bool endLine, params string[] parameterNames)
|
||||
{
|
||||
return BuildLambda(endLine, async: false, parameterNames: parameterNames);
|
||||
}
|
||||
|
||||
public CSharpCodeWritingScope BuildAsyncLambda(bool endLine, params string[] parameterNames)
|
||||
{
|
||||
return BuildLambda(endLine, async: true, parameterNames: parameterNames);
|
||||
}
|
||||
|
||||
private CSharpCodeWritingScope BuildLambda(bool endLine, bool async, string[] parameterNames)
|
||||
{
|
||||
if (async)
|
||||
{
|
||||
Write("async");
|
||||
}
|
||||
|
||||
Write("(").Write(string.Join(", ", parameterNames)).Write(") => ");
|
||||
|
||||
var scope = new CSharpCodeWritingScope(this);
|
||||
|
||||
if (endLine)
|
||||
{
|
||||
// End the lambda with a semicolon
|
||||
scope.OnClose += () =>
|
||||
{
|
||||
WriteLine(";");
|
||||
};
|
||||
}
|
||||
|
||||
return scope;
|
||||
}
|
||||
|
||||
public CSharpCodeWritingScope BuildNamespace(string name)
|
||||
{
|
||||
Write("namespace ").WriteLine(name);
|
||||
|
||||
return new CSharpCodeWritingScope(this);
|
||||
}
|
||||
|
||||
public CSharpCodeWritingScope BuildClassDeclaration(string accessibility, string name)
|
||||
{
|
||||
return BuildClassDeclaration(accessibility, name, Enumerable.Empty<string>());
|
||||
}
|
||||
|
||||
public CSharpCodeWritingScope BuildClassDeclaration(string accessibility, string name, string baseType)
|
||||
{
|
||||
return BuildClassDeclaration(accessibility, name, new string[] { baseType });
|
||||
}
|
||||
|
||||
public CSharpCodeWritingScope BuildClassDeclaration(string accessibility, string name, IEnumerable<string> baseTypes)
|
||||
{
|
||||
Write(accessibility).Write(" class ").Write(name);
|
||||
|
||||
if (baseTypes.Count() > 0)
|
||||
{
|
||||
Write(" : ");
|
||||
Write(string.Join(", ", baseTypes));
|
||||
}
|
||||
|
||||
WriteLine();
|
||||
|
||||
return new CSharpCodeWritingScope(this);
|
||||
}
|
||||
|
||||
public CSharpCodeWritingScope BuildConstructor(string name)
|
||||
{
|
||||
return BuildConstructor("public", name);
|
||||
}
|
||||
|
||||
public CSharpCodeWritingScope BuildConstructor(string accessibility, string name)
|
||||
{
|
||||
return BuildConstructor(accessibility, name, Enumerable.Empty<KeyValuePair<string, string>>());
|
||||
}
|
||||
|
||||
public CSharpCodeWritingScope BuildConstructor(string accessibility, string name, IEnumerable<KeyValuePair<string, string>> parameters)
|
||||
{
|
||||
Write(accessibility).Write(" ").Write(name).Write("(").Write(string.Join(", ", parameters.Select(p => p.Key + " " + p.Value))).WriteLine(")");
|
||||
|
||||
return new CSharpCodeWritingScope(this);
|
||||
}
|
||||
|
||||
public CSharpCodeWritingScope BuildMethodDeclaration(string accessibility, string returnType, string name)
|
||||
{
|
||||
return BuildMethodDeclaration(accessibility, returnType, name, Enumerable.Empty<KeyValuePair<string, string>>());
|
||||
}
|
||||
|
||||
public CSharpCodeWritingScope BuildMethodDeclaration(string accessibility, string returnType, string name, IEnumerable<KeyValuePair<string, string>> parameters)
|
||||
{
|
||||
Write(accessibility).Write(" ").Write(returnType).Write(" ").Write(name).Write("(").Write(string.Join(", ", parameters.Select(p => p.Key + " " + p.Value))).WriteLine(")");
|
||||
|
||||
return new CSharpCodeWritingScope(this);
|
||||
}
|
||||
|
||||
// TODO: Do I need to look at the document content to determine its mapping length?
|
||||
public CSharpLineMappingWriter BuildLineMapping(SourceLocation documentLocation, int contentLength, string sourceFilename)
|
||||
{
|
||||
return new CSharpLineMappingWriter(this, documentLocation, contentLength, sourceFilename);
|
||||
}
|
||||
|
||||
private void WriteVerbatimStringLiteral(string literal)
|
||||
{
|
||||
Write("@\"");
|
||||
|
||||
foreach (char c in literal)
|
||||
{
|
||||
if (c == '\"')
|
||||
{
|
||||
Write("\"\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
Write(c.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
Write("\"");
|
||||
}
|
||||
|
||||
private void WriteCStyleStringLiteral(string literal)
|
||||
{
|
||||
// From CSharpCodeGenerator.QuoteSnippetStringCStyle in CodeDOM
|
||||
Write("\"");
|
||||
for (int i = 0; i < literal.Length; i++)
|
||||
{
|
||||
switch (literal[i])
|
||||
{
|
||||
case '\r':
|
||||
Write("\\r");
|
||||
break;
|
||||
case '\t':
|
||||
Write("\\t");
|
||||
break;
|
||||
case '\"':
|
||||
Write("\\\"");
|
||||
break;
|
||||
case '\'':
|
||||
Write("\\\'");
|
||||
break;
|
||||
case '\\':
|
||||
Write("\\\\");
|
||||
break;
|
||||
case '\0':
|
||||
Write("\\\0");
|
||||
break;
|
||||
case '\n':
|
||||
Write("\\n");
|
||||
break;
|
||||
case '\u2028':
|
||||
case '\u2029':
|
||||
Write("\\u");
|
||||
Write(((int)literal[i]).ToString("X4", CultureInfo.InvariantCulture));
|
||||
break;
|
||||
default:
|
||||
Write(literal[i].ToString());
|
||||
break;
|
||||
}
|
||||
if (i > 0 && i % 80 == 0)
|
||||
{
|
||||
// If current character is a high surrogate and the following
|
||||
// character is a low surrogate, don't break them.
|
||||
// Otherwise when we write the string to a file, we might lose
|
||||
// the characters.
|
||||
if (Char.IsHighSurrogate(literal[i])
|
||||
&& (i < literal.Length - 1)
|
||||
&& Char.IsLowSurrogate(literal[i + 1]))
|
||||
{
|
||||
Write(literal[++i].ToString());
|
||||
}
|
||||
|
||||
Write("\" +");
|
||||
Write(NewLine);
|
||||
Write("\"");
|
||||
}
|
||||
}
|
||||
Write("\"");
|
||||
}
|
||||
|
||||
public CSharpCodeWriter WriteStartInstrumentationContext(
|
||||
ChunkGeneratorContext context,
|
||||
SyntaxTreeNode syntaxNode,
|
||||
bool isLiteral)
|
||||
{
|
||||
WriteStartMethodInvocation(context.Host.GeneratedClassContext.BeginContextMethodName);
|
||||
Write(syntaxNode.Start.AbsoluteIndex.ToString(CultureInfo.InvariantCulture));
|
||||
WriteParameterSeparator();
|
||||
Write(syntaxNode.Length.ToString(CultureInfo.InvariantCulture));
|
||||
WriteParameterSeparator();
|
||||
Write(isLiteral ? "true" : "false");
|
||||
return WriteEndMethodInvocation();
|
||||
}
|
||||
|
||||
public CSharpCodeWriter WriteEndInstrumentationContext(ChunkGeneratorContext context)
|
||||
{
|
||||
return WriteMethodInvocation(context.Host.GeneratedClassContext.EndContextMethodName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||
{
|
||||
public struct CSharpCodeWritingScope : IDisposable
|
||||
{
|
||||
private CodeWriter _writer;
|
||||
private bool _autoSpace;
|
||||
private int _tabSize;
|
||||
private int _startIndent;
|
||||
|
||||
public CSharpCodeWritingScope(CodeWriter writer) : this(writer, true) { }
|
||||
public CSharpCodeWritingScope(CodeWriter writer, int tabSize) : this(writer, tabSize, true) { }
|
||||
// TODO: Make indents (tabs) environment specific
|
||||
public CSharpCodeWritingScope(CodeWriter writer, bool autoSpace) : this(writer, 4, autoSpace) { }
|
||||
public CSharpCodeWritingScope(CodeWriter writer, int tabSize, bool autoSpace)
|
||||
{
|
||||
_writer = writer;
|
||||
_autoSpace = true;
|
||||
_tabSize = tabSize;
|
||||
_startIndent = -1; // Set in WriteStartScope
|
||||
|
||||
OnClose = () => { };
|
||||
|
||||
WriteStartScope();
|
||||
}
|
||||
|
||||
public Action OnClose;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
WriteEndScope();
|
||||
OnClose();
|
||||
}
|
||||
|
||||
private void WriteStartScope()
|
||||
{
|
||||
TryAutoSpace(" ");
|
||||
|
||||
_writer.WriteLine("{").IncreaseIndent(_tabSize);
|
||||
_startIndent = _writer.CurrentIndent;
|
||||
}
|
||||
|
||||
private void WriteEndScope()
|
||||
{
|
||||
TryAutoSpace(_writer.NewLine);
|
||||
|
||||
// Ensure the scope hasn't been modified
|
||||
if (_writer.CurrentIndent == _startIndent)
|
||||
{
|
||||
_writer.DecreaseIndent(_tabSize);
|
||||
}
|
||||
|
||||
_writer.WriteLine("}");
|
||||
}
|
||||
|
||||
private void TryAutoSpace(string spaceCharacter)
|
||||
{
|
||||
if (_autoSpace && _writer.LastWrite.Length > 0 && !Char.IsWhiteSpace(_writer.LastWrite.Last()))
|
||||
{
|
||||
_writer.Write(spaceCharacter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||
{
|
||||
public struct CSharpDisableWarningScope : IDisposable
|
||||
{
|
||||
private CSharpCodeWriter _writer;
|
||||
int _warningNumber;
|
||||
|
||||
public CSharpDisableWarningScope(CSharpCodeWriter writer) : this(writer, 219)
|
||||
{ }
|
||||
|
||||
public CSharpDisableWarningScope(CSharpCodeWriter writer, int warningNumber)
|
||||
{
|
||||
_writer = writer;
|
||||
_warningNumber = warningNumber;
|
||||
|
||||
_writer.WritePragma("warning disable " + _warningNumber);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_writer.WritePragma("warning restore " + _warningNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||
{
|
||||
public class CSharpLineMappingWriter : IDisposable
|
||||
{
|
||||
private readonly CSharpCodeWriter _writer;
|
||||
private readonly MappingLocation _documentMapping;
|
||||
private readonly int _startIndent;
|
||||
private readonly bool _writePragmas;
|
||||
private readonly bool _addLineMapping;
|
||||
|
||||
private SourceLocation _generatedLocation;
|
||||
private int _generatedContentLength;
|
||||
|
||||
private CSharpLineMappingWriter(CSharpCodeWriter writer, bool addLineMappings)
|
||||
{
|
||||
if (writer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(writer));
|
||||
}
|
||||
|
||||
_writer = writer;
|
||||
_addLineMapping = addLineMappings;
|
||||
_startIndent = _writer.CurrentIndent;
|
||||
_writer.ResetIndent();
|
||||
}
|
||||
|
||||
public CSharpLineMappingWriter(CSharpCodeWriter writer, SourceLocation documentLocation, int contentLength)
|
||||
: this(writer, addLineMappings: true)
|
||||
{
|
||||
_documentMapping = new MappingLocation(documentLocation, contentLength);
|
||||
_generatedLocation = _writer.GetCurrentSourceLocation();
|
||||
}
|
||||
|
||||
public CSharpLineMappingWriter(
|
||||
CSharpCodeWriter writer,
|
||||
SourceLocation documentLocation,
|
||||
int contentLength,
|
||||
string sourceFilename)
|
||||
: this(writer, documentLocation, contentLength)
|
||||
{
|
||||
_writePragmas = true;
|
||||
|
||||
_writer.WriteLineNumberDirective(documentLocation, sourceFilename);
|
||||
_generatedLocation = _writer.GetCurrentSourceLocation();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="CSharpLineMappingWriter"/> used for generation of runtime
|
||||
/// line mappings. The constructed instance of <see cref="CSharpLineMappingWriter"/> does not track
|
||||
/// mappings between the Razor content and the generated content.
|
||||
/// </summary>
|
||||
/// <param name="writer">The <see cref="CSharpCodeWriter"/> to write output to.</param>
|
||||
/// <param name="documentLocation">The <see cref="SourceLocation"/> of the Razor content being mapping.</param>
|
||||
/// <param name="sourceFileName">The input file path.</param>
|
||||
public CSharpLineMappingWriter(
|
||||
CSharpCodeWriter writer,
|
||||
SourceLocation documentLocation,
|
||||
string sourceFileName)
|
||||
: this(writer, addLineMappings: false)
|
||||
{
|
||||
if (writer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(writer));
|
||||
}
|
||||
|
||||
_writePragmas = true;
|
||||
_writer.WriteLineNumberDirective(documentLocation, sourceFileName);
|
||||
}
|
||||
|
||||
public void MarkLineMappingStart()
|
||||
{
|
||||
_generatedLocation = _writer.GetCurrentSourceLocation();
|
||||
}
|
||||
|
||||
public void MarkLineMappingEnd()
|
||||
{
|
||||
_generatedContentLength = _writer.GenerateCode().Length - _generatedLocation.AbsoluteIndex;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_addLineMapping)
|
||||
{
|
||||
// Verify that the generated length has not already been calculated
|
||||
if (_generatedContentLength == 0)
|
||||
{
|
||||
_generatedContentLength = _writer.GenerateCode().Length - _generatedLocation.AbsoluteIndex;
|
||||
}
|
||||
|
||||
var generatedLocation = new MappingLocation(_generatedLocation, _generatedContentLength);
|
||||
var documentMapping = _documentMapping;
|
||||
if (documentMapping.ContentLength == -1)
|
||||
{
|
||||
documentMapping = new MappingLocation(
|
||||
location: new SourceLocation(
|
||||
_documentMapping.FilePath,
|
||||
_documentMapping.AbsoluteIndex,
|
||||
_documentMapping.LineIndex,
|
||||
_documentMapping.CharacterIndex),
|
||||
contentLength: _generatedContentLength);
|
||||
}
|
||||
|
||||
_writer.LineMappingManager.AddMapping(
|
||||
documentLocation: documentMapping,
|
||||
generatedLocation: generatedLocation);
|
||||
}
|
||||
|
||||
if (_writePragmas)
|
||||
{
|
||||
// Need to add an additional line at the end IF there wasn't one already written.
|
||||
// This is needed to work with the C# editor's handling of #line ...
|
||||
var endsWithNewline = _writer.GenerateCode().EndsWith("\n");
|
||||
|
||||
// Always write at least 1 empty line to potentially separate code from pragmas.
|
||||
_writer.WriteLine();
|
||||
|
||||
// Check if the previous empty line wasn't enough to separate code from pragmas.
|
||||
if (!endsWithNewline)
|
||||
{
|
||||
_writer.WriteLine();
|
||||
}
|
||||
|
||||
_writer.WriteLineDefaultDirective()
|
||||
.WriteLineHiddenDirective();
|
||||
}
|
||||
|
||||
// Reset indent back to when it was started
|
||||
_writer.SetIndent(_startIndent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNet.Razor.Parser;
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||
{
|
||||
public class CSharpPaddingBuilder
|
||||
{
|
||||
private static readonly char[] _newLineChars = { '\r', '\n' };
|
||||
|
||||
private readonly RazorEngineHost _host;
|
||||
|
||||
public CSharpPaddingBuilder(RazorEngineHost host)
|
||||
{
|
||||
_host = host;
|
||||
}
|
||||
|
||||
// Special case for statement padding to account for brace positioning in the editor.
|
||||
public string BuildStatementPadding(Span target)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(target));
|
||||
}
|
||||
|
||||
var padding = CalculatePadding(target, generatedStart: 0);
|
||||
|
||||
// We treat statement padding specially so for brace positioning, so that in the following example:
|
||||
// @if (foo > 0)
|
||||
// {
|
||||
// }
|
||||
//
|
||||
// the braces shows up under the @ rather than under the if.
|
||||
if (_host.DesignTimeMode &&
|
||||
padding > 0 &&
|
||||
target.Previous.Kind == SpanKind.Transition && // target.Previous is guaranteed to not be null if you have padding.
|
||||
string.Equals(target.Previous.Content, SyntaxConstants.TransitionString, StringComparison.Ordinal))
|
||||
{
|
||||
padding--;
|
||||
}
|
||||
|
||||
var generatedCode = BuildPaddingInternal(padding);
|
||||
|
||||
return generatedCode;
|
||||
}
|
||||
|
||||
public string BuildExpressionPadding(Span target)
|
||||
{
|
||||
return BuildExpressionPadding(target, generatedStart: 0);
|
||||
}
|
||||
|
||||
public string BuildExpressionPadding(Span target, int generatedStart)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(target));
|
||||
}
|
||||
|
||||
var padding = CalculatePadding(target, generatedStart);
|
||||
|
||||
return BuildPaddingInternal(padding);
|
||||
}
|
||||
|
||||
internal int CalculatePadding(Span target, int generatedStart)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(target));
|
||||
}
|
||||
|
||||
int padding;
|
||||
|
||||
padding = CollectSpacesAndTabs(target, _host.TabSize) - generatedStart;
|
||||
|
||||
// if we add generated text that is longer than the padding we wanted to insert we have no recourse and we have to skip padding
|
||||
// example:
|
||||
// Razor code at column zero: @somecode()
|
||||
// Generated code will be:
|
||||
// In design time: __o = somecode();
|
||||
// In Run time: Write(somecode());
|
||||
//
|
||||
// In both cases the padding would have been 1 space to remote the space the @ symbol takes, which will be smaller than the 6
|
||||
// chars the hidden generated code takes.
|
||||
if (padding < 0)
|
||||
{
|
||||
padding = 0;
|
||||
}
|
||||
|
||||
return padding;
|
||||
}
|
||||
|
||||
private string BuildPaddingInternal(int padding)
|
||||
{
|
||||
if (_host.DesignTimeMode && _host.IsIndentingWithTabs)
|
||||
{
|
||||
var spaces = padding % _host.TabSize;
|
||||
var tabs = padding / _host.TabSize;
|
||||
|
||||
return new string('\t', tabs) + new string(' ', spaces);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new string(' ', padding);
|
||||
}
|
||||
}
|
||||
|
||||
private static int CollectSpacesAndTabs(Span target, int tabSize)
|
||||
{
|
||||
var firstSpanInLine = target;
|
||||
|
||||
string currentContent = null;
|
||||
|
||||
while (firstSpanInLine.Previous != null)
|
||||
{
|
||||
// When scanning previous spans we need to be break down the spans with spaces. The parser combines
|
||||
// whitespace into existing spans so you'll see tabs, newlines etc. within spans. We only care about
|
||||
// the \t in existing spans.
|
||||
var previousContent = firstSpanInLine.Previous.Content ?? string.Empty;
|
||||
|
||||
var lastNewLineIndex = previousContent.LastIndexOfAny(_newLineChars);
|
||||
|
||||
if (lastNewLineIndex < 0)
|
||||
{
|
||||
firstSpanInLine = firstSpanInLine.Previous;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (lastNewLineIndex != previousContent.Length - 1)
|
||||
{
|
||||
firstSpanInLine = firstSpanInLine.Previous;
|
||||
currentContent = previousContent.Substring(lastNewLineIndex + 1);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// We need to walk from the beginning of the line, because space + tab(tabSize) = tabSize columns, but tab(tabSize) + space = tabSize+1 columns.
|
||||
var currentSpanInLine = firstSpanInLine;
|
||||
|
||||
if (currentContent == null)
|
||||
{
|
||||
currentContent = currentSpanInLine.Content;
|
||||
}
|
||||
|
||||
var padding = 0;
|
||||
while (currentSpanInLine != target)
|
||||
{
|
||||
if (currentContent != null)
|
||||
{
|
||||
for (int i = 0; i < currentContent.Length; i++)
|
||||
{
|
||||
if (currentContent[i] == '\t')
|
||||
{
|
||||
// Example:
|
||||
// <space><space><tab><tab>:
|
||||
// iter 1) 1
|
||||
// iter 2) 2
|
||||
// iter 3) 4 = 2 + (4 - 2)
|
||||
// iter 4) 8 = 4 + (4 - 0)
|
||||
padding = padding + (tabSize - (padding % tabSize));
|
||||
}
|
||||
else
|
||||
{
|
||||
padding++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
currentSpanInLine = currentSpanInLine.Next;
|
||||
currentContent = currentSpanInLine.Content;
|
||||
}
|
||||
|
||||
return padding;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,761 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Razor.Chunks;
|
||||
using Microsoft.AspNet.Razor.CodeGenerators.Visitors;
|
||||
using Microsoft.AspNet.Razor.TagHelpers;
|
||||
using Microsoft.AspNet.Razor.Compilation.TagHelpers;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||
{
|
||||
/// <summary>
|
||||
/// Renders tag helper rendering code.
|
||||
/// </summary>
|
||||
public class CSharpTagHelperCodeRenderer
|
||||
{
|
||||
internal static readonly string ExecutionContextVariableName = "__tagHelperExecutionContext";
|
||||
internal static readonly string StringValueBufferVariableName = "__tagHelperStringValueBuffer";
|
||||
internal static readonly string ScopeManagerVariableName = "__tagHelperScopeManager";
|
||||
internal static readonly string RunnerVariableName = "__tagHelperRunner";
|
||||
|
||||
private readonly CSharpCodeWriter _writer;
|
||||
private readonly CodeGeneratorContext _context;
|
||||
private readonly IChunkVisitor _bodyVisitor;
|
||||
private readonly IChunkVisitor _literalBodyVisitor;
|
||||
private readonly TagHelperAttributeCodeVisitor _attributeCodeVisitor;
|
||||
private readonly GeneratedTagHelperContext _tagHelperContext;
|
||||
private readonly bool _designTimeMode;
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="CSharpTagHelperCodeRenderer"/>.
|
||||
/// </summary>
|
||||
/// <param name="bodyVisitor">The <see cref="IChunkVisitor"/> used to render chunks found in the body.</param>
|
||||
/// <param name="writer">The <see cref="CSharpCodeWriter"/> used to write code.</param>
|
||||
/// <param name="context">A <see cref="CodeGeneratorContext"/> instance that contains information about
|
||||
/// the current code generation process.</param>
|
||||
public CSharpTagHelperCodeRenderer(
|
||||
IChunkVisitor bodyVisitor,
|
||||
CSharpCodeWriter writer,
|
||||
CodeGeneratorContext context)
|
||||
{
|
||||
if (bodyVisitor == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(bodyVisitor));
|
||||
}
|
||||
|
||||
if (writer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(writer));
|
||||
}
|
||||
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
_bodyVisitor = bodyVisitor;
|
||||
_writer = writer;
|
||||
_context = context;
|
||||
_tagHelperContext = context.Host.GeneratedClassContext.GeneratedTagHelperContext;
|
||||
_designTimeMode = context.Host.DesignTimeMode;
|
||||
|
||||
_literalBodyVisitor = new CSharpLiteralCodeVisitor(this, writer, context);
|
||||
_attributeCodeVisitor = new TagHelperAttributeCodeVisitor(writer, context);
|
||||
AttributeValueCodeRenderer = new TagHelperAttributeValueCodeRenderer();
|
||||
}
|
||||
|
||||
public TagHelperAttributeValueCodeRenderer AttributeValueCodeRenderer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Renders the code for the given <paramref name="chunk"/>.
|
||||
/// </summary>
|
||||
/// <param name="chunk">A <see cref="TagHelperChunk"/> to render.</param>
|
||||
public void RenderTagHelper(TagHelperChunk chunk)
|
||||
{
|
||||
// Remove any duplicate TagHelperDescriptors that reference the same type name. Duplicates can occur when
|
||||
// multiple HtmlTargetElement attributes are on a TagHelper type and matches overlap for an HTML element.
|
||||
// Having more than one descriptor with the same TagHelper type results in generated code that runs
|
||||
// the same TagHelper X many times (instead of once) over a single HTML element.
|
||||
var tagHelperDescriptors = chunk.Descriptors.Distinct(TypeBasedTagHelperDescriptorComparer.Default);
|
||||
|
||||
RenderBeginTagHelperScope(chunk.TagName, chunk.TagMode, chunk.Children);
|
||||
|
||||
RenderTagHelpersCreation(chunk, tagHelperDescriptors);
|
||||
|
||||
RenderAttributes(chunk.Attributes, tagHelperDescriptors);
|
||||
|
||||
// No need to run anything in design time mode.
|
||||
if (!_designTimeMode)
|
||||
{
|
||||
RenderRunTagHelpers();
|
||||
RenderWriteTagHelperMethodCall(chunk);
|
||||
RenderEndTagHelpersScope();
|
||||
}
|
||||
}
|
||||
|
||||
internal static string GetVariableName(TagHelperDescriptor descriptor)
|
||||
{
|
||||
return "__" + descriptor.TypeName.Replace('.', '_');
|
||||
}
|
||||
|
||||
private void RenderBeginTagHelperScope(string tagName, TagMode tagMode, IList<Chunk> children)
|
||||
{
|
||||
// Scopes/execution contexts are a runtime feature.
|
||||
if (_designTimeMode)
|
||||
{
|
||||
// Render all of the tag helper children inline for IntelliSense.
|
||||
_bodyVisitor.Accept(children);
|
||||
return;
|
||||
}
|
||||
|
||||
// Call into the tag helper scope manager to start a new tag helper scope.
|
||||
// Also capture the value as the current execution context.
|
||||
_writer
|
||||
.WriteStartAssignment(ExecutionContextVariableName)
|
||||
.WriteStartInstanceMethodInvocation(
|
||||
ScopeManagerVariableName,
|
||||
_tagHelperContext.ScopeManagerBeginMethodName);
|
||||
|
||||
// Assign a unique ID for this instance of the source HTML tag. This must be unique
|
||||
// per call site, e.g. if the tag is on the view twice, there should be two IDs.
|
||||
_writer.WriteStringLiteral(tagName)
|
||||
.WriteParameterSeparator()
|
||||
.Write(nameof(TagMode))
|
||||
.Write(".")
|
||||
.Write(tagMode.ToString())
|
||||
.WriteParameterSeparator()
|
||||
.WriteStringLiteral(GenerateUniqueId())
|
||||
.WriteParameterSeparator();
|
||||
|
||||
// We remove the target writer so TagHelper authors can retrieve content.
|
||||
var oldWriter = _context.TargetWriterName;
|
||||
_context.TargetWriterName = null;
|
||||
|
||||
using (_writer.BuildAsyncLambda(endLine: false))
|
||||
{
|
||||
// Render all of the tag helper children.
|
||||
_bodyVisitor.Accept(children);
|
||||
}
|
||||
|
||||
_context.TargetWriterName = oldWriter;
|
||||
|
||||
_writer.WriteParameterSeparator()
|
||||
.Write(_tagHelperContext.StartTagHelperWritingScopeMethodName)
|
||||
.WriteParameterSeparator()
|
||||
.Write(_tagHelperContext.EndTagHelperWritingScopeMethodName)
|
||||
.WriteEndMethodInvocation();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a unique ID for an HTML element.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A globally unique ID.
|
||||
/// </returns>
|
||||
protected virtual string GenerateUniqueId()
|
||||
{
|
||||
return Guid.NewGuid().ToString("N");
|
||||
}
|
||||
|
||||
private void RenderTagHelpersCreation(
|
||||
TagHelperChunk chunk,
|
||||
IEnumerable<TagHelperDescriptor> tagHelperDescriptors)
|
||||
{
|
||||
// This is to maintain value accessors for attributes when creating the TagHelpers.
|
||||
// Ultimately it enables us to do scenarios like this:
|
||||
// myTagHelper1.Foo = DateTime.Now;
|
||||
// myTagHelper2.Foo = myTagHelper1.Foo;
|
||||
var htmlAttributeValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var tagHelperDescriptor in tagHelperDescriptors)
|
||||
{
|
||||
var tagHelperVariableName = GetVariableName(tagHelperDescriptor);
|
||||
|
||||
// Create the tag helper
|
||||
_writer.WriteStartAssignment(tagHelperVariableName)
|
||||
.WriteStartMethodInvocation(_tagHelperContext.CreateTagHelperMethodName,
|
||||
tagHelperDescriptor.TypeName)
|
||||
.WriteEndMethodInvocation();
|
||||
|
||||
// Execution contexts and throwing errors for null dictionary properties are a runtime feature.
|
||||
if (_designTimeMode)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_writer.WriteInstanceMethodInvocation(
|
||||
ExecutionContextVariableName,
|
||||
_tagHelperContext.ExecutionContextAddMethodName,
|
||||
tagHelperVariableName);
|
||||
|
||||
// Track dictionary properties we have confirmed are non-null.
|
||||
var confirmedDictionaries = new HashSet<string>(StringComparer.Ordinal);
|
||||
|
||||
// Ensure that all created TagHelpers have initialized dictionary bound properties which are used
|
||||
// via TagHelper indexers.
|
||||
foreach (var chunkAttribute in chunk.Attributes)
|
||||
{
|
||||
var associatedAttributeDescriptor = tagHelperDescriptor.Attributes.FirstOrDefault(
|
||||
attributeDescriptor => attributeDescriptor.IsNameMatch(chunkAttribute.Key));
|
||||
|
||||
if (associatedAttributeDescriptor != null &&
|
||||
associatedAttributeDescriptor.IsIndexer &&
|
||||
confirmedDictionaries.Add(associatedAttributeDescriptor.PropertyName))
|
||||
{
|
||||
// Throw a reasonable Exception at runtime if the dictionary property is null.
|
||||
_writer
|
||||
.Write("if (")
|
||||
.Write(tagHelperVariableName)
|
||||
.Write(".")
|
||||
.Write(associatedAttributeDescriptor.PropertyName)
|
||||
.WriteLine(" == null)");
|
||||
using (_writer.BuildScope())
|
||||
{
|
||||
// System is in Host.NamespaceImports for all MVC scenarios. No need to generate FullName
|
||||
// of InvalidOperationException type.
|
||||
_writer
|
||||
.Write("throw ")
|
||||
.WriteStartNewObject(nameof(InvalidOperationException))
|
||||
.WriteStartMethodInvocation(_tagHelperContext.FormatInvalidIndexerAssignmentMethodName)
|
||||
.WriteStringLiteral(chunkAttribute.Key)
|
||||
.WriteParameterSeparator()
|
||||
.WriteStringLiteral(tagHelperDescriptor.TypeName)
|
||||
.WriteParameterSeparator()
|
||||
.WriteStringLiteral(associatedAttributeDescriptor.PropertyName)
|
||||
.WriteEndMethodInvocation(endLine: false) // End of method call
|
||||
.WriteEndMethodInvocation(endLine: true); // End of new expression / throw statement
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderAttributes(
|
||||
IList<KeyValuePair<string, Chunk>> chunkAttributes,
|
||||
IEnumerable<TagHelperDescriptor> tagHelperDescriptors)
|
||||
{
|
||||
var renderedBoundAttributeNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// Go through the HTML attributes in source order, assigning to properties or indexers or adding to
|
||||
// TagHelperExecutionContext.HTMLAttributes' as we go.
|
||||
foreach (var attribute in chunkAttributes)
|
||||
{
|
||||
var attributeName = attribute.Key;
|
||||
var attributeValueChunk = attribute.Value;
|
||||
var associatedDescriptors = tagHelperDescriptors.Where(descriptor =>
|
||||
descriptor.Attributes.Any(attributeDescriptor => attributeDescriptor.IsNameMatch(attributeName)));
|
||||
|
||||
// Bound attributes have associated descriptors. First attribute value wins if there are duplicates;
|
||||
// later values of duplicate bound attributes are treated as if they were unbound.
|
||||
if (associatedDescriptors.Any() && renderedBoundAttributeNames.Add(attributeName))
|
||||
{
|
||||
if (attributeValueChunk == null)
|
||||
{
|
||||
// Minimized attributes are not valid for bound attributes. TagHelperBlockRewriter has already
|
||||
// logged an error if it was a bound attribute; so we can skip.
|
||||
continue;
|
||||
}
|
||||
|
||||
// We need to capture the tag helper's property value accessor so we can retrieve it later
|
||||
// if there are more tag helpers that need the value.
|
||||
string valueAccessor = null;
|
||||
|
||||
foreach (var associatedDescriptor in associatedDescriptors)
|
||||
{
|
||||
var associatedAttributeDescriptor = associatedDescriptor.Attributes.First(
|
||||
attributeDescriptor => attributeDescriptor.IsNameMatch(attributeName));
|
||||
var tagHelperVariableName = GetVariableName(associatedDescriptor);
|
||||
|
||||
valueAccessor = RenderBoundAttribute(
|
||||
attributeName,
|
||||
attributeValueChunk,
|
||||
tagHelperVariableName,
|
||||
valueAccessor,
|
||||
associatedAttributeDescriptor);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RenderUnboundAttribute(attributeName, attributeValueChunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string RenderBoundAttribute(
|
||||
string attributeName,
|
||||
Chunk attributeValueChunk,
|
||||
string tagHelperVariableName,
|
||||
string previousValueAccessor,
|
||||
TagHelperAttributeDescriptor attributeDescriptor)
|
||||
{
|
||||
var currentValueAccessor = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0}.{1}",
|
||||
tagHelperVariableName,
|
||||
attributeDescriptor.PropertyName);
|
||||
|
||||
if (attributeDescriptor.IsIndexer)
|
||||
{
|
||||
var dictionaryKey = attributeName.Substring(attributeDescriptor.Name.Length);
|
||||
currentValueAccessor += $"[\"{dictionaryKey}\"]";
|
||||
}
|
||||
|
||||
// If this attribute value has not been seen before, need to record its value.
|
||||
if (previousValueAccessor == null)
|
||||
{
|
||||
// Bufferable attributes are attributes that can have Razor code inside of them. Such
|
||||
// attributes have string values and may be calculated using a temporary TextWriter or other
|
||||
// buffer.
|
||||
var bufferableAttribute = attributeDescriptor.IsStringProperty;
|
||||
|
||||
RenderNewAttributeValueAssignment(
|
||||
attributeDescriptor,
|
||||
bufferableAttribute,
|
||||
attributeValueChunk,
|
||||
currentValueAccessor);
|
||||
|
||||
if (_designTimeMode)
|
||||
{
|
||||
// Execution contexts are a runtime feature.
|
||||
return currentValueAccessor;
|
||||
}
|
||||
|
||||
// We need to inform the context of the attribute value.
|
||||
_writer
|
||||
.WriteStartInstanceMethodInvocation(
|
||||
ExecutionContextVariableName,
|
||||
_tagHelperContext.ExecutionContextAddTagHelperAttributeMethodName)
|
||||
.WriteStringLiteral(attributeName)
|
||||
.WriteParameterSeparator()
|
||||
.Write(currentValueAccessor)
|
||||
.WriteEndMethodInvocation();
|
||||
|
||||
return currentValueAccessor;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The attribute value has already been determined and accessor was passed to us as
|
||||
// previousValueAccessor, we don't want to evaluate the value twice so lets just use the
|
||||
// previousValueLocation.
|
||||
_writer
|
||||
.WriteStartAssignment(currentValueAccessor)
|
||||
.Write(previousValueAccessor)
|
||||
.WriteLine(";");
|
||||
|
||||
return previousValueAccessor;
|
||||
}
|
||||
}
|
||||
|
||||
// Render assignment of attribute value to the value accessor.
|
||||
private void RenderNewAttributeValueAssignment(
|
||||
TagHelperAttributeDescriptor attributeDescriptor,
|
||||
bool bufferableAttribute,
|
||||
Chunk attributeValueChunk,
|
||||
string valueAccessor)
|
||||
{
|
||||
// Plain text values are non Razor code (@DateTime.Now) values. If an attribute is bufferable it
|
||||
// may be more than just a plain text value, it may also contain Razor code which is why we attempt
|
||||
// to retrieve a plain text value here.
|
||||
string textValue;
|
||||
var isPlainTextValue = TryGetPlainTextValue(attributeValueChunk, out textValue);
|
||||
|
||||
if (bufferableAttribute)
|
||||
{
|
||||
if (!isPlainTextValue)
|
||||
{
|
||||
// If we haven't recorded a value and we need to buffer an attribute value and the value is not
|
||||
// plain text then we need to prepare the value prior to setting it below.
|
||||
BuildBufferedWritingScope(attributeValueChunk, htmlEncodeValues: false);
|
||||
}
|
||||
|
||||
_writer.WriteStartAssignment(valueAccessor);
|
||||
|
||||
if (isPlainTextValue)
|
||||
{
|
||||
// If the attribute is bufferable but has a plain text value that means the value
|
||||
// is a string which needs to be surrounded in quotes.
|
||||
RenderQuotedAttributeValue(textValue, attributeDescriptor);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The value contains more than plain text e.g. stringAttribute ="Time: @DateTime.Now".
|
||||
RenderBufferedAttributeValue(attributeDescriptor);
|
||||
}
|
||||
|
||||
_writer.WriteLine(";");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Write out simple assignment for non-string property value. Try to keep the whole
|
||||
// statement together and the #line pragma correct to make debugging possible.
|
||||
using (var lineMapper = new CSharpLineMappingWriter(
|
||||
_writer,
|
||||
attributeValueChunk.Association.Start,
|
||||
_context.SourceFile))
|
||||
{
|
||||
// Place the assignment LHS to align RHS with original attribute value's indentation.
|
||||
// Unfortunately originalIndent is incorrect if original line contains tabs. Unable to
|
||||
// use a CSharpPaddingBuilder because the Association has no Previous node; lost the
|
||||
// original Span sequence when the parse tree was rewritten.
|
||||
var originalIndent = attributeValueChunk.Start.CharacterIndex;
|
||||
var generatedLength = valueAccessor.Length + " = ".Length;
|
||||
var newIndent = originalIndent - generatedLength;
|
||||
if (newIndent > 0)
|
||||
{
|
||||
_writer.Indent(newIndent);
|
||||
}
|
||||
|
||||
_writer.WriteStartAssignment(valueAccessor);
|
||||
lineMapper.MarkLineMappingStart();
|
||||
|
||||
// Write out bare expression for this attribute value. Property is not a string.
|
||||
// So quoting or buffering are not helpful.
|
||||
RenderRawAttributeValue(attributeValueChunk, attributeDescriptor, isPlainTextValue);
|
||||
|
||||
// End the assignment to the attribute.
|
||||
lineMapper.MarkLineMappingEnd();
|
||||
_writer.WriteLine(";");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderUnboundAttribute(string attributeName, Chunk attributeValueChunk)
|
||||
{
|
||||
// Render children to provide IntelliSense at design time. No need for the execution context logic, it's
|
||||
// a runtime feature.
|
||||
if (_designTimeMode)
|
||||
{
|
||||
if (attributeValueChunk != null)
|
||||
{
|
||||
_bodyVisitor.Accept(attributeValueChunk);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have a minimized attribute there is no value
|
||||
if (attributeValueChunk == null)
|
||||
{
|
||||
_writer
|
||||
.WriteStartInstanceMethodInvocation(
|
||||
ExecutionContextVariableName,
|
||||
_tagHelperContext.ExecutionContextAddMinimizedHtmlAttributeMethodName)
|
||||
.WriteStringLiteral(attributeName)
|
||||
.WriteEndMethodInvocation();
|
||||
}
|
||||
else
|
||||
{
|
||||
string textValue = null;
|
||||
var isPlainTextValue = TryGetPlainTextValue(attributeValueChunk, out textValue);
|
||||
|
||||
if (isPlainTextValue)
|
||||
{
|
||||
// If it's a plain text value then we need to surround the value with quotes.
|
||||
_writer
|
||||
.WriteStartInstanceMethodInvocation(
|
||||
ExecutionContextVariableName,
|
||||
_tagHelperContext.ExecutionContextAddHtmlAttributeMethodName)
|
||||
.WriteStringLiteral(attributeName)
|
||||
.WriteParameterSeparator()
|
||||
.WriteStartMethodInvocation(_tagHelperContext.MarkAsHtmlEncodedMethodName)
|
||||
.WriteStringLiteral(textValue)
|
||||
.WriteEndMethodInvocation(endLine: false)
|
||||
.WriteEndMethodInvocation();
|
||||
}
|
||||
else if (IsDynamicAttributeValue(attributeValueChunk))
|
||||
{
|
||||
// Dynamic attribute value should be run through the conditional attribute removal system. It's
|
||||
// unbound and contains C#.
|
||||
|
||||
// TagHelper attribute rendering is buffered by default. We do not want to write to the current
|
||||
// writer.
|
||||
var currentTargetWriter = _context.TargetWriterName;
|
||||
var currentWriteAttributeMethodName = _context.Host.GeneratedClassContext.WriteAttributeValueMethodName;
|
||||
_context.TargetWriterName = null;
|
||||
|
||||
Debug.Assert(attributeValueChunk is ParentChunk);
|
||||
var children = ((ParentChunk)attributeValueChunk).Children;
|
||||
var attributeCount = children.Count(c => c is DynamicCodeAttributeChunk || c is LiteralCodeAttributeChunk);
|
||||
|
||||
_writer
|
||||
.WriteStartMethodInvocation(_tagHelperContext.BeginAddHtmlAttributeValuesMethodName)
|
||||
.Write(ExecutionContextVariableName)
|
||||
.WriteParameterSeparator()
|
||||
.WriteStringLiteral(attributeName)
|
||||
.WriteParameterSeparator()
|
||||
.Write(attributeCount.ToString(CultureInfo.InvariantCulture))
|
||||
.WriteEndMethodInvocation();
|
||||
|
||||
_attributeCodeVisitor.Accept(attributeValueChunk);
|
||||
|
||||
_writer.WriteMethodInvocation(
|
||||
_tagHelperContext.EndAddHtmlAttributeValuesMethodName,
|
||||
ExecutionContextVariableName);
|
||||
|
||||
_context.TargetWriterName = currentTargetWriter;
|
||||
}
|
||||
else
|
||||
{
|
||||
// HTML attributes are always strings. This attribute contains C# but is not dynamic. This occurs
|
||||
// when the attribute is a data-* attribute.
|
||||
|
||||
// Attribute value is not plain text, must be buffered to determine its final value.
|
||||
BuildBufferedWritingScope(attributeValueChunk, htmlEncodeValues: true);
|
||||
|
||||
_writer
|
||||
.WriteStartInstanceMethodInvocation(
|
||||
ExecutionContextVariableName,
|
||||
_tagHelperContext.ExecutionContextAddHtmlAttributeMethodName)
|
||||
.WriteStringLiteral(attributeName)
|
||||
.WriteParameterSeparator()
|
||||
.WriteStartMethodInvocation(_tagHelperContext.MarkAsHtmlEncodedMethodName);
|
||||
|
||||
RenderBufferedAttributeValueAccessor(_writer);
|
||||
|
||||
_writer
|
||||
.WriteEndMethodInvocation(endLine: false)
|
||||
.WriteEndMethodInvocation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderEndTagHelpersScope()
|
||||
{
|
||||
_writer.WriteStartAssignment(ExecutionContextVariableName)
|
||||
.WriteInstanceMethodInvocation(ScopeManagerVariableName,
|
||||
_tagHelperContext.ScopeManagerEndMethodName);
|
||||
}
|
||||
|
||||
private void RenderWriteTagHelperMethodCall(TagHelperChunk chunk)
|
||||
{
|
||||
_writer
|
||||
.WriteStartInstrumentationContext(_context, chunk.Association, isLiteral: false)
|
||||
.Write("await ");
|
||||
|
||||
if (!string.IsNullOrEmpty(_context.TargetWriterName))
|
||||
{
|
||||
_writer
|
||||
.WriteStartMethodInvocation(_tagHelperContext.WriteTagHelperToAsyncMethodName)
|
||||
.Write(_context.TargetWriterName)
|
||||
.WriteParameterSeparator();
|
||||
}
|
||||
else
|
||||
{
|
||||
_writer.WriteStartMethodInvocation(_tagHelperContext.WriteTagHelperAsyncMethodName);
|
||||
}
|
||||
|
||||
_writer
|
||||
.Write(ExecutionContextVariableName)
|
||||
.WriteEndMethodInvocation()
|
||||
.WriteEndInstrumentationContext(_context);
|
||||
}
|
||||
|
||||
private void RenderRunTagHelpers()
|
||||
{
|
||||
_writer.Write(ExecutionContextVariableName)
|
||||
.Write(".")
|
||||
.WriteStartAssignment(_tagHelperContext.ExecutionContextOutputPropertyName)
|
||||
.Write("await ")
|
||||
.WriteStartInstanceMethodInvocation(RunnerVariableName,
|
||||
_tagHelperContext.RunnerRunAsyncMethodName);
|
||||
|
||||
_writer.Write(ExecutionContextVariableName)
|
||||
.WriteEndMethodInvocation();
|
||||
}
|
||||
|
||||
private void RenderBufferedAttributeValue(TagHelperAttributeDescriptor attributeDescriptor)
|
||||
{
|
||||
// Pass complexValue: false because variable.ToString() replaces any original complexity in the expression.
|
||||
RenderAttributeValue(
|
||||
attributeDescriptor,
|
||||
valueRenderer: (writer) =>
|
||||
{
|
||||
RenderBufferedAttributeValueAccessor(writer);
|
||||
},
|
||||
complexValue: false);
|
||||
}
|
||||
|
||||
private void RenderRawAttributeValue(
|
||||
Chunk attributeValueChunk,
|
||||
TagHelperAttributeDescriptor attributeDescriptor,
|
||||
bool isPlainTextValue)
|
||||
{
|
||||
RenderAttributeValue(
|
||||
attributeDescriptor,
|
||||
valueRenderer: (writer) =>
|
||||
{
|
||||
var visitor =
|
||||
new CSharpTagHelperAttributeValueVisitor(writer, _context, attributeDescriptor.TypeName);
|
||||
visitor.Accept(attributeValueChunk);
|
||||
},
|
||||
complexValue: !isPlainTextValue);
|
||||
}
|
||||
|
||||
private void RenderQuotedAttributeValue(string value, TagHelperAttributeDescriptor attributeDescriptor)
|
||||
{
|
||||
RenderAttributeValue(
|
||||
attributeDescriptor,
|
||||
valueRenderer: (writer) =>
|
||||
{
|
||||
writer.WriteStringLiteral(value);
|
||||
},
|
||||
complexValue: false);
|
||||
}
|
||||
|
||||
// Render a buffered writing scope for the HTML attribute value.
|
||||
private void BuildBufferedWritingScope(Chunk htmlAttributeChunk, bool htmlEncodeValues)
|
||||
{
|
||||
// We're building a writing scope around the provided chunks which captures everything written from the
|
||||
// page. Therefore, we do not want to write to any other buffer since we're using the pages buffer to
|
||||
// ensure we capture all content that's written, directly or indirectly.
|
||||
var oldWriter = _context.TargetWriterName;
|
||||
_context.TargetWriterName = null;
|
||||
|
||||
// Need to disable instrumentation inside of writing scopes, the instrumentation will not detect
|
||||
// content written inside writing scopes.
|
||||
var oldInstrumentation = _context.Host.EnableInstrumentation;
|
||||
|
||||
try
|
||||
{
|
||||
_context.Host.EnableInstrumentation = false;
|
||||
|
||||
// Scopes are a runtime feature.
|
||||
if (!_designTimeMode)
|
||||
{
|
||||
_writer.WriteMethodInvocation(_tagHelperContext.StartTagHelperWritingScopeMethodName);
|
||||
}
|
||||
|
||||
var visitor = htmlEncodeValues ? _bodyVisitor : _literalBodyVisitor;
|
||||
visitor.Accept(htmlAttributeChunk);
|
||||
|
||||
// Scopes are a runtime feature.
|
||||
if (!_designTimeMode)
|
||||
{
|
||||
_writer.WriteStartAssignment(StringValueBufferVariableName)
|
||||
.WriteMethodInvocation(_tagHelperContext.EndTagHelperWritingScopeMethodName);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Reset instrumentation back to what it was, leaving the writing scope.
|
||||
_context.Host.EnableInstrumentation = oldInstrumentation;
|
||||
|
||||
// Reset the writer/buffer back to what it was, leaving the writing scope.
|
||||
_context.TargetWriterName = oldWriter;
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderAttributeValue(TagHelperAttributeDescriptor attributeDescriptor,
|
||||
Action<CSharpCodeWriter> valueRenderer,
|
||||
bool complexValue)
|
||||
{
|
||||
AttributeValueCodeRenderer.RenderAttributeValue(
|
||||
attributeDescriptor,
|
||||
_writer,
|
||||
_context,
|
||||
valueRenderer,
|
||||
complexValue);
|
||||
}
|
||||
|
||||
private void RenderBufferedAttributeValueAccessor(CSharpCodeWriter writer)
|
||||
{
|
||||
if (_designTimeMode)
|
||||
{
|
||||
// There is no value buffer in design time mode but we still want to write out a value. We write a
|
||||
// value to ensure the tag helper's property type is string.
|
||||
writer.Write("string.Empty");
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteInstanceMethodInvocation(
|
||||
StringValueBufferVariableName,
|
||||
_tagHelperContext.TagHelperContentGetContentMethodName,
|
||||
endLine: false,
|
||||
parameters: new string[] { _tagHelperContext.HtmlEncoderPropertyName });
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsDynamicAttributeValue(Chunk attributeValueChunk)
|
||||
{
|
||||
var parentChunk = attributeValueChunk as ParentChunk;
|
||||
if (parentChunk != null)
|
||||
{
|
||||
return parentChunk.Children.Any(child => child is DynamicCodeAttributeChunk);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryGetPlainTextValue(Chunk chunk, out string plainText)
|
||||
{
|
||||
var parentChunk = chunk as ParentChunk;
|
||||
|
||||
plainText = null;
|
||||
|
||||
if (parentChunk == null || parentChunk.Children.Count != 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var literalChildChunk = parentChunk.Children[0] as LiteralChunk;
|
||||
|
||||
if (literalChildChunk == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
plainText = literalChildChunk.Text;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// A CSharpCodeVisitor which does not HTML encode values. Used when rendering bound string attribute values.
|
||||
private class CSharpLiteralCodeVisitor : CSharpCodeVisitor
|
||||
{
|
||||
public CSharpLiteralCodeVisitor(
|
||||
CSharpTagHelperCodeRenderer tagHelperRenderer,
|
||||
CSharpCodeWriter writer,
|
||||
CodeGeneratorContext context)
|
||||
: base(writer, context)
|
||||
{
|
||||
// Ensure that no matter how this class is used, we don't create numerous CSharpTagHelperCodeRenderer
|
||||
// instances.
|
||||
TagHelperRenderer = tagHelperRenderer;
|
||||
}
|
||||
|
||||
protected override string WriteMethodName
|
||||
{
|
||||
get
|
||||
{
|
||||
return Context.Host.GeneratedClassContext.WriteLiteralMethodName;
|
||||
}
|
||||
}
|
||||
|
||||
protected override string WriteToMethodName
|
||||
{
|
||||
get
|
||||
{
|
||||
return Context.Host.GeneratedClassContext.WriteLiteralToMethodName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class TagHelperAttributeCodeVisitor : CSharpCodeVisitor
|
||||
{
|
||||
public TagHelperAttributeCodeVisitor(
|
||||
CSharpCodeWriter writer,
|
||||
CodeGeneratorContext context)
|
||||
: base(writer, context)
|
||||
{
|
||||
}
|
||||
|
||||
protected override string WriteAttributeValueMethodName =>
|
||||
Context.Host.GeneratedClassContext.GeneratedTagHelperContext.AddHtmlAttributeValueMethodName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||
{
|
||||
public abstract class CodeGenerator
|
||||
{
|
||||
private readonly CodeGeneratorContext _context;
|
||||
|
||||
public CodeGenerator(CodeGeneratorContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
protected CodeGeneratorContext Context
|
||||
{
|
||||
get { return _context; }
|
||||
}
|
||||
|
||||
public abstract CodeGeneratorResult Generate();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Razor.Chunks.Generators;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||
{
|
||||
/// <summary>
|
||||
/// Context object with information used to generate a Razor page.
|
||||
/// </summary>
|
||||
public class CodeGeneratorContext : ChunkGeneratorContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Instantiates a new instance of the <see cref="CodeGeneratorContext"/> object.
|
||||
/// </summary>
|
||||
/// <param name="generatorContext">A <see cref="ChunkGeneratorContext"/> to copy information from.</param>
|
||||
/// <param name="errorSink">
|
||||
/// The <see cref="ErrorSink"/> used to collect <see cref="RazorError"/>s encountered
|
||||
/// when parsing the current Razor document.
|
||||
/// </param>
|
||||
public CodeGeneratorContext(ChunkGeneratorContext generatorContext, ErrorSink errorSink)
|
||||
: base(generatorContext)
|
||||
{
|
||||
ErrorSink = errorSink;
|
||||
ExpressionRenderingMode = ExpressionRenderingMode.WriteToOutput;
|
||||
}
|
||||
|
||||
// Internal for testing.
|
||||
internal CodeGeneratorContext(
|
||||
RazorEngineHost host,
|
||||
string className,
|
||||
string rootNamespace,
|
||||
string sourceFile,
|
||||
bool shouldGenerateLinePragmas,
|
||||
ErrorSink errorSink)
|
||||
: base(host, className, rootNamespace, sourceFile, shouldGenerateLinePragmas)
|
||||
{
|
||||
ErrorSink = errorSink;
|
||||
ExpressionRenderingMode = ExpressionRenderingMode.WriteToOutput;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The current C# rendering mode.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="ExpressionRenderingMode.WriteToOutput"/> forces C# generation to write
|
||||
/// <see cref="Chunks.Chunk"/>s to the output page, i.e. WriteLiteral("Hello World").
|
||||
/// <see cref="ExpressionRenderingMode.InjectCode"/> writes <see cref="Chunks.Chunk"/> values in their
|
||||
/// rawest form, i.g. "Hello World".
|
||||
/// </remarks>
|
||||
public ExpressionRenderingMode ExpressionRenderingMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The C# writer to write <see cref="Chunks.Chunk"/> information to.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If <see cref="TargetWriterName"/> is <c>null</c> values will be written using a default write method
|
||||
/// i.e. WriteLiteral("Hello World").
|
||||
/// If <see cref="TargetWriterName"/> is not <c>null</c> values will be written to the given
|
||||
/// <see cref="TargetWriterName"/>, i.e. WriteLiteralTo(myWriter, "Hello World").
|
||||
/// </remarks>
|
||||
public string TargetWriterName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <c>SHA1</c> based checksum for the file whose location is defined by
|
||||
/// <see cref="ChunkGeneratorContext.SourceFile"/>.
|
||||
/// </summary>
|
||||
public string Checksum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used to aggregate <see cref="RazorError"/>s.
|
||||
/// </summary>
|
||||
public ErrorSink ErrorSink { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||
{
|
||||
public class CodeGeneratorResult
|
||||
{
|
||||
public CodeGeneratorResult(string code, IList<LineMapping> designTimeLineMappings)
|
||||
{
|
||||
Code = code;
|
||||
DesignTimeLineMappings = designTimeLineMappings;
|
||||
}
|
||||
|
||||
public string Code { get; private set; }
|
||||
public IList<LineMapping> DesignTimeLineMappings { get; private set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,216 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||
{
|
||||
public class CodeWriter : IDisposable
|
||||
{
|
||||
private static readonly char[] NewLineCharacters = new char[] { '\r', '\n' };
|
||||
private readonly StringWriter _writer = new StringWriter();
|
||||
private bool _newLine;
|
||||
private string _cache = string.Empty;
|
||||
private bool _dirty = false;
|
||||
|
||||
private int _absoluteIndex;
|
||||
private int _currentLineIndex;
|
||||
private int _currentLineCharacterIndex;
|
||||
|
||||
public string LastWrite { get; private set; }
|
||||
|
||||
public int CurrentIndent { get; private set; }
|
||||
|
||||
public string NewLine
|
||||
{
|
||||
get
|
||||
{
|
||||
return _writer.NewLine;
|
||||
}
|
||||
set
|
||||
{
|
||||
_writer.NewLine = value;
|
||||
}
|
||||
}
|
||||
|
||||
public CodeWriter ResetIndent()
|
||||
{
|
||||
return SetIndent(0);
|
||||
}
|
||||
|
||||
public CodeWriter IncreaseIndent(int size)
|
||||
{
|
||||
CurrentIndent += size;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter DecreaseIndent(int size)
|
||||
{
|
||||
CurrentIndent -= size;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter SetIndent(int size)
|
||||
{
|
||||
CurrentIndent = size;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter Indent(int size)
|
||||
{
|
||||
if (_newLine)
|
||||
{
|
||||
_writer.Write(new string(' ', size));
|
||||
Flush();
|
||||
|
||||
_currentLineCharacterIndex += size;
|
||||
_absoluteIndex += size;
|
||||
|
||||
_dirty = true;
|
||||
_newLine = false;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter Write(string data)
|
||||
{
|
||||
Indent(CurrentIndent);
|
||||
|
||||
_writer.Write(data);
|
||||
Flush();
|
||||
|
||||
LastWrite = data;
|
||||
_dirty = true;
|
||||
_newLine = false;
|
||||
|
||||
if (data == null || data.Length == 0)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
_absoluteIndex += data.Length;
|
||||
|
||||
// The data string might contain a partial newline where the previously
|
||||
// written string has part of the newline.
|
||||
var i = 0;
|
||||
int? trailingPartStart = null;
|
||||
var builder = _writer.GetStringBuilder();
|
||||
|
||||
if (
|
||||
// Check the last character of the previous write operation.
|
||||
builder.Length - data.Length - 1 >= 0 &&
|
||||
builder[builder.Length - data.Length - 1] == '\r' &&
|
||||
|
||||
// Check the first character of the current write operation.
|
||||
builder[builder.Length - data.Length] == '\n')
|
||||
{
|
||||
// This is newline that's spread across two writes. Skip the first character of the
|
||||
// current write operation.
|
||||
//
|
||||
// We don't need to increment our newline counter because we already did that when we
|
||||
// saw the \r.
|
||||
i += 1;
|
||||
trailingPartStart = 1;
|
||||
}
|
||||
|
||||
// Iterate the string, stopping at each occurrence of a newline character. This lets us count the
|
||||
// newline occurrences and keep the index of the last one.
|
||||
while ((i = data.IndexOfAny(NewLineCharacters, i)) >= 0)
|
||||
{
|
||||
// Newline found.
|
||||
_currentLineIndex++;
|
||||
_currentLineCharacterIndex = 0;
|
||||
|
||||
i++;
|
||||
|
||||
// We might have stopped at a \r, so check if it's followed by \n and then advance the index to
|
||||
// start the next search after it.
|
||||
if (data.Length > i &&
|
||||
data[i - 1] == '\r' &&
|
||||
data[i] == '\n')
|
||||
{
|
||||
i++;
|
||||
}
|
||||
|
||||
// The 'suffix' of the current line starts after this newline token.
|
||||
trailingPartStart = i;
|
||||
}
|
||||
|
||||
if (trailingPartStart == null)
|
||||
{
|
||||
// No newlines, just add the length of the data buffer
|
||||
_currentLineCharacterIndex += data.Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Newlines found, add the trailing part of 'data'
|
||||
_currentLineCharacterIndex += (data.Length - trailingPartStart.Value);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter WriteLine()
|
||||
{
|
||||
LastWrite = _writer.NewLine;
|
||||
|
||||
_writer.WriteLine();
|
||||
Flush();
|
||||
|
||||
_currentLineIndex++;
|
||||
_currentLineCharacterIndex = 0;
|
||||
_absoluteIndex += _writer.NewLine.Length;
|
||||
|
||||
_dirty = true;
|
||||
_newLine = true;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter WriteLine(string data)
|
||||
{
|
||||
return Write(data).WriteLine();
|
||||
}
|
||||
|
||||
public CodeWriter Flush()
|
||||
{
|
||||
_writer.Flush();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public string GenerateCode()
|
||||
{
|
||||
if (_dirty)
|
||||
{
|
||||
_cache = _writer.ToString();
|
||||
_dirty = false;
|
||||
}
|
||||
|
||||
return _cache;
|
||||
}
|
||||
|
||||
public SourceLocation GetCurrentSourceLocation()
|
||||
{
|
||||
return new SourceLocation(_absoluteIndex, _currentLineIndex, _currentLineCharacterIndex);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_writer.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||
{
|
||||
public enum ExpressionRenderingMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that expressions should be written to the output stream
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// If @foo is rendered with WriteToOutput, the code generator would output the following code:
|
||||
///
|
||||
/// Write(foo);
|
||||
/// </example>
|
||||
WriteToOutput,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that expressions should simply be placed as-is in the code, and the context in which
|
||||
/// the code exists will be used to render it
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// If @foo is rendered with InjectCode, the code generator would output the following code:
|
||||
///
|
||||
/// foo
|
||||
/// </example>
|
||||
InjectCode
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,219 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||
{
|
||||
public struct GeneratedClassContext
|
||||
{
|
||||
public static readonly string DefaultWriteMethodName = "Write";
|
||||
public static readonly string DefaultWriteLiteralMethodName = "WriteLiteral";
|
||||
public static readonly string DefaultExecuteMethodName = "ExecuteAsync";
|
||||
public static readonly string DefaultBeginWriteAttributeMethodName = "BeginWriteAttribute";
|
||||
public static readonly string DefaultBeginWriteAttributeToMethodName = "BeginWriteAttributeTo";
|
||||
public static readonly string DefaultEndWriteAttributeMethodName = "EndWriteAttribute";
|
||||
public static readonly string DefaultEndWriteAttributeToMethodName = "EndWriteAttributeTo";
|
||||
public static readonly string DefaultWriteAttributeValueMethodName = "WriteAttributeValue";
|
||||
public static readonly string DefaultWriteAttributeValueToMethodName = "WriteAttributeValueTo";
|
||||
|
||||
public static readonly GeneratedClassContext Default =
|
||||
new GeneratedClassContext(
|
||||
DefaultExecuteMethodName,
|
||||
DefaultWriteMethodName,
|
||||
DefaultWriteLiteralMethodName,
|
||||
new GeneratedTagHelperContext());
|
||||
|
||||
public GeneratedClassContext(
|
||||
string executeMethodName,
|
||||
string writeMethodName,
|
||||
string writeLiteralMethodName,
|
||||
GeneratedTagHelperContext generatedTagHelperContext)
|
||||
: this()
|
||||
{
|
||||
if (generatedTagHelperContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(generatedTagHelperContext));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(executeMethodName))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
CommonResources.Argument_Cannot_Be_Null_Or_Empty,
|
||||
nameof(executeMethodName));
|
||||
}
|
||||
if (string.IsNullOrEmpty(writeMethodName))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
CommonResources.Argument_Cannot_Be_Null_Or_Empty,
|
||||
nameof(writeMethodName));
|
||||
}
|
||||
if (string.IsNullOrEmpty(writeLiteralMethodName))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
CommonResources.Argument_Cannot_Be_Null_Or_Empty,
|
||||
nameof(writeLiteralMethodName));
|
||||
}
|
||||
|
||||
GeneratedTagHelperContext = generatedTagHelperContext;
|
||||
|
||||
WriteMethodName = writeMethodName;
|
||||
WriteLiteralMethodName = writeLiteralMethodName;
|
||||
ExecuteMethodName = executeMethodName;
|
||||
|
||||
WriteToMethodName = null;
|
||||
WriteLiteralToMethodName = null;
|
||||
TemplateTypeName = null;
|
||||
DefineSectionMethodName = null;
|
||||
|
||||
BeginWriteAttributeMethodName = DefaultBeginWriteAttributeMethodName;
|
||||
BeginWriteAttributeToMethodName = DefaultBeginWriteAttributeToMethodName;
|
||||
EndWriteAttributeMethodName = DefaultEndWriteAttributeMethodName;
|
||||
EndWriteAttributeToMethodName = DefaultEndWriteAttributeToMethodName;
|
||||
WriteAttributeValueMethodName = DefaultWriteAttributeValueMethodName;
|
||||
WriteAttributeValueToMethodName = DefaultWriteAttributeValueToMethodName;
|
||||
}
|
||||
|
||||
public GeneratedClassContext(
|
||||
string executeMethodName,
|
||||
string writeMethodName,
|
||||
string writeLiteralMethodName,
|
||||
string writeToMethodName,
|
||||
string writeLiteralToMethodName,
|
||||
string templateTypeName,
|
||||
GeneratedTagHelperContext generatedTagHelperContext)
|
||||
: this(executeMethodName,
|
||||
writeMethodName,
|
||||
writeLiteralMethodName,
|
||||
generatedTagHelperContext)
|
||||
{
|
||||
WriteToMethodName = writeToMethodName;
|
||||
WriteLiteralToMethodName = writeLiteralToMethodName;
|
||||
TemplateTypeName = templateTypeName;
|
||||
}
|
||||
|
||||
public GeneratedClassContext(
|
||||
string executeMethodName,
|
||||
string writeMethodName,
|
||||
string writeLiteralMethodName,
|
||||
string writeToMethodName,
|
||||
string writeLiteralToMethodName,
|
||||
string templateTypeName,
|
||||
string defineSectionMethodName,
|
||||
GeneratedTagHelperContext generatedTagHelperContext)
|
||||
: this(executeMethodName,
|
||||
writeMethodName,
|
||||
writeLiteralMethodName,
|
||||
writeToMethodName,
|
||||
writeLiteralToMethodName,
|
||||
templateTypeName,
|
||||
generatedTagHelperContext)
|
||||
{
|
||||
DefineSectionMethodName = defineSectionMethodName;
|
||||
}
|
||||
|
||||
public GeneratedClassContext(
|
||||
string executeMethodName,
|
||||
string writeMethodName,
|
||||
string writeLiteralMethodName,
|
||||
string writeToMethodName,
|
||||
string writeLiteralToMethodName,
|
||||
string templateTypeName,
|
||||
string defineSectionMethodName,
|
||||
string beginContextMethodName,
|
||||
string endContextMethodName,
|
||||
GeneratedTagHelperContext generatedTagHelperContext)
|
||||
: this(executeMethodName,
|
||||
writeMethodName,
|
||||
writeLiteralMethodName,
|
||||
writeToMethodName,
|
||||
writeLiteralToMethodName,
|
||||
templateTypeName,
|
||||
defineSectionMethodName,
|
||||
generatedTagHelperContext)
|
||||
{
|
||||
BeginContextMethodName = beginContextMethodName;
|
||||
EndContextMethodName = endContextMethodName;
|
||||
}
|
||||
|
||||
// Required Items
|
||||
public string WriteMethodName { get; }
|
||||
public string WriteLiteralMethodName { get; }
|
||||
public string WriteToMethodName { get; }
|
||||
public string WriteLiteralToMethodName { get; }
|
||||
public string ExecuteMethodName { get; }
|
||||
public GeneratedTagHelperContext GeneratedTagHelperContext { get; }
|
||||
|
||||
// Optional Items
|
||||
public string BeginContextMethodName { get; set; }
|
||||
public string EndContextMethodName { get; set; }
|
||||
public string DefineSectionMethodName { get; set; }
|
||||
public string TemplateTypeName { get; set; }
|
||||
|
||||
public string BeginWriteAttributeMethodName { get; set; }
|
||||
public string BeginWriteAttributeToMethodName { get; set; }
|
||||
public string EndWriteAttributeMethodName { get; set; }
|
||||
public string EndWriteAttributeToMethodName { get; set; }
|
||||
public string WriteAttributeValueMethodName { get; set; }
|
||||
public string WriteAttributeValueToMethodName { get; set; }
|
||||
|
||||
public bool AllowSections
|
||||
{
|
||||
get { return !string.IsNullOrEmpty(DefineSectionMethodName); }
|
||||
}
|
||||
|
||||
public bool AllowTemplates
|
||||
{
|
||||
get { return !string.IsNullOrEmpty(TemplateTypeName); }
|
||||
}
|
||||
|
||||
public bool SupportsInstrumentation
|
||||
{
|
||||
get { return !string.IsNullOrEmpty(BeginContextMethodName) && !string.IsNullOrEmpty(EndContextMethodName); }
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (!(obj is GeneratedClassContext))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var other = (GeneratedClassContext)obj;
|
||||
return string.Equals(DefineSectionMethodName, other.DefineSectionMethodName, StringComparison.Ordinal) &&
|
||||
string.Equals(WriteMethodName, other.WriteMethodName, StringComparison.Ordinal) &&
|
||||
string.Equals(WriteLiteralMethodName, other.WriteLiteralMethodName, StringComparison.Ordinal) &&
|
||||
string.Equals(WriteToMethodName, other.WriteToMethodName, StringComparison.Ordinal) &&
|
||||
string.Equals(WriteLiteralToMethodName, other.WriteLiteralToMethodName, StringComparison.Ordinal) &&
|
||||
string.Equals(ExecuteMethodName, other.ExecuteMethodName, StringComparison.Ordinal) &&
|
||||
string.Equals(TemplateTypeName, other.TemplateTypeName, StringComparison.Ordinal) &&
|
||||
string.Equals(BeginContextMethodName, other.BeginContextMethodName, StringComparison.Ordinal) &&
|
||||
string.Equals(EndContextMethodName, other.EndContextMethodName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
// Hash code should include only immutable properties.
|
||||
var hashCodeCombiner = HashCodeCombiner.Start();
|
||||
|
||||
hashCodeCombiner.Add(WriteMethodName, StringComparer.Ordinal);
|
||||
hashCodeCombiner.Add(WriteLiteralMethodName, StringComparer.Ordinal);
|
||||
hashCodeCombiner.Add(WriteToMethodName, StringComparer.Ordinal);
|
||||
hashCodeCombiner.Add(WriteLiteralToMethodName, StringComparer.Ordinal);
|
||||
hashCodeCombiner.Add(ExecuteMethodName, StringComparer.Ordinal);
|
||||
|
||||
return hashCodeCombiner;
|
||||
}
|
||||
|
||||
public static bool operator ==(GeneratedClassContext left, GeneratedClassContext right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(GeneratedClassContext left, GeneratedClassContext right)
|
||||
{
|
||||
return !left.Equals(right);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains necessary information for the tag helper code generation process.
|
||||
/// </summary>
|
||||
public class GeneratedTagHelperContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Instantiates a new instance of the <see cref="GeneratedTagHelperContext"/> with default values.
|
||||
/// </summary>
|
||||
public GeneratedTagHelperContext()
|
||||
{
|
||||
BeginAddHtmlAttributeValuesMethodName = "BeginAddHtmlAttributeValues";
|
||||
EndAddHtmlAttributeValuesMethodName = "EndAddHtmlAttributeValues";
|
||||
AddHtmlAttributeValueMethodName = "AddHtmlAttributeValue";
|
||||
CreateTagHelperMethodName = "CreateTagHelper";
|
||||
RunnerRunAsyncMethodName = "RunAsync";
|
||||
ScopeManagerBeginMethodName = "Begin";
|
||||
ScopeManagerEndMethodName = "End";
|
||||
ExecutionContextAddMethodName = "Add";
|
||||
ExecutionContextAddTagHelperAttributeMethodName = "AddTagHelperAttribute";
|
||||
ExecutionContextAddMinimizedHtmlAttributeMethodName = "AddMinimizedHtmlAttribute";
|
||||
ExecutionContextAddHtmlAttributeMethodName = "AddHtmlAttribute";
|
||||
ExecutionContextOutputPropertyName = "Output";
|
||||
FormatInvalidIndexerAssignmentMethodName = "FormatInvalidIndexerAssignment";
|
||||
MarkAsHtmlEncodedMethodName = "Html.Raw";
|
||||
StartTagHelperWritingScopeMethodName = "StartTagHelperWritingScope";
|
||||
EndTagHelperWritingScopeMethodName = "EndTagHelperWritingScope";
|
||||
RunnerTypeName = "TagHelperRunner";
|
||||
ScopeManagerTypeName = "TagHelperScopeManager";
|
||||
ExecutionContextTypeName = "TagHelperExecutionContext";
|
||||
TagHelperContentTypeName = "TagHelperContent";
|
||||
WriteTagHelperAsyncMethodName = "WriteTagHelperAsync";
|
||||
WriteTagHelperToAsyncMethodName = "WriteTagHelperToAsync";
|
||||
TagHelperContentGetContentMethodName = "GetContent";
|
||||
HtmlEncoderPropertyName = "HtmlEncoder";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The name of the method used to begin the addition of unbound, complex tag helper attributes to
|
||||
/// TagHelperExecutionContexts.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Method signature should be
|
||||
/// <code>
|
||||
/// public void BeginAddHtmlAttributeValues(
|
||||
/// TagHelperExecutionContext executionContext,
|
||||
/// string attributeName)
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public string BeginAddHtmlAttributeValuesMethodName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Method name used to end addition of unbound, complex tag helper attributes to TagHelperExecutionContexts.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Method signature should be
|
||||
/// <code>
|
||||
/// public void EndAddHtmlAttributeValues(
|
||||
/// TagHelperExecutionContext executionContext)
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public string EndAddHtmlAttributeValuesMethodName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Method name used to add individual components of an unbound, complex tag helper attribute to
|
||||
/// TagHelperExecutionContexts.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Method signature:
|
||||
/// <code>
|
||||
/// public void AddHtmlAttributeValues(
|
||||
/// string prefix,
|
||||
/// int prefixOffset,
|
||||
/// string value,
|
||||
/// int valueOffset,
|
||||
/// int valueLength,
|
||||
/// bool isLiteral)
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public string AddHtmlAttributeValueMethodName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the method used to create a tag helper.
|
||||
/// </summary>
|
||||
public string CreateTagHelperMethodName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the <see cref="RunnerTypeName"/> method used to run tag helpers.
|
||||
/// </summary>
|
||||
public string RunnerRunAsyncMethodName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the <see cref="ExecutionContextTypeName"/> method used to start a scope.
|
||||
/// </summary>
|
||||
public string ScopeManagerBeginMethodName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the <see cref="ExecutionContextTypeName"/> method used to end a scope.
|
||||
/// </summary>
|
||||
public string ScopeManagerEndMethodName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the <see cref="ExecutionContextTypeName"/> method used to add tag helper attributes.
|
||||
/// </summary>
|
||||
public string ExecutionContextAddTagHelperAttributeMethodName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the <see cref="ExecutionContextTypeName"/> method used to add minimized HTML attributes.
|
||||
/// </summary>
|
||||
public string ExecutionContextAddMinimizedHtmlAttributeMethodName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the <see cref="ExecutionContextTypeName"/> method used to add HTML attributes.
|
||||
/// </summary>
|
||||
public string ExecutionContextAddHtmlAttributeMethodName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the <see cref="ExecutionContextTypeName"/> method used to add tag helpers.
|
||||
/// </summary>
|
||||
public string ExecutionContextAddMethodName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The property accessor for the tag helper's output.
|
||||
/// </summary>
|
||||
public string ExecutionContextOutputPropertyName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the method used to format an error message about using an indexer when the tag helper property
|
||||
/// is <c>null</c>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Method signature should be
|
||||
/// <code>
|
||||
/// public string FormatInvalidIndexerAssignment(
|
||||
/// string attributeName, // Name of the HTML attribute associated with the indexer.
|
||||
/// string tagHelperTypeName, // Full name of the tag helper type.
|
||||
/// string propertyName) // Dictionary property in the tag helper.
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public string FormatInvalidIndexerAssignmentMethodName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the method used to wrap a <see cref="string"/> value and mark it as HTML-encoded.
|
||||
/// </summary>
|
||||
/// <remarks>Used together with <see cref="ExecutionContextAddHtmlAttributeMethodName"/>.</remarks>
|
||||
public string MarkAsHtmlEncodedMethodName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the method used to start a new writing scope.
|
||||
/// </summary>
|
||||
public string StartTagHelperWritingScopeMethodName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the method used to end a writing scope.
|
||||
/// </summary>
|
||||
public string EndTagHelperWritingScopeMethodName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the type used to run tag helpers.
|
||||
/// </summary>
|
||||
public string RunnerTypeName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the type used to create scoped <see cref="ExecutionContextTypeName"/> instances.
|
||||
/// </summary>
|
||||
public string ScopeManagerTypeName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the type describing a specific tag helper scope.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Contains information about in-scope tag helpers, HTML attributes, and the tag helpers' output.
|
||||
/// </remarks>
|
||||
public string ExecutionContextTypeName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the type containing tag helper content.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Contains the data returned by EndTagHelperWriteScope().
|
||||
/// </remarks>
|
||||
public string TagHelperContentTypeName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the method used to write <see cref="ExecutionContextTypeName"/>.
|
||||
/// </summary>
|
||||
public string WriteTagHelperAsyncMethodName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the method used to write <see cref="ExecutionContextTypeName"/> to a specified
|
||||
/// <see cref="System.IO.TextWriter"/>.
|
||||
/// </summary>
|
||||
public string WriteTagHelperToAsyncMethodName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the property containing the <c>IHtmlEncoder</c>.
|
||||
/// </summary>
|
||||
public string HtmlEncoderPropertyName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the method used to convert a <c>TagHelperContent</c> into a <see cref="string"/>.
|
||||
/// </summary>
|
||||
public string TagHelperContentGetContentMethodName { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNet.Razor.Chunks;
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
using Microsoft.AspNet.Razor.Compilation.TagHelpers;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||
{
|
||||
/// <summary>
|
||||
/// The results of parsing and generating code for a Razor document.
|
||||
/// </summary>
|
||||
public class GeneratorResults : ParserResults
|
||||
{
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="GeneratorResults"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="parserResults">The results of parsing a document.</param>
|
||||
/// <param name="codeGeneratorResult">The results of generating code for the document.</param>
|
||||
/// <param name="chunkTree">A <see cref="ChunkTree"/> for the document.</param>
|
||||
public GeneratorResults(ParserResults parserResults,
|
||||
CodeGeneratorResult codeGeneratorResult,
|
||||
ChunkTree chunkTree)
|
||||
: this(parserResults.Document,
|
||||
parserResults.TagHelperDescriptors,
|
||||
parserResults.ErrorSink,
|
||||
codeGeneratorResult,
|
||||
chunkTree)
|
||||
{
|
||||
if (parserResults == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(parserResults));
|
||||
}
|
||||
if (codeGeneratorResult == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(codeGeneratorResult));
|
||||
}
|
||||
if (chunkTree == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(chunkTree));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="GeneratorResults"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="document">The <see cref="Block"/> for the syntax tree.</param>
|
||||
/// <param name="tagHelperDescriptors">
|
||||
/// The <see cref="TagHelperDescriptor"/>s that apply to the current Razor document.
|
||||
/// </param>
|
||||
/// <param name="errorSink">
|
||||
/// The <see cref="ErrorSink"/> used to collect <see cref="RazorError"/>s encountered when parsing the
|
||||
/// current Razor document.
|
||||
/// </param>
|
||||
/// <param name="codeGeneratorResult">The results of generating code for the document.</param>
|
||||
/// <param name="chunkTree">A <see cref="ChunkTree"/> for the document.</param>
|
||||
public GeneratorResults(Block document,
|
||||
IEnumerable<TagHelperDescriptor> tagHelperDescriptors,
|
||||
ErrorSink errorSink,
|
||||
CodeGeneratorResult codeGeneratorResult,
|
||||
ChunkTree chunkTree)
|
||||
: base(document, tagHelperDescriptors, errorSink)
|
||||
{
|
||||
if (document == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(document));
|
||||
}
|
||||
|
||||
if (tagHelperDescriptors == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tagHelperDescriptors));
|
||||
}
|
||||
|
||||
if (errorSink == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(errorSink));
|
||||
}
|
||||
|
||||
if (codeGeneratorResult == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(codeGeneratorResult));
|
||||
}
|
||||
|
||||
if (chunkTree == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(chunkTree));
|
||||
}
|
||||
|
||||
GeneratedCode = codeGeneratorResult.Code;
|
||||
DesignTimeLineMappings = codeGeneratorResult.DesignTimeLineMappings;
|
||||
ChunkTree = chunkTree;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The generated code for the document.
|
||||
/// </summary>
|
||||
public string GeneratedCode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="LineMapping"/>s used to project code from a file during design time.
|
||||
/// </summary>
|
||||
public IList<LineMapping> DesignTimeLineMappings { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="Chunks.ChunkTree"/> for the document.
|
||||
/// </summary>
|
||||
public ChunkTree ChunkTree { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Globalization;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||
{
|
||||
public class LineMapping
|
||||
{
|
||||
public LineMapping(MappingLocation documentLocation, MappingLocation generatedLocation)
|
||||
{
|
||||
DocumentLocation = documentLocation;
|
||||
GeneratedLocation = generatedLocation;
|
||||
}
|
||||
|
||||
public MappingLocation DocumentLocation { get; }
|
||||
|
||||
public MappingLocation GeneratedLocation { get; }
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var other = obj as LineMapping;
|
||||
if (ReferenceEquals(other, null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return DocumentLocation.Equals(other.DocumentLocation) &&
|
||||
GeneratedLocation.Equals(other.GeneratedLocation);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hashCodeCombiner = HashCodeCombiner.Start();
|
||||
hashCodeCombiner.Add(DocumentLocation);
|
||||
hashCodeCombiner.Add(GeneratedLocation);
|
||||
|
||||
return hashCodeCombiner;
|
||||
}
|
||||
|
||||
public static bool operator ==(LineMapping left, LineMapping right)
|
||||
{
|
||||
if (ReferenceEquals(left, right))
|
||||
{
|
||||
// Exact equality e.g. both objects are null.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(left, null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(LineMapping left, LineMapping right)
|
||||
{
|
||||
if (ReferenceEquals(left, right))
|
||||
{
|
||||
// Exact equality e.g. both objects are null.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(left, null))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return !left.Equals(right);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentUICulture, "{0} -> {1}", DocumentLocation, GeneratedLocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||
{
|
||||
public class LineMappingManager
|
||||
{
|
||||
public LineMappingManager()
|
||||
{
|
||||
Mappings = new List<LineMapping>();
|
||||
}
|
||||
|
||||
public List<LineMapping> Mappings { get; }
|
||||
|
||||
public void AddMapping(MappingLocation documentLocation, MappingLocation generatedLocation)
|
||||
{
|
||||
Mappings.Add(new LineMapping(documentLocation, generatedLocation));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.CodeGenerators
|
||||
{
|
||||
public class MappingLocation
|
||||
{
|
||||
public MappingLocation()
|
||||
{
|
||||
}
|
||||
|
||||
public MappingLocation(SourceLocation location, int contentLength)
|
||||
{
|
||||
ContentLength = contentLength;
|
||||
AbsoluteIndex = location.AbsoluteIndex;
|
||||
LineIndex = location.LineIndex;
|
||||
CharacterIndex = location.CharacterIndex;
|
||||
FilePath = location.FilePath;
|
||||
}
|
||||
|
||||
public int ContentLength { get; }
|
||||
|
||||
public int AbsoluteIndex { get; }
|
||||
|
||||
public int LineIndex { get; }
|
||||
|
||||
public int CharacterIndex { get; }
|
||||
|
||||
public string FilePath { get; }
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var other = obj as MappingLocation;
|
||||
if (ReferenceEquals(other, null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return string.Equals(FilePath, other.FilePath, StringComparison.Ordinal) &&
|
||||
AbsoluteIndex == other.AbsoluteIndex &&
|
||||
ContentLength == other.ContentLength &&
|
||||
LineIndex == other.LineIndex &&
|
||||
CharacterIndex == other.CharacterIndex;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hashCodeCombiner = HashCodeCombiner.Start();
|
||||
hashCodeCombiner.Add(FilePath, StringComparer.Ordinal);
|
||||
hashCodeCombiner.Add(AbsoluteIndex);
|
||||
hashCodeCombiner.Add(ContentLength);
|
||||
hashCodeCombiner.Add(LineIndex);
|
||||
hashCodeCombiner.Add(CharacterIndex);
|
||||
|
||||
return hashCodeCombiner;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format(
|
||||
CultureInfo.CurrentCulture, "({0}:{1},{2} [{3}] {4})",
|
||||
AbsoluteIndex,
|
||||
LineIndex,
|
||||
CharacterIndex,
|
||||
ContentLength,
|
||||
FilePath);
|
||||
}
|
||||
|
||||
public static bool operator ==(MappingLocation left, MappingLocation right)
|
||||
{
|
||||
if (ReferenceEquals(left, right))
|
||||
{
|
||||
// Exact equality e.g. both objects are null.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(left, null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(MappingLocation left, MappingLocation right)
|
||||
{
|
||||
if (ReferenceEquals(left, right))
|
||||
{
|
||||
// Exact equality e.g. both objects are null.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(left, null))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return !left.Equals(right);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue