// Implemented based on // https://code.qt.io/cgit/qt/qtdeclarative.git/tree/src/qml/ // compiler/qqmlirbuilder.cpp // parser/{qqmljs.g,qqmljsast_p.h,qqmljslexer.cpp} // 93ff9bb648e7361b8f553fe99d4f9ba68a14bae5 module.exports = grammar(require('tree-sitter-typescript/typescript/grammar'), { name: 'qmljs', supertypes: ($, original) => original.concat([ $._ui_object_member, $._ui_script_statement, ]), inline: ($, original) => original.concat([ $._ui_root_member, $._ui_object_member, $._ui_property_type, $._ui_binding_value, $._ui_property_value, $._ui_script_statement, $._ui_qualified_id, $._ui_identifier, ]), conflicts: ($, original) => original.concat([ [$.ui_property_modifier, $.ui_required], // required property name vs required property [$.ui_nested_identifier, $.primary_expression], // Nested.Obj {} vs member.expr ]), rules: { // should be 'ui_program' per naming convention, but we need to override the // start rule of the javascript/typescript grammar. program: $ => seq( optional($.hash_bang_line), repeat(choice( $.ui_pragma, $.ui_import, )), field('root', $._ui_root_member), ), ui_pragma: $ => seq( 'pragma', field('name', $.identifier), // PragmaId optional(seq( ':', field('value', $.identifier), )), $._semicolon, ), ui_import: $ => seq( 'import', field('source', choice( $.string, $.identifier, $.nested_identifier, )), // ImportId optional(field('version', $.ui_version_specifier)), optional(seq( 'as', field('alias', $.identifier), // QmlIdentifier )), $._semicolon, ), ui_version_specifier: $ => { const version_number = alias(/\d+/, $.number); return choice( field('major', version_number), seq( field('major', version_number), '.', field('minor', version_number), ), ); }, _ui_root_member: $ => choice( $.ui_object_definition, $.ui_annotated_object, ), ui_object_definition: $ => seq( field('type_name', $._ui_qualified_id), field('initializer', $.ui_object_initializer), ), ui_annotated_object: $ => seq( repeat1(field('annotation', $.ui_annotation)), field('definition', $.ui_object_definition), ), ui_annotation: $ => seq( '@', field('type_name', choice( $.identifier, $.nested_identifier, )), // UiSimpleQualifiedId field('initializer', $.ui_object_initializer), ), ui_object_initializer: $ => seq( '{', repeat(choice( $._ui_object_member, $.ui_annotated_object_member, )), '}', ), ui_annotated_object_member: $ => seq( repeat1(field('annotation', $.ui_annotation)), field('definition', $._ui_object_member), ), _ui_object_member: $ => choice( $.ui_object_definition, $.ui_object_definition_binding, $.ui_binding, $.ui_property, $.ui_required, $.ui_signal, $.ui_inline_component, $.generator_function_declaration, $.function_declaration, $.variable_declaration, alias($._qml_enum_declaration, $.enum_declaration), ), ui_object_definition_binding: $ => seq( field('type_name', $._ui_qualified_id), 'on', field('name', $._ui_qualified_id), field('initializer', $.ui_object_initializer), ), ui_binding: $ => seq( field('name', $._ui_qualified_id), ':', field('value', $._ui_binding_value), ), // Duplicated modifiers are rejected by qqmljs.g, but we don't check for that. ui_property: $ => seq( repeat($.ui_property_modifier), 'property', field('type', choice( $._ui_property_type, $.ui_list_property_type, )), field('name', $._ui_identifier), // QmlIdentifier choice( seq( ':', field('value', $._ui_property_value), ), $._semicolon, ), ), _ui_property_type: $ => choice( $._type_identifier, $.nested_type_identifier, ), // "T_IDENTIFIER T_LT UiPropertyType T_GT", but only "list" is allowed as // a property type modifier. ui_list_property_type: $ => seq( alias('list', $.type_identifier), '<', $._ui_property_type, '>', ), ui_property_modifier: $ => choice( 'default', 'readonly', 'required', ), _ui_binding_value: $ => choice( $.ui_object_array, $.ui_object_definition, $._ui_script_statement, ), // similar to $._ui_binding_value, but optional semicolon is also allowed // for array/object. _ui_property_value: $=> choice( seq($.ui_object_array, $._semicolon), // UiObjectMemberWithArray seq($.ui_object_definition, $._semicolon), // UiObjectMemberExpressionStatementLookahead $._ui_script_statement, ), ui_object_array: $ => seq( '[', sep1($.ui_object_definition, ','), // UiArrayMemberList ']', ), _ui_script_statement: $ => choice( $.statement_block, $.empty_statement, $.expression_statement, $.if_statement, $.with_statement, $.switch_statement, $.try_statement, ), ui_required: $ => seq( 'required', field('name', $._ui_identifier), // QmlIdentifier $._semicolon, ), ui_signal: $ => seq( 'signal', field('name', $.identifier), optional(field('parameters', $.ui_signal_parameters)), $._semicolon, ), ui_signal_parameters: $ => seq( '(', sep($.ui_signal_parameter, ','), ')', ), ui_signal_parameter: $ => choice( seq( field('name', $.identifier), // QmlIdentifier ':', field('type', $._ui_property_type), ), seq( field('type', $._ui_property_type), field('name', $.identifier), // QmlIdentifier ), ), ui_inline_component: $ => seq( 'component', field('name', $.identifier), ':', field('component', $.ui_object_definition), ), // QML enum can be considered a restricted form of the TypeScript enum. _qml_enum_declaration: $ => seq( 'enum', field('name', $.identifier), field('body', alias($._qml_enum_body, $.enum_body)), ), _qml_enum_body: $ => seq( '{', sep1(choice( field('name', $.identifier), alias($._qml_enum_assignment, $.enum_assignment), ), ','), '}', ), _qml_enum_assignment: $ => seq( field('name', $.identifier), '=', field('value', choice( $.number, alias($._qml_enum_negative_number, $.unary_expression), )), ), _qml_enum_negative_number: $ => seq( field('operator', '-'), // '+' is not allowed field('argument', $.number), ), // MemberExpression -> reparseAsQualifiedId() _ui_qualified_id: $ => choice( $._ui_identifier, alias($.ui_nested_identifier, $.nested_identifier), ), _ui_identifier: $ => choice( $.identifier, alias($._reserved_identifier, $.identifier), ), ui_nested_identifier: $ => seq( $._ui_qualified_id, '.', $.identifier, ), // teach JavaScript/TypeScript grammar about QML keywords. _reserved_identifier: ($, original) => choice( original, 'property', 'signal', 'readonly', 'on', 'required', 'component', // not QML keywords, but qmljs.g accepts them as JS expressions: 'from', 'of', ), }, }); function sep(rule, sep) { return optional(sep1(rule, sep)); } function sep1(rule, sep) { return seq(rule, repeat(seq(sep, rule))); }