16. Smithy IDL

Smithy models are defined using either the Smithy interface definition language (IDL) or the JSON abstract syntax tree (AST). This document defines the ABNF grammar and syntax for defining models with the Smithy IDL.

16.1. Smithy IDL overview

The Smithy IDL is made up of 3, ordered sections, each of which is optional:

  1. Control section; defines parser directives like which version of the IDL to use.
  2. Metadata section; applies metadata to the entire model.
  3. Shape section; where shapes and traits are defined. A namespace MUST be defined before any shapes or traits can be defined. smithy:use_statements can be defined after a namespace and before shapes or traits to refer to shapes in other namespaces using a shorter name.

The following example defines a model file with each section:

// (1) Control section
$version: "1.0"

// (2) Metadata section
metadata foo = "bar"

// (3) Shape section
namespace smithy.example

use smithy.other.namespace#MyString

structure MyStructure {
    @required
    foo: MyString
}

16.2. Lexical notes

Smithy models MUST be encoded using UTF-8 and SHOULD use Unix style line endings (\n). The Smithy ABNF is whitespace sensitive.

16.3. Smithy IDL ABNF

The Smithy IDL is defined by the following ABNF:

idl =
    ws control_section metadata_section shape_section

Whitespace

ws =
    *(sp / newline / comment) ; whitespace

sp =
    *(%x20  / %x09) ; " " and \t

br =
    sp (comment / newline) sp ; break

newline =
    %x0A / %x0D.0A ; \n and \r\n

Comments

comment =
     documentation_comment / line_comment

documentation_comment =
    "///" *not_newline br

line_comment =
     "//" *not_newline newline

not_newline =
     %x09 / %x20-10FFFF ; Any character except newline

Control

control_section =
    *(control_statement)

control_statement =
    "$" ws node_object_key ws ":" ws node_value br

Metadata

metadata_section =
    *(metadata_statement)

metadata_statement =
    "metadata" ws node_object_key ws "=" ws node_value br

Node values

node_value =
    node_array
  / node_object
  / number
  / node_keywords
  / node_string_value

node_array =
    empty_node_array / populated_node_array

empty_node_array =
    "[" ws "]"

populated_node_array =
    "[" ws node_value ws
         *(comma node_value ws)
         trailing_comma "]"

trailing_comma =
    [comma]

comma =
    "," ws

node_object =
    empty_node_object / populated_node_object

empty_node_object =
    "{" ws "}"

populated_node_object =
    "{" ws node_object_kvp ws
         *(comma node_object_kvp ws)
         trailing_comma "}"

node_object_kvp =
    node_object_key ws ":" ws node_value

node_object_key =
    quoted_text / identifier

number =
    [minus] int [frac] [exp]

decimal_point =
    %x2E ; .

digit1_9 =
    %x31-39 ; 1-9

e =
    %x65 / %x45 ; e E

exp =
    e [minus / plus] 1*DIGIT

frac =
    decimal_point 1*DIGIT

int =
    zero / (digit1_9 *DIGIT)

minus =
    %x2D ; -

plus =
    %x2B ; +

zero =
    %x30 ; 0

node_keywords =
     "true" / "false" / "null"

node_string_value =
    shape_id / text_block / quoted_text

quoted_text =
    DQUOTE *quoted_char DQUOTE

quoted_char =
    %x20-21        ; space - "!"
  / %x23-5B        ; "#" - "["
  / %x5D-10FFFF    ; "]"+
  / escaped_char
  / preserved_double

escaped_char =
    escape (escape / "'" / DQUOTE / "b" / "f" / "n" / "r" / "t" / "/" / unicode_escape)

unicode_escape =
    "u" hex hex hex hex

hex =
     DIGIT / %x41-46 / %x61-66

preserved_double =
    escape (%x20-21 / %x23-5B / %x5D-10FFFF)

escape =
    %x5C ; backslash

text_block =
    three_dquotes br *quoted_char three_dquotes

three_dquotes =
    DQUOTE DQUOTE DQUOTE

Shapes

shape_section =
    [namespace_statement [use_section] [shape_statements]]

