class JsDuck::TypeParser

Validates the syntax of type definitions.

The parser supports a combination of two syntaxes:

  1. Traditional type expressions found in ExtJS code:

    "string"
    3.14
    SomeType
    Name.spaced.Type
    Number[]
    String/RegExp
    Type...
    
  2. Google Closure Compiler Type Expressions:

    boolean
    Window
    goog.ui.Menu
    
    Array.<string>
    Object.<string, number>
    
    {myNum: number, myObject}
    
    (number|boolean)
    ?number
    !Object
    ...number
    *
    
    function(string, boolean): number
    function(new:goog.ui.Menu, string)
    function(this:goog.ui.Menu, string)
    function(?string=, number=)
    function(string, ...[number])

Attributes

error[R]

Allows to check the type of error that was encountered. It will be either of the two:

  • :syntax - type definition syntax is incorrect

  • :name - one of the names of the types is unknown

out[R]

When parsing was successful, then contains the output HTML - the input type-definition with types themselves replaced with links.

Public Class Methods

new(formatter) click to toggle source

Initializes the parser with a Format::Doc instance.

# File lib/jsduck/type_parser.rb, line 54
def initialize(formatter)
  @relations = formatter.relations
  @formatter = formatter
  @primitives = {
    "boolean" => "Boolean",
    "number" => "Number",
    "string" => "String",
    "null" => "null",
    "undefined" => "undefined",
    "void" => "void",
  }
end

Public Instance Methods

parse(str) click to toggle source

Parses the type definition

<type> ::= <alteration-type>
# File lib/jsduck/type_parser.rb, line 71
def parse(str)
  @input = StringScanner.new(str)
  @error = :syntax
  @out = []

  # Return immediately if the base type doesn't match
  return false unless alteration_type

  # Concatenate all output
  @out = @out.join

  # Success if we have reached the end of input
  return @input.eos?
end

Private Instance Methods

alteration_type() click to toggle source

<alteration-type> ::= <varargs-type> [ (“/” | “|”) <varargs-type> ]*

# File lib/jsduck/type_parser.rb, line 91
def alteration_type
  skip_whitespace

  # Return immediately if varargs-type doesn't match
  return false unless varargs_type

  skip_whitespace

  # Go through enumeration of types, separated with "/" or "|"
  while @input.check(/[\/|]/)
    @out << @input.scan(/[\/|]/)

    skip_whitespace
    return false unless varargs_type
    skip_whitespace
  end

  true
end
ftype_arg() click to toggle source

<ftype-arg> ::= <alteration-type> [ “=” ]

# File lib/jsduck/type_parser.rb, line 288
def ftype_arg
  return false unless alteration_type

  # Each argument can be optional (ending with "=")
  @out << "=" if @input.scan(/[=]/)
  skip_whitespace

  true
end
function_type() click to toggle source

<function-type> ::= “function(” <function-type-arguments> “)” [ “:” <null-type> ]

# File lib/jsduck/type_parser.rb, line 232
def function_type
  @out << @input.scan(/function\(/)

  skip_whitespace
  if !@input.check(/\)/)
    return false unless function_type_arguments
  end

  return false unless @input.scan(/\)/)
  @out << ")"

  skip_whitespace
  if @input.scan(/:/)
    @out << ":"
    skip_whitespace
    return false unless null_type
  end

  true
end
function_type_arguments() click to toggle source

<function-type-arguments> ::= <ftype-first-arg> [ “,” <ftype-arg> ]*

<ftype-first-arg> ::= “new” “:” <type-name>

| "this" ":" <type-name>
| <ftype-arg>
# File lib/jsduck/type_parser.rb, line 260
def function_type_arguments
  skip_whitespace

  # First argument is special
  if s = @input.scan(/new\s*:\s*/)
    @out << s
    return false unless type_name
  elsif s = @input.scan(/this\s*:\s*/)
    @out << s
    return false unless type_name
  else
    return false unless ftype_arg
  end

  skip_whitespace

  # Go through additional arguments, separated with ","
  while @input.check(/,/)
    @out << @input.scan(/,/)
    return false unless ftype_arg
  end

  true
end
null_type() click to toggle source

<null-type> ::= [ “?” | “!” ] <array-type>

<array-type> ::= <atomic-type> [ “[]” ]*

<atomic-type> ::= <union-type> | <record-type> | <function-type> | <string-literal> | <type-name>

