Search code examples
javascript.netvb.netclearscript

Suppress irrelevant members when using an anonymous type in ClearScript


I'm implementing a .NET wrapper around https://linkify.js.org/ java-script library. For this task I'm using Microsoft's ClearScript.

The only problem I have is when implementing the attributes property that is passed in the options object parameter for the linkifyHtml function.

The function expects an argument like this in java-script code:

attributes: {
  title: "External Link",
  etc: "etc..."
}

So, the idea I had is to define this Attributes property which returns an anonymous type:

Public Class LinkifyFormatOptions 

    Public Property Attributes As Func(Of LinkifyLinkType, String, Object) =
        Function(linkType As LinkifyLinkType, href As String) As Object
            Select Case linkType
                Case LinkifyLinkType.Url
                    Return New With {.test_attribute = "This is a test attribute"}
                Case Else
                    Return Nothing
            End Select
        End Function
        
End Class

... which later I accommodate its return value for building the options object:

Friend Function GetLinkifyOptions() As Object
    Dim options As New With {
        .attributes = Function(href As String, linkType As String) As Object
                          Select Case linkType
                              Case "url"
                                  Return Me.Attributes(LinkifyLinkType.Url, href)
                              Case "email"
                                  Return Me.Attributes(LinkifyLinkType.Email, href)
                              Case "hashtag"
                                  Return Me.Attributes(LinkifyLinkType.Hashtag, href)
                              Case "mention"
                                  Return Me.Attributes(LinkifyLinkType.Mention, href)
                              Case "ticket"
                                  Return Me.Attributes(LinkifyLinkType.Ticket, href)
                              Case Else
                                  Return Nothing
                          End Select
                      End Function,
    .className = etc...,
    .formatHref = etc...,
    .etc = etc...
    }
End Function

... to finally call the java-script's linkifyHtml function here:

Imports Microsoft.ClearScript
Imports Microsoft.ClearScript.V8

Public Shared Function Linkify(str As String,
                               [interface] As LinkifyInterface,
                               Optional plugins As LinkifyPlugins = LinkifyPlugins.None,
                               Optional formatOptions As LinkifyFormatOptions = Nothing) As String

    ' Argument validations and etc...

    Using engine As New V8ScriptEngine("linkify_engine", V8ScriptEngineFlags.DisableGlobalMembers)
        Dim options As Object = formatOptions.GetLinkifyOptions()
        ' linkify.js scripts loading and etc...
        
        Dim linkifyFunction As ScriptObject = DirectCast(engine.Evaluate("linkifyHtml"), ScriptObject)
        Return CStr(linkifyFunction.Invoke(asConstructor:=False, str, options))
    End Using
    
End Function

The problem is that the ClearScript engine is exposing the members of the Object type, like "ToString" method, "GetHashCode", etc.

This is the output I get after calling the java-script's linkifyHtml function:

<p>Domain: <a href="http://example.domain.com" $test_attribute="This is a test attribute" ToString="[object Object]" Equals="[object Object]" GetHashCode="[object Object]" GetType="[object Object]" Finalize="[object Object]" MemberwiseClone="[object Object]" test_attribute="This is a test attribute" toJSON="function toJSON() { [native code] }">example.domain.com</a></p>

I partially solved that issue by enabling the V8ScriptEngine.SuppressInstanceMethodEnumeration Property like this:

Using engine As New V8ScriptEngine("linkify_engine", V8ScriptEngineFlags.DisableGlobalMembers)
    engine.SuppressInstanceMethodEnumeration = True
    ' etc...
End Using

However, a reflected toJSON function and other garbage is still present in the output and I don't know how to get rid of it:

<p>Domain: <a href="http://example.domain.com" $test_attribute="This is a test attribute" test_attribute="This is a test attribute" toJSON="function toJSON() { [native code] }">example.domain.com</a></p>

The desired output, of course, will be this:

<p>Domain: <a href="http://example.domain.com" test_attribute="This is a test attribute">example.domain.com</a></p>

Then, I would like to know how to suppress that reflected toJSON member, together with the members that starts with a "$" symbol, like $test_attribute.

I think that maybe I would need to replace and adapt the anonymous type usage for a qualified type that inherits or implements a specific interface from the Microsoft.ClearScript namespaces and/or maybe also use specific attribute classes, all that to let the V8 engine do things in the right way... but I don't know how to.

I tried to define a custom type that I used to redefine (shadowing or overriding) ToString, GetHashCode, Equals and the other members, and then I tried to apply various attribute classes in those redefined members to try to "hide" them from the ClearScript engine, without success. Anyway, the toJSON function I don't know where it comes from.


Solution

  • I can reproduce your issue with this simplified code:

    Dim attributes = New With {.test_attribute = "This is a test attribute"}
    Dim options = New With {.attributes = attributes}
    Console.WriteLine(engine.Script.linkifyHtml("example.domain.com", options))
    

    Comments: The $test_attribute property seems to be a part of the anonymous type, and ClearScript is probably doing the right thing in exposing it. On the other hand, the presence of toJSON as an enumerable property is likely to be a bug in ClearScript.

    Recommendation: Instead of using an anonymous type, use a script object. Here's a function that creates and populates a script object:

    Private Function CreateObject(engine As V8ScriptEngine, ParamArray props() As (name As String, value As Object))
        Dim result = engine.Evaluate("({})")
        For Each prop In props
            result(prop.name) = prop.value
        Next
        CreateObject = result
    End Function
    

    You can use that function to get the desired behavior:

    Dim attributes = CreateObject(engine, ("test_attribute", "This is a test attribute"))
    Dim options = CreateObject(engine, ("attributes", attributes))
    Console.WriteLine(engine.Script.linkifyHtml("example.domain.com", options))