namespace_statement =
    "namespace" ws namespace br

use_section =
    *(use_statement)

use_statement =
    "use" ws absolute_root_shape_id br

shape_statements =
    *(shape_statement / apply_statement)

shape_statement =
    trait_statements shape_body br

shape_body =
    simple_shape_statement
  / list_statement
  / set_statement
  / map_statement
  / structure_statement
  / union_statement
  / service_statement
  / operation_statement
  / resource_statement

simple_shape_statement =
    simple_type_name ws identifier

simple_type_name =
    "blob" / "boolean" / "document" / "string"
  / "byte" / "short" / "integer" / "long"
  / "float" / "double" / "bigInteger"
  / "bigDecimal" / "timestamp"

shape_members =
    empty_shape_members / populated_shape_members

empty_shape_members =
    "{" ws "}"

populated_shape_members =
    "{" ws shape_member_kvp
    *(comma shape_member_kvp ws) trailing_comma "}"

shape_member_kvp =
    trait_statements identifier ws ":" ws shape_id

list_statement =
    "list" ws identifier ws shape_members

set_statement =
    "set" ws identifier ws shape_members

map_statement =
    "map" ws identifier ws shape_members

structure_statement =
    "structure" ws identifier ws shape_members

union_statement =
    "union" ws identifier ws shape_members

service_statement =
    "service" ws identifier ws node_object

operation_statement =
    "operation" ws identifier ws node_object

resource_statement =
    "resource" ws identifier ws node_object

Traits

trait_statements =
     *(ws trait) ws

trait =
    "@" shape_id [trait_body]

trait_body =
    "(" ws trait_body_value ws ")"

trait_body_value =
    trait_structure / node_value

trait_structure =
    trait_structure_kvp *(ws comma trait_structure_kvp)

trait_structure_kvp =
    node_object_key ws ":" ws node_value

apply_statement =
    "apply" ws shape_id ws trait br

Shape ID

See also

Refer to Shape ID for the ABNF grammar of shape IDs.

16.4. Comments

A comment can appear at any place between tokens where whitespace (smithy:ws) can appear. Comments in Smithy are defined using two forward slashes followed by any character. A newline terminates a comment.

// This is a comment
namespace com.foo // This is also a comment

// Another comment
string MyString

Note

Three forward slashes can be used to define the documentation of a shape using a special documentation comment.

16.5. Control section

The control section of a model contains control statements that apply parser directives to a specific IDL file. Because control statements influence parsing, they MUST appear at the beginning of a file before any other statements and have no effect on the semantic model

The version statement is currently the only control statement defined in the Smithy IDL. Implementations MUST ignore unknown control statements.

16.5.1. Version statement

The Smithy specification is versioned using a major . minor versioning scheme. A version requirement is specified for a model file using the $version control statement. When no version number is specified in the IDL, an implementation SHOULD assume that the model can be loaded. Because this can lead to unexpected parsing errors, models SHOULD always include a version.

The value provided in a version control statement is a string that MUST adhere to the following ABNF:

version_string =
    1*DIGIT [ "." 1*DIGIT ]

The following example sets the version to 1, meaning that tooling MUST support a version greater than or equal to 1.0 and less than 2.0:

$version: "1"

A minor version SHOULD be provided when a model depends on a feature released in a minor update of the specification. The following example sets the version requirement of a file to 1.1, meaning that tooling MUST support a version greater than or equal to 1.1 and less than 2.0:

$version: "1.1"

Version compatibility

A single version statement can appear in a model file, but different versions MAY be encountered when merging multiple model files together. Multiple versions are supported if and only if all of the version statements are supported by the tool loading the models.

16.6. Metadata section

The metadata section is used to apply untyped metadata to the entire model. A smithy:metadata_statement consists of the metadata key to set, followed by =, followed by the node value to assign to the key.

The following example defines metadata in the model:

metadata greeting = "hello"
metadata "stringList" = ["a", "b", "c"]

16.7. Shape section

The shape section of the IDL is used to define shapes and apply traits to shapes.

16.7.1. Namespaces