# File lib/jsduck/type_parser.rb, line 148
def null_type
  if nullability = @input.scan(/[?!]/)
    @out << nullability
  end

  if @input.check(/\(/)
    return false unless union_type
  elsif @input.check(/\{/)
    return false unless record_type
  elsif @input.check(/function\(/)
    return false unless function_type
  elsif @input.check(/['"]/)
    return false unless string_literal
  elsif @input.check(/[\d-]/)
    return false unless number_literal
  else
    return false unless type_name
  end

  while @input.scan(/\[\]/)
    @out << "[]"
  end

  true
end
number_literal() click to toggle source

<number-literal> ::= [ “-” ] <digit>+ [ “.” <digit>+ ]

# File lib/jsduck/type_parser.rb, line 310
def number_literal
  @out << @input.scan(/-?\d+(\.\d+)?/)

  true
end
record_type() click to toggle source

<record-type> ::= “{” <rtype-item> [ “,” <rtype-item> ]* “}”

# File lib/jsduck/type_parser.rb, line 191
def record_type
  @out << @input.scan(/\{/)

  return false unless rtype_item

  while @input.scan(/,/)
    @out << ","
    return false unless rtype_item
  end

  return false unless @input.scan(/\}/)
  @out << "}"

  true
end
rtype_item() click to toggle source

<rtype-item> ::= <ident> “:” <null-type>

| <ident>
# File lib/jsduck/type_parser.rb, line 211
def rtype_item
  skip_whitespace

  key = @input.scan(/[a-zA-Z0-9_]+/)
  return false unless key
  @out << key

  skip_whitespace
  if @input.scan(/:/)
    @out << ":"
    skip_whitespace
    return false unless null_type
    skip_whitespace
  end

  true
end
skip_whitespace() click to toggle source
# File lib/jsduck/type_parser.rb, line 381
def skip_whitespace
  ws = @input.scan(/\s*/)
  @out << ws if ws
end
string_literal() click to toggle source

<string-literal> ::= '.*' | “.*”

# File lib/jsduck/type_parser.rb, line 301
def string_literal
  @out << @input.scan(/"([^\\"]|\\.)*?"|'([^\\']|\\.)*?'/)

  true
end
type_arguments() click to toggle source

<type-arguments> ::= <alteration-type> [ “,” <alteration-type> ]*

# File lib/jsduck/type_parser.rb, line 361
def type_arguments
  skip_whitespace

  # First argument is required
  return false unless alteration_type

  skip_whitespace

  # Go through additional arguments, separated with ","
  while @input.check(/,/)
    @out << @input.scan(/,/)

    skip_whitespace
    return false unless alteration_type
    skip_whitespace
  end

  true
end
type_name() click to toggle source

<type-name> ::= <type-application> | “*”

<type-application> ::= <ident-chain> [ “.” “<” <type-arguments> “>” ]

<type-arguments> ::= <alteration-type> [ “,” <alteration-type> ]*

<ident-chain> ::= <ident> [ “.” <ident> ]*

<ident> ::= [a-zA-Z0-9_]+

# File lib/jsduck/type_parser.rb, line 327
def type_name
  name = @input.scan(/[a-zA-Z0-9_]+(\.[a-zA-Z0-9_]+)*|\*/)

  if !name
    return false
  elsif @relations[name]
    @out << @formatter.link(name, nil, name)
  elsif @primitives[name]
    if @relations[@primitives[name]]
      @out << @formatter.link(@primitives[name], nil, name)
    else
      @out << name
    end
  elsif @relations.ignore?(name) || name == "*"
    @out << name
  else
    @error = :name
    return false
  end

  # All type names besides * can be followed by .<arguments>
  if name != "*" && @input.scan(/\.</)
    @out << ".&lt;"
    return false unless type_arguments
    return false unless @input.scan(/>/)
    @out << "&gt;"
  end

  true
end
union_type() click to toggle source

<union-type> ::= “(” <alteration-type> “)”

# File lib/jsduck/type_parser.rb, line 177
def union_type
  @out << @input.scan(/\(/)

  return false unless alteration_type

  return false unless @input.scan(/\)/)
  @out << ")"

  true
end
varargs_type() click to toggle source

<varargs-type> ::= “…” <null-type>

| "..." "[" <null-type> "]"
| <null-type> "..."
| <null-type>
# File lib/jsduck/type_parser.rb, line 117
def varargs_type
  if @input.scan(/\.\.\./)
    varargs = true
    @out << "..."
    if @input.scan(/\[/)
      varargs_bracketed = true
      @out << "["
    end
  end

  return false unless null_type

  if !varargs
    @out << "..." if @input.scan(/\.\.\./)
  end

  if varargs_bracketed
    return false unless @input.scan(/\]/)
    @out << "]"
  end

  true
end