Shapes can only be defined after a namespace is declared. A namespace is declared using a namespace statement. Only one namespace can appear per file.

The following example defines a string shape named MyString in the smithy.example namespace:

namespace smithy.example

string MyString

16.7.2. Referring to shapes

The use section of the IDL is used to import shapes into the current namespace so that they can be referred to using a relative shape ID. The use_statements that make up this section have no effect on the semantic model.

The following example uses smithy.example#Foo and smithy.example#Baz so that they can be referred to using only Foo and Baz.

namespace smithy.hello

use smithy.example#Foo
use smithy.example#Baz

map MyMap {
    // Resolves to smithy.example#Foo
    key: Foo,
    // Resolves to smithy.example#Baz
    value: Baz,
}

A use statement can refer to traits too. The following example uses the smithy.example#test and smithy.example#anotherTrait traits so that they can be applied using relative shape IDs:

namespace smithy.hello

use smithy.example#test
use smithy.example#anotherTrait

@test // <-- Resolves to smithy.example#test
string MyString

Use statement validation

  1. A shape cannot be defined in a file with the same name as one of the shapes imported with a use statement.
  2. Shapes IDs with members names cannot be imported with a use statement.

16.7.2.1. Relative shape ID resolution

Relative shape IDs are resolved using the following process:

  1. If a smithy:use_statement has imported a shape with the same name, the shape ID resolves to the imported shape ID.
  2. If a shape is defined in the same namespace as the shape with the same name, the namespace of the shape resolves to the current namespace.
  3. If a shape is defined in the prelude with the same name, the namespace resolves to smithy.api.
  4. If a relative shape ID does not satisfy one of the above cases, the shape ID is invalid, and the namespace is inherited from the current namespace.

The following example Smithy model contains comments above each member of the shape named MyStructure that describes the shape the member resolves to.

namespace smithy.example

use foo.baz#Bar

string MyString

structure MyStructure {
    // Resolves to smithy.example#MyString
    // There is a shape named MyString defined in the same namespace.
    a: MyString,

    // Resolves to smithy.example#MyString
    // Absolute shape IDs do not perform namespace resolution.
    b: smithy.example#MyString,

    // Resolves to foo.baz#Bar
    // The "use foo.baz#Bar" statement imported the Bar symbol,
    // allowing the shape to be referenced using a relative shape ID.
    c: Bar,

    // Resolves to smithy.api#String
    // No shape named String was imported through a use statement
    // the smithy.example namespace does not contain a shape named
    // String, and the prelude model contains a shape named String.
    d: String,

    // Resolves to smithy.example#MyBoolean.
    // There is a shape named MyBoolean defined in the same namespace.
    // Forward references are supported both within the same file and
    // across multiple files.
    e: MyBoolean,

    // Resolves to smithy.example#InvalidShape. A shape by this name has
    // not been imported through a use statement, a shape by this name
    // does not exist in the current namespace, and a shape by this name
    // does not exist in the prelude model.
    f: InvalidShape,
}

boolean MyBoolean

16.7.2.2. Syntactic shape IDs

Unquoted string values that are not object keys in the Smithy IDL are considered lexical shape IDs and are resolved to absolute shape IDs using the process defined in Relative shape ID resolution.

The following model defines a list that references a string shape defined in another namespace.

namespace smithy.example

use smithy.other#MyString

list MyList {
    member: MyString
}

The above model is equivalent to the following JSON AST model:

{
    "smithy": "1.0",
    "shapes": {
        "smithy.example#MyList": {
            "type": "list",
            "members": {
                "target": "smithy.other#MyString"
            }
        }
    }
}

Use quotes for literal strings

Values that are not meant to be shape IDs MUST be quoted. The following model is syntactically valid but semantically incorrect because it resolves the value of the error trait to the shape ID "smithy.example#client" rather than using the string literal value of "client":

namespace smithy.example

@error(client) // <-- This MUST be "client"
structure Error

string client

The above example is equivalent to the following incorrect JSON AST:

{
    "smithy": "1.0",
    "shapes": {
        "smithy.example#Error": {
            "type": "structure",
            "traits": {
                "smithy.api#error": "smithy.example#client"
            }
        },
        "smithy.example#client": {
            "type": "string"
        }
    }
}

Object keys

Object keys are not treated as shape IDs. The following example defines a metadata object, and when loaded into the semantic model, the object key String remains the same literal string value of String while the value is treated as a shape ID and resolves to the string literal "smithy.api#String".

metadata foo = {
    String: String,
}

The above example is equivalent to the following JSON AST:

{
    "smithy": "1.0",
    "metadata": {
        "String": "smithy.api#String"
    }
}

Semantic model

Syntactic shape IDs are syntactic sugar for defining fully-qualified shape IDs inside of strings, and this difference is inconsequential in the semantic model. A syntactic shape ID SHOULD be resolved to a string that contains a fully-qualified shape ID when parsing the model.

Validation

When a syntactic shape ID is found that does not target an actual shape in the fully loaded semantic model, an implementation SHOULD emit a DANGER validation event with an ID of SyntacticShapeIdTarget. This validation brings attention to the broken reference and helps to ensure that modelers do not unintentionally use a syntactic shape ID when they should have used a string. A DANGER severity is used so that the validation can be suppressed in the rare cases that the broken reference can be ignored.

16.7.3. Defining shapes

Shapes are defined using a smithy:shape_statement.

16.7.3.1. Simple shapes

Simple shapes are defined using a smithy:simple_shape_statement.

The following example defines a string shape:

namespace smithy.example

string MyString

The following example defines an integer shape with a range trait:

namespace smithy.example

@range(min: 0, max: 1000)
integer MaxResults

16.7.3.2. List shapes

A list shape is defined using a smithy:list_statement.

The following example defines a list with a string member from the prelude:

namespace smithy.example

list MyList {
    member: String
}

Traits can be applied to the list shape and its member:

namespace smithy.example

@length(min: 3, max: 10)
list MyList {
    @length(min: 1, max: 100)
    member: String
}

16.7.3.3. Set shapes

A set set shape is defined using a smithy:set_statement.

The following example defines a set of strings:

namespace smithy.example

set StringSet {
    member: String
}

Traits can be applied to the set shape and its members:

namespace smithy.example

@deprecated
set StringSet {
    @sensitive
    member: String
}

16.7.3.4. Map shapes

A map shape is defined using a smithy:map_statement.

The following example defines a map of strings to integers:

namespace smithy.example

map IntegerMap {
    key: String,
    value: Integer
}

Traits can be applied to the map shape and its members:

namespace smithy.example

@length(min: 0, max: 100)
map IntegerMap {
    @length(min: 1, max: 10)
    key: String,

    @sensitive
    value: Integer
}

16.7.3.5. Structure shapes

A structure shape is defined using a smithy:structure_statement.

The following example defines a structure with two members:

namespace smithy.example

structure MyStructure {
    foo: String,
    baz: Integer,
}

Traits can be applied to structure members:

namespace smithy.example

/// This is MyStructure.
structure MyStructure {
    /// This is documentation for `foo`.
    @required
    foo: String,

    /// This is documentation for `baz`.
    @deprecated
    baz: Integer,
}

16.7.3.6. Union shapes

A union shape is defined using a smithy:union_statement.

The following example defines a union shape with several members:

namespace smithy.example

union MyUnion {
    i32: Integer,

    stringA: String,

    @sensitive
    stringB: String,
}

16.7.3.7. Service shape

A service shape is defined using a smithy:service_statement and the provided smithy:node_object supports the same properties defined in the service specification.

The following example defines a service named ModelRepository that binds a resource named Model and an operation named PingService:

namespace smithy.example

service ModelRepository {
    version: "2020-07-13",
    resources: [Model],
    operations: [PingService]
}

16.7.3.8. Operation shape

An operation shape is defined using an smithy:operation_statement and the provided smithy:node_object supports the same properties defined in the operation specification.

The following example defines an operation shape that accepts an input structure named Input, returns an output structure named Output, and can potentially return the Unavailable or BadRequest error structures.

namespace smithy.example

operation PingService {
    input: Input,
    output: Output,
    errors: [Unavailable, BadRequest]
}

16.7.3.9. Resource shape

A resource shape is defined using a smithy:resource_statement and the provided smithy:node_object supports the same properties defined in the resource specification.

The following example defines a resource shape that has a single identifier, and defines a read operation:

namespace smithy.example

resource Model {
    identifiers: {
        modelId: String,
    },
    read: GetModel,
}

16.7.4. Documentation comment

Documentation comments are a special kind of smithy:comment that provide documentation for shapes. A documentation comment is formed when three forward slashes ("///") appear as the first non-whitespace characters on a line.

Documentation comments are defined using CommonMark. The text after the forward slashes is considered the contents of the line. If the text starts with a space (" "), the leading space is removed from the content. Successive documentation comments are combined together using a newline ("\n") to form the documentation of a shape.

The following Smithy IDL example,

namespace smithy.example

/// This is documentation about a shape.
///
/// - This is a list
/// - More of the list.
string MyString

/// This is documentation about a trait shape.
///   More docs here.
@trait
structure myTrait {}

is equivalent to the following JSON AST model:

{
    "smithy": "1.0",
    "shapes": {
        "smithy.example#MyString": {
            "type": "string",
            "traits": {
                "smithy.api#documentation": "This is documentation about a shape.\n\n- This is a list\n- More of the list."
            }
        },
        "smithy.example#myTrait": {
            "type": "structure",
            "traits": {
                "smithy.api#trait": {},
                "smithy.api#documentation": "This is documentation about a trait shapes.\n  More docs here."
            }
        }
    }
}

Placement

Documentation comments are only treated as shape documentation when the comment appears immediately before a shape, and documentation comments MUST appear before any traits applied to the shape in order for the documentation to be applied to a shape.

The following example applies a documentation trait to the shape because the documentation comment comes before the traits applied to a shape:

/// A deprecated string.
@deprecated
string MyString

Documentation comments can also be applied to members of a shape.

/// Documentation about the structure.
structure Example {
    /// Documentation about the member.
    @sensitive
    foo: String,
}

Semantic model

Documentation comments are syntactic sugar equivalent to applying the documentation trait, and this difference is inconsequential in the semantic model.

16.7.5. Applying traits

Trait values immediately preceding a shape definition are applied to the shape. The shape ID of a trait is resolved against smithy:use_statements and the current namespace in exactly the same same way as other shape IDs.

The following example applies the sensitive trait and documentation trait to MyString:

namespace smithy.example

@sensitive
@documentation("Contains a string")
string MyString

16.7.5.1. Trait values

The value that can be provided for a trait depends on its type. A value for a trait is defined by enclosing the value in parenthesis. Trait values can only appear immediately before a shape.

The following example applies various traits to a structure shape and its members.

@documentation("An animal in the animal kingdom")
structure Animal {
    @required
    name: smithy.api#String,

    @length(min: 0)
    @tags(["private-beta"])
    age: smithy.api#Integer,
}

16.7.5.2. Structure, map, and union trait values

Traits that are a structure, union, or map are defined using a special syntax that places key-value pairs inside of the trait parenthesis. Wrapping braces, "{" and "}", are not permitted.

@structuredTrait(foo: "bar", baz: "bam")

Nested structure, map, and union values are defined using node value productions:

@structuredTrait(
    foo: {
        bar: "baz",
        qux: "true",
    }
)

Omitting a value is allowed on list, set, map, and structure traits if the shapes have no length constraints or required members. The following applications of the foo trait are equivalent:

namespace smithy.example

@trait
structure foo {}

@foo
string MyString1

@foo()
string MyString2

16.7.5.3. List and set trait values

Traits that are a list or set shape are defined inside of brackets ([) and (]) using a smithy:node_array production.

@tags(["a", "b"])

16.7.5.4. Other trait values

All other trait values MUST adhere to the JSON type mappings defined in Trait node values.

The following example defines a string trait value:

@documentation("Hello")

16.7.5.5. Apply statement

Traits can be applied to shapes outside of a shape's definition using an smithy:apply_statement.

The following example applies the documentation trait and length trait to the smithy.example#MyString shape:

namespace smithy.example

apply MyString @documentation("This is my string!")
apply MyString @length(min: 1, max: 10)

Traits can be applied to members too:

namespace smithy.example

apply MyStructure$foo @documentation("Structure member documentation")
apply MyUnion$foo @documentation("Union member documentation")
apply MyList$member @documentation("List member documentation")
apply MySet$member @documentation("Set member documentation")
apply MyMap$key @documentation("Map key documentation")
apply MyMap$value @documentation("Map key documentation")

See also

Refer to trait conflict resolution for information on how trait conflicts are resolved.

Note

In the semantic model, applying traits outside of a shape definition is treated exactly the same as applying the trait inside of a shape definition.

16.8. Node values

Node values are analogous to JSON values. Node values are used to define metadata and trait values. Smithy's node values have many advantages over JSON: comments, unquoted keys, unquoted strings, text blocks, and trailing commas.

The following example defines a complex object metadata entry using a node value:

metadata foo = {
    hello: 123,
    "foo": "456",
    testing: """
        Hello!
        """,
    an_array: [10.5],
    nested-object: {
        hello-there$: true
    }, // <-- Trailing comma
}

Array node

An array node is defined like a JSON array. A smithy:node_array contains zero or more heterogeneous smithy:node_values. A trailing comma is allowed in a node_array.

The following examples define arrays with zero, one, and two values:

  • []
  • [true]
  • [1, "hello",]

Object node

An object node is defined like a JSON object. A smithy:node_object contains zero or more key value pairs of strings (a smithy:node_object_key) that map to heterogeneous smithy:node_values. A trailing comma is allowed in a node_object.

The following examples define objects with zero, one, and two key value pairs:

  • {}
  • {foo: true}
  • {foo: "hello", "bar": [1, 2, {}]}

Number node

A node smithy:number contains numeric data. It is defined like a JSON number. The following examples define several number values:

  • 0
  • 0.0
  • 1234
  • -1234.1234
  • 1e+2
  • 1.0e-10

Node keywords

Several keywords are used when parsing smithy:node_value.

  • true: The value is treated as a boolean true
  • false: The value is treated as a boolean false
  • null: The value is treated like a JSON null

16.8.1. String values

A node_value can contain smithy:node_string_value productions that all define strings.

New lines

New lines in strings are normalized from CR (u000D) and CRLF (u000Du000A) to LF (u000A). This ensures that strings defined in a Smithy model are equivalent across platforms. If a literal \r is desired, it can be added a string value using the Unicode escape \u000d.

String equivalence

The node_string_value production defines several productions used to define strings, and in order for these productions to work in concert with the JSON AST format, each of these production MUST be treated like equivalent string values when loaded into the semantic model.

16.8.2. String escape characters

The Smithy IDL supports escape sequences only within quoted strings. The following escape sequences are allowed:

Unicode code point Escape Meaning
U+0022 \" double quote
U+005C \\ backslash
U+002F \/ forward slash
U+0008 \b backspace BS
U+000C \f form feed FF
U+000A \n line feed LF
U+000D \r carriage return CR
U+0009 \t horizontal tab HT
U+HHHH \uHHHH 4-digit hexadecimal Unicode code point
nothing \\r\n, \\r, \\n escaped new line expands to nothing

Any other sequence following a backslash is an error.

16.8.3. Text blocks

A text block is a string literal that can span multiple lines and automatically removes any incidental whitespace. Smithy text blocks are heavily inspired by text blocks defined in JEP 355.

A text block is opened with three double quotes ("""), followed by a newline, zero or more content characters, and closed with three double quotes. Text blocks differentiate incidental whitespace from significant whitespace. Smithy will re-indent the content of a text block by removing all incidental whitespace.

@documentation("""
    <div>
        <p>Hello!</p>
    </div>
    """)

The four leading spaces in the above text block are considered insignificant because they are common across all lines. Because the closing delimiter appears on its own line, a trailing new line is added to the result. The content of the text block is re-indented to remove the insignificant whitespace, making it equivalent to the following:

@documentation("<div>\n    <p>Hello!</p>\n</div>\n")

The closing delimiter can be placed on the same line as content if no new line is desired at the end of the result. The above example could be rewritten to not including a trailing new line:

@documentation("""
    <div>
        <p>Hello!</p>
    </div>""")

This example is equivalent to the following:

@documentation("<div>\n    <p>Hello!</p>\n</div>")

The following text blocks are ill-formed:

"""foo"""  // missing new line following open delimiter
""" """    // missing new line following open delimiter
"""
"          // missing closing delimiter

16.8.3.1. Incidental white space removal

Smithy will re-indent the content of a text block by removing all incidental whitespace using the following algorithm:

  1. Split the content of the text block at every LF, producing a list of lines. The opening LF of the text block is not considered.

    Given the following example ("." is used to represent spaces),

    @documentation("""
    ....Foo
    ........Baz
    
    ..
    ....Bar
    ....""")
    

    the following lines are produced:

    ["    Foo", "        Baz", "", "  ", "    Bar", "    "]
    
  2. Compute the common whitespace prefix by iterating over each line, counting the number of leading spaces (" ") and taking the minimum count. Except for the last line of content, lines that are empty or consist wholly of whitespace are not considered. If the last line of content (that is, the line that contains the closing delimiter) appears on its own line, then that line's leading whitespace is considered when determining the common whitespace prefix, allowing the closing delimiter to determine the amount of indentation to remove.

    Using the previous example, the common whitespace prefix is four spaces. The empty third line and the blank fourth lines are not considered when computing the common whitespace. The following uses "." to represent the common whitespace prefix:

    @documentation("""
    ....Foo
    ....    Baz
    
    ....
    ....Bar
    ....""")
    
  3. Remove the common white space prefix from each line.

    This step produces the following values from the previous example:

    ["Foo", "    Baz", "", "", "Bar", ""]
    
  4. Remove any trailing spaces from each line.

  5. Concatenate each line together, separated by LF.

    This step produces the following result ("|" is used to represent the left margin):

    |Foo
    |    Baz
    |
    |
    |Bar
    |
    

16.8.3.2. Significant trailing line

The last line of text block content is used when determining the common whitespace prefix.

Consider the following example:

@documentation("""
    Foo
        Baz
    Bar
""")

Because the closing delimiter is at the margin and left of the rest of the content, the common whitespace prefix is 0 characters, resulting in the following equivalent string:

@documentation("    Foo\n        Baz\n    Bar\n")

If the closing delimiter is moved to the right of the content, then it has no bearing on the common whitespace prefix. The common whitespace prefix in the following example is visualized using "." to represent spaces:

@documentation("""
....Foo
....    Baz
....Bar
        """)

Because lines are trimmed when they are added to the result, the above example is equivalent to the following:

@documentation("Foo\n    Baz\nBar\n")

16.8.3.3. Escapes in text blocks

Text blocks support all of the string escape characters of other strings. The use of three double quotes allows unescaped double quotes (") to appear in text blocks. The following text block is interpreted as "hello!":

"""
"hello!"
"""

Three quotes can appear in a text block without being treated as the closing delimiter as long as one of the quotes are escaped. The following text block is interpreted as foo """\nbaz:

"""
foo \"""
baz"""

String escapes are interpreted after incidental whitespace is removed from a text block. The following example uses "." to denote spaces:

"""
..<div>
....<p>Hi\\n....bar</p>
..</div>
.."""

Because string escapes are expanded after incidental whitespace is removed, it is interpreted as:

<div>
..<p>Hi
....bar</p>
</div>

New lines in the text block can be escaped. This allows for long, single-line strings to be broken into multiple lines in the IDL. The following example is interpreted as Foo Baz Bam:

"""
Foo \
Baz \
Bam"""

Escaped new lines can be intermixed with unescaped newlines. The following example is interpreted as Foo\nBaz Bam:

"""
Foo
Baz \
Bam"""
17. JSON AST →