diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..b5448370 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,484 @@ +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = false +max_line_length = 120 +tab_width = 4 +ij_continuation_indent_size = 8 +ij_formatter_off_tag = @formatter:off +ij_formatter_on_tag = @formatter:on +ij_formatter_tags_enabled = true +ij_smart_tabs = false +ij_visual_guides = +ij_wrap_on_typing = false + +[*.css] +ij_css_align_closing_brace_with_properties = false +ij_css_blank_lines_around_nested_selector = 1 +ij_css_blank_lines_between_blocks = 1 +ij_css_block_comment_add_space = false +ij_css_brace_placement = end_of_line +ij_css_enforce_quotes_on_format = false +ij_css_hex_color_long_format = false +ij_css_hex_color_lower_case = false +ij_css_hex_color_short_format = false +ij_css_hex_color_upper_case = false +ij_css_keep_blank_lines_in_code = 2 +ij_css_keep_indents_on_empty_lines = false +ij_css_keep_single_line_blocks = false +ij_css_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow +ij_css_space_after_colon = true +ij_css_space_before_opening_brace = true +ij_css_use_double_quotes = true +ij_css_value_alignment = do_not_align + +[.editorconfig] +ij_editorconfig_align_group_field_declarations = false +ij_editorconfig_space_after_colon = false +ij_editorconfig_space_after_comma = true +ij_editorconfig_space_before_colon = false +ij_editorconfig_space_before_comma = false +ij_editorconfig_spaces_around_assignment_operators = true + +[{*.ats,*.cts,*.mts,*.ts}] +ij_continuation_indent_size = 4 +ij_typescript_align_imports = false +ij_typescript_align_multiline_array_initializer_expression = false +ij_typescript_align_multiline_binary_operation = false +ij_typescript_align_multiline_chained_methods = false +ij_typescript_align_multiline_extends_list = false +ij_typescript_align_multiline_for = true +ij_typescript_align_multiline_parameters = true +ij_typescript_align_multiline_parameters_in_calls = false +ij_typescript_align_multiline_ternary_operation = false +ij_typescript_align_object_properties = 0 +ij_typescript_align_union_types = false +ij_typescript_align_var_statements = 0 +ij_typescript_array_initializer_new_line_after_left_brace = false +ij_typescript_array_initializer_right_brace_on_new_line = false +ij_typescript_array_initializer_wrap = off +ij_typescript_assignment_wrap = off +ij_typescript_binary_operation_sign_on_next_line = false +ij_typescript_binary_operation_wrap = off +ij_typescript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** +ij_typescript_blank_lines_after_imports = 1 +ij_typescript_blank_lines_around_class = 1 +ij_typescript_blank_lines_around_field = 0 +ij_typescript_blank_lines_around_field_in_interface = 0 +ij_typescript_blank_lines_around_function = 1 +ij_typescript_blank_lines_around_method = 1 +ij_typescript_blank_lines_around_method_in_interface = 1 +ij_typescript_block_brace_style = end_of_line +ij_typescript_block_comment_add_space = false +ij_typescript_block_comment_at_first_column = true +ij_typescript_call_parameters_new_line_after_left_paren = false +ij_typescript_call_parameters_right_paren_on_new_line = false +ij_typescript_call_parameters_wrap = off +ij_typescript_catch_on_new_line = false +ij_typescript_chained_call_dot_on_new_line = true +ij_typescript_class_brace_style = end_of_line +ij_typescript_class_decorator_wrap = split_into_lines +ij_typescript_class_field_decorator_wrap = off +ij_typescript_class_method_decorator_wrap = off +ij_typescript_comma_on_new_line = false +ij_typescript_do_while_brace_force = never +ij_typescript_else_on_new_line = false +ij_typescript_enforce_trailing_comma = keep +ij_typescript_enum_constants_wrap = on_every_item +ij_typescript_extends_keyword_wrap = off +ij_typescript_extends_list_wrap = off +ij_typescript_field_prefix = _ +ij_typescript_file_name_style = relaxed +ij_typescript_finally_on_new_line = false +ij_typescript_for_brace_force = never +ij_typescript_for_statement_new_line_after_left_paren = false +ij_typescript_for_statement_right_paren_on_new_line = false +ij_typescript_for_statement_wrap = off +ij_typescript_force_quote_style = false +ij_typescript_force_semicolon_style = false +ij_typescript_function_expression_brace_style = end_of_line +ij_typescript_function_parameter_decorator_wrap = off +ij_typescript_if_brace_force = never +ij_typescript_import_merge_members = global +ij_typescript_import_prefer_absolute_path = global +ij_typescript_import_sort_members = true +ij_typescript_import_sort_module_name = false +ij_typescript_import_use_node_resolution = true +ij_typescript_imports_wrap = on_every_item +ij_typescript_indent_case_from_switch = true +ij_typescript_indent_chained_calls = true +ij_typescript_indent_package_children = 0 +ij_typescript_jsdoc_include_types = false +ij_typescript_jsx_attribute_value = braces +ij_typescript_keep_blank_lines_in_code = 2 +ij_typescript_keep_first_column_comment = true +ij_typescript_keep_indents_on_empty_lines = false +ij_typescript_keep_line_breaks = true +ij_typescript_keep_simple_blocks_in_one_line = false +ij_typescript_keep_simple_methods_in_one_line = false +ij_typescript_line_comment_add_space = true +ij_typescript_line_comment_at_first_column = false +ij_typescript_method_brace_style = end_of_line +ij_typescript_method_call_chain_wrap = off +ij_typescript_method_parameters_new_line_after_left_paren = false +ij_typescript_method_parameters_right_paren_on_new_line = false +ij_typescript_method_parameters_wrap = off +ij_typescript_object_literal_wrap = on_every_item +ij_typescript_object_types_wrap = on_every_item +ij_typescript_parentheses_expression_new_line_after_left_paren = false +ij_typescript_parentheses_expression_right_paren_on_new_line = false +ij_typescript_place_assignment_sign_on_next_line = false +ij_typescript_prefer_as_type_cast = false +ij_typescript_prefer_explicit_types_function_expression_returns = false +ij_typescript_prefer_explicit_types_function_returns = false +ij_typescript_prefer_explicit_types_vars_fields = false +ij_typescript_prefer_parameters_wrap = false +ij_typescript_property_prefix = +ij_typescript_reformat_c_style_comments = false +ij_typescript_space_after_colon = true +ij_typescript_space_after_comma = true +ij_typescript_space_after_dots_in_rest_parameter = false +ij_typescript_space_after_generator_mult = true +ij_typescript_space_after_property_colon = true +ij_typescript_space_after_quest = true +ij_typescript_space_after_type_colon = true +ij_typescript_space_after_unary_not = false +ij_typescript_space_before_async_arrow_lparen = true +ij_typescript_space_before_catch_keyword = true +ij_typescript_space_before_catch_left_brace = true +ij_typescript_space_before_catch_parentheses = true +ij_typescript_space_before_class_lbrace = true +ij_typescript_space_before_class_left_brace = true +ij_typescript_space_before_colon = true +ij_typescript_space_before_comma = false +ij_typescript_space_before_do_left_brace = true +ij_typescript_space_before_else_keyword = true +ij_typescript_space_before_else_left_brace = true +ij_typescript_space_before_finally_keyword = true +ij_typescript_space_before_finally_left_brace = true +ij_typescript_space_before_for_left_brace = true +ij_typescript_space_before_for_parentheses = true +ij_typescript_space_before_for_semicolon = false +ij_typescript_space_before_function_left_parenth = true +ij_typescript_space_before_generator_mult = false +ij_typescript_space_before_if_left_brace = true +ij_typescript_space_before_if_parentheses = true +ij_typescript_space_before_method_call_parentheses = false +ij_typescript_space_before_method_left_brace = true +ij_typescript_space_before_method_parentheses = false +ij_typescript_space_before_property_colon = false +ij_typescript_space_before_quest = true +ij_typescript_space_before_switch_left_brace = true +ij_typescript_space_before_switch_parentheses = true +ij_typescript_space_before_try_left_brace = true +ij_typescript_space_before_type_colon = false +ij_typescript_space_before_unary_not = false +ij_typescript_space_before_while_keyword = true +ij_typescript_space_before_while_left_brace = true +ij_typescript_space_before_while_parentheses = true +ij_typescript_spaces_around_additive_operators = true +ij_typescript_spaces_around_arrow_function_operator = true +ij_typescript_spaces_around_assignment_operators = true +ij_typescript_spaces_around_bitwise_operators = true +ij_typescript_spaces_around_equality_operators = true +ij_typescript_spaces_around_logical_operators = true +ij_typescript_spaces_around_multiplicative_operators = true +ij_typescript_spaces_around_relational_operators = true +ij_typescript_spaces_around_shift_operators = true +ij_typescript_spaces_around_unary_operator = false +ij_typescript_spaces_within_array_initializer_brackets = false +ij_typescript_spaces_within_brackets = false +ij_typescript_spaces_within_catch_parentheses = false +ij_typescript_spaces_within_for_parentheses = false +ij_typescript_spaces_within_if_parentheses = false +ij_typescript_spaces_within_imports = false +ij_typescript_spaces_within_interpolation_expressions = false +ij_typescript_spaces_within_method_call_parentheses = false +ij_typescript_spaces_within_method_parentheses = false +ij_typescript_spaces_within_object_literal_braces = false +ij_typescript_spaces_within_object_type_braces = true +ij_typescript_spaces_within_parentheses = false +ij_typescript_spaces_within_switch_parentheses = false +ij_typescript_spaces_within_type_assertion = false +ij_typescript_spaces_within_union_types = true +ij_typescript_spaces_within_while_parentheses = false +ij_typescript_special_else_if_treatment = true +ij_typescript_ternary_operation_signs_on_next_line = false +ij_typescript_ternary_operation_wrap = off +ij_typescript_union_types_wrap = on_every_item +ij_typescript_use_chained_calls_group_indents = false +ij_typescript_use_double_quotes = true +ij_typescript_use_explicit_js_extension = auto +ij_typescript_use_import_type = auto +ij_typescript_use_path_mapping = always +ij_typescript_use_public_modifier = false +ij_typescript_use_semicolon_after_statement = true +ij_typescript_var_declaration_wrap = normal +ij_typescript_while_brace_force = never +ij_typescript_while_on_new_line = false +ij_typescript_wrap_comments = false + +[{*.bash,*.sh,*.zsh}] +indent_size = 2 +tab_width = 2 +ij_shell_binary_ops_start_line = false +ij_shell_keep_column_alignment_padding = false +ij_shell_minify_program = false +ij_shell_redirect_followed_by_space = false +ij_shell_switch_cases_indented = false +ij_shell_use_unix_line_separator = true + +[{*.cjs,*.es6,*.js,*.mjs}] +ij_continuation_indent_size = 4 +ij_javascript_align_imports = false +ij_javascript_align_multiline_array_initializer_expression = false +ij_javascript_align_multiline_binary_operation = false +ij_javascript_align_multiline_chained_methods = false +ij_javascript_align_multiline_extends_list = false +ij_javascript_align_multiline_for = true +ij_javascript_align_multiline_parameters = true +ij_javascript_align_multiline_parameters_in_calls = false +ij_javascript_align_multiline_ternary_operation = false +ij_javascript_align_object_properties = 0 +ij_javascript_align_union_types = false +ij_javascript_align_var_statements = 0 +ij_javascript_array_initializer_new_line_after_left_brace = false +ij_javascript_array_initializer_right_brace_on_new_line = false +ij_javascript_array_initializer_wrap = off +ij_javascript_assignment_wrap = off +ij_javascript_binary_operation_sign_on_next_line = false +ij_javascript_binary_operation_wrap = off +ij_javascript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** +ij_javascript_blank_lines_after_imports = 1 +ij_javascript_blank_lines_around_class = 1 +ij_javascript_blank_lines_around_field = 0 +ij_javascript_blank_lines_around_function = 1 +ij_javascript_blank_lines_around_method = 1 +ij_javascript_block_brace_style = end_of_line +ij_javascript_block_comment_add_space = false +ij_javascript_block_comment_at_first_column = true +ij_javascript_call_parameters_new_line_after_left_paren = false +ij_javascript_call_parameters_right_paren_on_new_line = false +ij_javascript_call_parameters_wrap = off +ij_javascript_catch_on_new_line = false +ij_javascript_chained_call_dot_on_new_line = true +ij_javascript_class_brace_style = end_of_line +ij_javascript_class_decorator_wrap = split_into_lines +ij_javascript_class_field_decorator_wrap = off +ij_javascript_class_method_decorator_wrap = off +ij_javascript_comma_on_new_line = false +ij_javascript_do_while_brace_force = never +ij_javascript_else_on_new_line = false +ij_javascript_enforce_trailing_comma = keep +ij_javascript_extends_keyword_wrap = off +ij_javascript_extends_list_wrap = off +ij_javascript_field_prefix = _ +ij_javascript_file_name_style = relaxed +ij_javascript_finally_on_new_line = false +ij_javascript_for_brace_force = never +ij_javascript_for_statement_new_line_after_left_paren = false +ij_javascript_for_statement_right_paren_on_new_line = false +ij_javascript_for_statement_wrap = off +ij_javascript_force_quote_style = false +ij_javascript_force_semicolon_style = false +ij_javascript_function_expression_brace_style = end_of_line +ij_javascript_function_parameter_decorator_wrap = off +ij_javascript_if_brace_force = never +ij_javascript_import_merge_members = global +ij_javascript_import_prefer_absolute_path = global +ij_javascript_import_sort_members = true +ij_javascript_import_sort_module_name = false +ij_javascript_import_use_node_resolution = true +ij_javascript_imports_wrap = on_every_item +ij_javascript_indent_case_from_switch = true +ij_javascript_indent_chained_calls = true +ij_javascript_indent_package_children = 0 +ij_javascript_jsx_attribute_value = braces +ij_javascript_keep_blank_lines_in_code = 2 +ij_javascript_keep_first_column_comment = true +ij_javascript_keep_indents_on_empty_lines = false +ij_javascript_keep_line_breaks = true +ij_javascript_keep_simple_blocks_in_one_line = false +ij_javascript_keep_simple_methods_in_one_line = false +ij_javascript_line_comment_add_space = true +ij_javascript_line_comment_at_first_column = false +ij_javascript_method_brace_style = end_of_line +ij_javascript_method_call_chain_wrap = off +ij_javascript_method_parameters_new_line_after_left_paren = false +ij_javascript_method_parameters_right_paren_on_new_line = false +ij_javascript_method_parameters_wrap = off +ij_javascript_object_literal_wrap = on_every_item +ij_javascript_object_types_wrap = on_every_item +ij_javascript_parentheses_expression_new_line_after_left_paren = false +ij_javascript_parentheses_expression_right_paren_on_new_line = false +ij_javascript_place_assignment_sign_on_next_line = false +ij_javascript_prefer_as_type_cast = false +ij_javascript_prefer_explicit_types_function_expression_returns = false +ij_javascript_prefer_explicit_types_function_returns = false +ij_javascript_prefer_explicit_types_vars_fields = false +ij_javascript_prefer_parameters_wrap = false +ij_javascript_property_prefix = +ij_javascript_reformat_c_style_comments = false +ij_javascript_space_after_colon = true +ij_javascript_space_after_comma = true +ij_javascript_space_after_dots_in_rest_parameter = false +ij_javascript_space_after_generator_mult = true +ij_javascript_space_after_property_colon = true +ij_javascript_space_after_quest = true +ij_javascript_space_after_type_colon = true +ij_javascript_space_after_unary_not = false +ij_javascript_space_before_async_arrow_lparen = true +ij_javascript_space_before_catch_keyword = true +ij_javascript_space_before_catch_left_brace = true +ij_javascript_space_before_catch_parentheses = true +ij_javascript_space_before_class_lbrace = true +ij_javascript_space_before_class_left_brace = true +ij_javascript_space_before_colon = true +ij_javascript_space_before_comma = false +ij_javascript_space_before_do_left_brace = true +ij_javascript_space_before_else_keyword = true +ij_javascript_space_before_else_left_brace = true +ij_javascript_space_before_finally_keyword = true +ij_javascript_space_before_finally_left_brace = true +ij_javascript_space_before_for_left_brace = true +ij_javascript_space_before_for_parentheses = true +ij_javascript_space_before_for_semicolon = false +ij_javascript_space_before_function_left_parenth = true +ij_javascript_space_before_generator_mult = false +ij_javascript_space_before_if_left_brace = true +ij_javascript_space_before_if_parentheses = true +ij_javascript_space_before_method_call_parentheses = false +ij_javascript_space_before_method_left_brace = true +ij_javascript_space_before_method_parentheses = false +ij_javascript_space_before_property_colon = false +ij_javascript_space_before_quest = true +ij_javascript_space_before_switch_left_brace = true +ij_javascript_space_before_switch_parentheses = true +ij_javascript_space_before_try_left_brace = true +ij_javascript_space_before_type_colon = false +ij_javascript_space_before_unary_not = false +ij_javascript_space_before_while_keyword = true +ij_javascript_space_before_while_left_brace = true +ij_javascript_space_before_while_parentheses = true +ij_javascript_spaces_around_additive_operators = true +ij_javascript_spaces_around_arrow_function_operator = true +ij_javascript_spaces_around_assignment_operators = true +ij_javascript_spaces_around_bitwise_operators = true +ij_javascript_spaces_around_equality_operators = true +ij_javascript_spaces_around_logical_operators = true +ij_javascript_spaces_around_multiplicative_operators = true +ij_javascript_spaces_around_relational_operators = true +ij_javascript_spaces_around_shift_operators = true +ij_javascript_spaces_around_unary_operator = false +ij_javascript_spaces_within_array_initializer_brackets = false +ij_javascript_spaces_within_brackets = false +ij_javascript_spaces_within_catch_parentheses = false +ij_javascript_spaces_within_for_parentheses = false +ij_javascript_spaces_within_if_parentheses = false +ij_javascript_spaces_within_imports = false +ij_javascript_spaces_within_interpolation_expressions = false +ij_javascript_spaces_within_method_call_parentheses = false +ij_javascript_spaces_within_method_parentheses = false +ij_javascript_spaces_within_object_literal_braces = false +ij_javascript_spaces_within_object_type_braces = true +ij_javascript_spaces_within_parentheses = false +ij_javascript_spaces_within_switch_parentheses = false +ij_javascript_spaces_within_type_assertion = false +ij_javascript_spaces_within_union_types = true +ij_javascript_spaces_within_while_parentheses = false +ij_javascript_special_else_if_treatment = true +ij_javascript_ternary_operation_signs_on_next_line = false +ij_javascript_ternary_operation_wrap = off +ij_javascript_union_types_wrap = on_every_item +ij_javascript_use_chained_calls_group_indents = false +ij_javascript_use_double_quotes = true +ij_javascript_use_explicit_js_extension = auto +ij_javascript_use_import_type = auto +ij_javascript_use_path_mapping = always +ij_javascript_use_public_modifier = false +ij_javascript_use_semicolon_after_statement = true +ij_javascript_var_declaration_wrap = normal +ij_javascript_while_brace_force = never +ij_javascript_while_on_new_line = false +ij_javascript_wrap_comments = false + +[{*.har,*.jsb2,*.jsb3,*.json,*.jsonc,*.postman_collection,*.postman_collection.json,*.postman_environment,*.postman_environment.json,.babelrc,.eslintrc,.prettierrc,.stylelintrc,.ws-context,jest.config}] +indent_size = 2 +ij_json_array_wrapping = split_into_lines +ij_json_keep_blank_lines_in_code = 0 +ij_json_keep_indents_on_empty_lines = false +ij_json_keep_line_breaks = true +ij_json_keep_trailing_comma = false +ij_json_object_wrapping = split_into_lines +ij_json_property_alignment = do_not_align +ij_json_space_after_colon = true +ij_json_space_after_comma = true +ij_json_space_before_colon = false +ij_json_space_before_comma = false +ij_json_spaces_within_braces = false +ij_json_spaces_within_brackets = false +ij_json_wrap_long_lines = false + +[{*.htm,*.html,*.sht,*.shtm,*.shtml}] +ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3 +ij_html_align_attributes = true +ij_html_align_text = false +ij_html_attribute_wrap = normal +ij_html_block_comment_add_space = false +ij_html_block_comment_at_first_column = true +ij_html_do_not_align_children_of_min_lines = 0 +ij_html_do_not_break_if_inline_tags = title,h1,h2,h3,h4,h5,h6,p +ij_html_do_not_indent_children_of_tags = html,body,thead,tbody,tfoot +ij_html_enforce_quotes = false +ij_html_inline_tags = a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var +ij_html_keep_blank_lines = 2 +ij_html_keep_indents_on_empty_lines = false +ij_html_keep_line_breaks = true +ij_html_keep_line_breaks_in_text = true +ij_html_keep_whitespaces = false +ij_html_keep_whitespaces_inside = span,pre,textarea +ij_html_line_comment_at_first_column = true +ij_html_new_line_after_last_attribute = never +ij_html_new_line_before_first_attribute = never +ij_html_quote_style = double +ij_html_remove_new_line_before_tags = br +ij_html_space_after_tag_name = false +ij_html_space_around_equality_in_attribute = false +ij_html_space_inside_empty_tag = false +ij_html_text_wrap = normal + +[{*.markdown,*.md}] +ij_markdown_force_one_space_after_blockquote_symbol = true +ij_markdown_force_one_space_after_header_symbol = true +ij_markdown_force_one_space_after_list_bullet = true +ij_markdown_force_one_space_between_words = true +ij_markdown_format_tables = true +ij_markdown_insert_quote_arrows_on_wrap = true +ij_markdown_keep_indents_on_empty_lines = false +ij_markdown_keep_line_breaks_inside_text_blocks = true +ij_markdown_max_lines_around_block_elements = 1 +ij_markdown_max_lines_around_header = 1 +ij_markdown_max_lines_between_paragraphs = 1 +ij_markdown_min_lines_around_block_elements = 1 +ij_markdown_min_lines_around_header = 1 +ij_markdown_min_lines_between_paragraphs = 1 +ij_markdown_wrap_text_if_long = true +ij_markdown_wrap_text_inside_blockquotes = true + +[{*.yaml,*.yml}] +indent_size = 2 +ij_yaml_align_values_properties = do_not_align +ij_yaml_autoinsert_sequence_marker = true +ij_yaml_block_mapping_on_new_line = false +ij_yaml_indent_sequence_value = true +ij_yaml_keep_indents_on_empty_lines = false +ij_yaml_keep_line_breaks = true +ij_yaml_line_comment_add_space = false +ij_yaml_line_comment_add_space_on_reformat = false +ij_yaml_line_comment_at_first_column = true +ij_yaml_sequence_on_new_line = false +ij_yaml_space_before_colon = false +ij_yaml_spaces_within_braces = true +ij_yaml_spaces_within_brackets = true diff --git a/.gitattributes b/.gitattributes index a7167808..0c62b810 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,6 @@ /test/ export-ignore /docs/ export-ignore +/.editorconfig export-ignore /build.sh export-ignore linguist-vendored /Writerside export-ignore linguist-vendored /benchmark diff --git a/CHANGELOG.md b/CHANGELOG.md index cf049876..96a5bffe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,34 @@ # Changelog +# v0.9.0 + +validation +- [x] validate invalid pseudo classes +- [x] rewrite selector validation +- [ ] lenient mode that preserves + - [ ] unknown at-rules + - [ ] unknown declarations + - [ ] unknown pseudo classes + +media query level 5 +- [x] at-rule custom-media +- [x] at-rule when-else custom media +- [x] at-rule charset validation +- [x] media query error handling +- [x] at-rule container +- [ ] expand at-rule custom-media +- [ ] expand at-rule when-else + +selector validation +- [ ] pseudo class arguments validation +- [ ] pseudo class validation + +declaration validation +- [ ] validate declaration + +error validation +- [ ] when a parent is marked as invalid node, do not parse or validate descendant nodes + # v0.8.0 - [x] validate selectors using mdn data diff --git a/README.md b/README.md index bd289c86..3d465f97 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![playground](https://img.shields.io/badge/playground-try%20it%20now-%230a7398 -)](https://tbela99.github.io/css-parser/playground/)[![npm](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Ftbela99%2Fcss-parser%2Fmaster%2Fpackage.json&query=version&logo=npm&label=npm&link=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2F%40tbela99%2Fcss-parser)](https://www.npmjs.com/package/@tbela99/css-parser) [![npm](https://img.shields.io/jsr/v/%40tbela99/css-parser?link=https%3A%2F%2Fjsr.io%2F%40tbela99%2Fcss-parser +)](https://tbela99.github.io/css-parser/playground/) [![npm](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Ftbela99%2Fcss-parser%2Fmaster%2Fpackage.json&query=version&logo=npm&label=npm&link=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2F%40tbela99%2Fcss-parser)](https://www.npmjs.com/package/@tbela99/css-parser) [![npm](https://img.shields.io/jsr/v/%40tbela99/css-parser?link=https%3A%2F%2Fjsr.io%2F%40tbela99%2Fcss-parser )](https://jsr.io/@tbela99/css-parser) [![cov](https://tbela99.github.io/css-parser/badges/coverage.svg)](https://github.com/tbela99/css-parser/actions) [![NPM Downloads](https://img.shields.io/npm/dm/%40tbela99%2Fcss-parser)](https://www.npmjs.com/package/@tbela99/css-parser) # css-parser @@ -164,10 +164,11 @@ Include ParseOptions and RenderOptions in the :root {} or html {} rule. - removeEmpty: boolean, optional. remove empty rule lists from the ast. -> Minify Options +> Validation Options - validation: boolean, optional. enable strict css validation using (mdn data)[https://github.com/mdn/data]. only the selector is validated at this time. +- lenient: boolean, optional. ignore unknown at-rules, pseudo-classes and declarations. > Sourcemap Options @@ -187,7 +188,8 @@ Include ParseOptions and RenderOptions > Minify Options -- minify: boolean, optional. default to _true_. minify css output. +- beautify: boolean, optional. default to _false_. beautify css output. +- minify: boolean, optional. default to _true_. minify css values. - withParents: boolean, optional. render this node and its parents. - removeEmpty: boolean, optional. remove empty rule lists from the ast. - expandNestingRules: boolean, optional. expand nesting rules. diff --git a/dist/index-umd-web.js b/dist/index-umd-web.js index ee5e2f99..418210c4 100644 --- a/dist/index-umd-web.js +++ b/dist/index-umd-web.js @@ -8,6 +8,7 @@ (function (ValidationLevel) { ValidationLevel[ValidationLevel["Valid"] = 0] = "Valid"; ValidationLevel[ValidationLevel["Drop"] = 1] = "Drop"; + ValidationLevel[ValidationLevel["Lenient"] = 2] = "Lenient"; /* preserve unknown at-rules, declarations and pseudo-classes */ })(ValidationLevel || (ValidationLevel = {})); exports.EnumToken = void 0; (function (EnumToken) { @@ -204,7 +205,7 @@ } }; const colorFuncColorSpace = ['srgb', 'srgb-linear', 'display-p3', 'prophoto-rgb', 'a98-rgb', 'rec2020', 'xyz', 'xyz-d65', 'xyz-d50']; - ({ typ: exports.EnumToken.IdenTokenType, val: 'none' }); + ({ typ: exports.EnumToken.IdenTokenType}); const D50 = [0.3457 / 0.3585, 1.00000, (1.0 - 0.3457 - 0.3585) / 0.3585]; const k = Math.pow(29, 3) / Math.pow(3, 3); const e = Math.pow(6, 3) / Math.pow(29, 3); @@ -735,7 +736,7 @@ 1.2914855378640917399 * b, 3); return lsrgb2srgbvalues( /* r: */ - +4.076741661347994 * L - + 4.076741661347994 * L - 3.307711590408193 * M + 0.230969928729428 * S, /* g: */ @@ -3332,17 +3333,27 @@ } } } + /** + * render ast + * @param data + * @param options + */ function doRender(data, options = {}) { + const minify = options.minify ?? true; + const beautify = options.beautify ?? !minify; options = { - ...(options.minify ?? true ? { + ...(beautify ? { + indent: ' ', + newLine: '\n', + } : { indent: '', newLine: '', + }), + ...(minify ? { removeEmpty: true, removeComments: true } : { - indent: ' ', - newLine: '\n', - compress: false, + removeEmpty: false, removeComments: false, }), sourcemap: false, convertColor: true, expandNestingRules: false, preserveLicense: false, ...options }; @@ -3410,7 +3421,18 @@ } update(position, str); } - // @ts-ignore + /** + * render ast node + * @param data + * @param options + * @param sourcemap + * @param position + * @param errors + * @param reducer + * @param cache + * @param level + * @param indents + */ function renderAstNode(data, options, sourcemap, position, errors, reducer, cache, level = 0, indents = []) { if (indents.length < level + 1) { indents.push(options.indent.repeat(level)); @@ -3503,8 +3525,15 @@ // return renderToken(data as Token, options, cache, reducer, errors); throw new Error(`render: unexpected token ${JSON.stringify(data, null, 1)}`); } - return ''; } + /** + * render ast token + * @param token + * @param options + * @param cache + * @param reducer + * @param errors + */ function renderToken(token, options = {}, cache = Object.create(null), reducer, errors) { if (reducer == null) { reducer = function (acc, curr) { @@ -3934,6 +3963,341 @@ 'aural', 'braille', 'embossed', 'handheld', 'projection', 'tty', 'tv', 'speech']; // https://www.w3.org/TR/css-values-4/#math-function const mathFuncs = ['calc', 'clamp', 'min', 'max', 'round', 'mod', 'rem', 'sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'atan2', 'pow', 'sqrt', 'hypot', 'log', 'exp', 'abs', 'sign']; + const webkitPseudoAliasMap = { + '-webkit-autofill': 'autofill', + '-webkit-any': 'is', + '-moz-any': 'is', + '-webkit-border-after': 'border-block-end', + '-webkit-border-after-color': 'border-block-end-color', + '-webkit-border-after-style': 'border-block-end-style', + '-webkit-border-after-width': 'border-block-end-width', + '-webkit-border-before': 'border-block-start', + '-webkit-border-before-color': 'border-block-start-color', + '-webkit-border-before-style': 'border-block-start-style', + '-webkit-border-before-width': 'border-block-start-width', + '-webkit-border-end': 'border-inline-end', + '-webkit-border-end-color': 'border-inline-end-color', + '-webkit-border-end-style': 'border-inline-end-style', + '-webkit-border-end-width': 'border-inline-end-width', + '-webkit-border-start': 'border-inline-start', + '-webkit-border-start-color': 'border-inline-start-color', + '-webkit-border-start-style': 'border-inline-start-style', + '-webkit-border-start-width': 'border-inline-start-width', + '-webkit-box-align': 'align-items', + '-webkit-box-direction': 'flex-direction', + '-webkit-box-flex': 'flex-grow', + '-webkit-box-lines': 'flex-flow', + '-webkit-box-ordinal-group': 'order', + '-webkit-box-orient': 'flex-direction', + '-webkit-box-pack': 'justify-content', + '-webkit-column-break-after': 'break-after', + '-webkit-column-break-before': 'break-before', + '-webkit-column-break-inside': 'break-inside', + '-webkit-font-feature-settings': 'font-feature-settings', + '-webkit-hyphenate-character': 'hyphenate-character', + '-webkit-initial-letter': 'initial-letter', + '-webkit-margin-end': 'margin-block-end', + '-webkit-margin-start': 'margin-block-start', + '-webkit-padding-after': 'padding-block-end', + '-webkit-padding-before': 'padding-block-start', + '-webkit-padding-end': 'padding-inline-end', + '-webkit-padding-start': 'padding-inline-start', + '-webkit-min-device-pixel-ratio': 'min-resolution', + '-webkit-max-device-pixel-ratio': 'max-resolution' + }; + // https://developer.mozilla.org/en-US/docs/Web/CSS/WebKit_Extensions + // https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-scrollbar + const webkitExtensions = new Set([ + '-webkit-app-region', + '-webkit-border-horizontal-spacing', + '-webkit-border-vertical-spacing', + '-webkit-box-reflect', + '-webkit-column-axis', + '-webkit-column-progression', + '-webkit-cursor-visibility', + '-webkit-font-smoothing', + '-webkit-hyphenate-limit-after', + '-webkit-hyphenate-limit-before', + '-webkit-hyphenate-limit-lines', + '-webkit-line-align', + '-webkit-line-box-contain', + '-webkit-line-clamp', + '-webkit-line-grid', + '-webkit-line-snap', + '-webkit-locale', + '-webkit-logical-height', + '-webkit-logical-width', + '-webkit-margin-after', + '-webkit-margin-before', + '-webkit-mask-box-image-outset', + '-webkit-mask-box-image-repeat', + '-webkit-mask-box-image-slice', + '-webkit-mask-box-image-source', + '-webkit-mask-box-image-width', + '-webkit-mask-box-image', + '-webkit-mask-composite', + '-webkit-mask-position-x', + '-webkit-mask-position-y', + '-webkit-mask-repeat-x', + '-webkit-mask-repeat-y', + '-webkit-mask-source-type', + '-webkit-max-logical-height', + '-webkit-max-logical-width', + '-webkit-min-logical-height', + '-webkit-min-logical-width', + '-webkit-nbsp-mode', + '-webkit-perspective-origin-x', + '-webkit-perspective-origin-y', + '-webkit-rtl-ordering', + '-webkit-tap-highlight-color', + '-webkit-text-decoration-skip', + '-webkit-text-decorations-in-effect', + '-webkit-text-fill-color', + '-webkit-text-security', + '-webkit-text-stroke-color', + '-webkit-text-stroke-width', + '-webkit-text-stroke', + '-webkit-text-zoom', + '-webkit-touch-callout', + '-webkit-transform-origin-x', + '-webkit-transform-origin-y', + '-webkit-transform-origin-z', + '-webkit-user-drag', + '-webkit-user-modify', + '-webkit-border-after', + '-webkit-border-after-color', + '-webkit-border-after-style', + '-webkit-border-after-width', + '-webkit-border-before', + '-webkit-border-before-color', + '-webkit-border-before-style', + '-webkit-border-before-width', + '-webkit-border-end', + '-webkit-border-end-color', + '-webkit-border-end-style', + '-webkit-border-end-width', + '-webkit-border-start', + '-webkit-border-start-color', + '-webkit-border-start-style', + '-webkit-border-start-width', + '-webkit-box-align', + '-webkit-box-direction', + '-webkit-box-flex-group', + '-webkit-box-flex', + '-webkit-box-lines', + '-webkit-box-ordinal-group', + '-webkit-box-orient', + '-webkit-box-pack', + '-webkit-column-break-after', + '-webkit-column-break-before', + '-webkit-column-break-inside', + '-webkit-font-feature-settings', + '-webkit-hyphenate-character', + '-webkit-initial-letter', + '-webkit-margin-end', + '-webkit-margin-start', + '-webkit-padding-after', + '-webkit-padding-before', + '-webkit-padding-end', + '-webkit-padding-start', + '-webkit-fill-available', + ':-webkit-animating-full-screen-transition', + ':-webkit-any', + ':-webkit-any-link', + ':-webkit-autofill', + ':-webkit-autofill-strong-password', + ':-webkit-drag', + ':-webkit-full-page-media', + ':-webkit-full-screen*', + ':-webkit-full-screen-ancestor', + ':-webkit-full-screen-document', + ':-webkit-full-screen-controls-hidden', + '::-webkit-file-upload-button*', + '::-webkit-inner-spin-button', + '::-webkit-input-placeholder', + '::-webkit-meter-bar', + '::-webkit-meter-even-less-good-value', + '::-webkit-meter-inner-element', + '::-webkit-meter-optimum-value', + '::-webkit-meter-suboptimum-value', + '::-webkit-progress-bar', + '::-webkit-progress-inner-element', + '::-webkit-progress-value', + '::-webkit-search-cancel-button', + '::-webkit-search-results-button', + '::-webkit-slider-runnable-track', + '::-webkit-slider-thumb', + '-webkit-animation', + '-webkit-device-pixel-ratio', + '-webkit-transform-2d', + '-webkit-transform-3d', + '-webkit-transition', + '::-webkit-scrollbar', + '::-webkit-scrollbar-button', + '::-webkit-scrollbar', + '::-webkit-scrollbar-thumb', + '::-webkit-scrollbar-track', + '::-webkit-scrollbar-track-piece', + '::-webkit-scrollbar:vertical', + '::-webkit-scrollbar-corner ', + '::-webkit-resizer', + ':vertical', + ':horizontal', + ]); + // https://developer.mozilla.org/en-US/docs/Web/CSS/Mozilla_Extensions + const mozExtensions = new Set([ + '-moz-box-align', + '-moz-box-direction', + '-moz-box-flex', + '-moz-box-ordinal-group', + '-moz-box-orient', + '-moz-box-pack', + '-moz-float-edge', + '-moz-force-broken-image-icon', + '-moz-image-region', + '-moz-orient', + '-moz-osx-font-smoothing', + '-moz-user-focus', + '-moz-user-input', + '-moz-user-modify', + '-moz-animation', + '-moz-animation-delay', + '-moz-animation-direction', + '-moz-animation-duration', + '-moz-animation-fill-mode', + '-moz-animation-iteration-count', + '-moz-animation-name', + '-moz-animation-play-state', + '-moz-animation-timing-function', + '-moz-appearance', + '-moz-backface-visibility', + '-moz-background-clip', + '-moz-background-origin', + '-moz-background-inline-policy', + '-moz-background-size', + '-moz-border-end', + '-moz-border-end-color', + '-moz-border-end-style', + '-moz-border-end-width', + '-moz-border-image', + '-moz-border-start', + '-moz-border-start-color', + '-moz-border-start-style', + '-moz-border-start-width', + '-moz-box-sizing', + 'clip-path', + '-moz-column-count', + '-moz-column-fill', + '-moz-column-gap', + '-moz-column-width', + '-moz-column-rule', + '-moz-column-rule-width', + '-moz-column-rule-style', + '-moz-column-rule-color', + 'filter', + '-moz-font-feature-settings', + '-moz-font-language-override', + '-moz-hyphens', + '-moz-margin-end', + '-moz-margin-start', + 'mask', + '-moz-opacity', + '-moz-outline', + '-moz-outline-color', + '-moz-outline-offset', + '-moz-outline-style', + '-moz-outline-width', + '-moz-padding-end', + '-moz-padding-start', + '-moz-perspective', + '-moz-perspective-origin', + 'pointer-events', + '-moz-tab-size', + '-moz-text-align-last', + '-moz-text-decoration-color', + '-moz-text-decoration-line', + '-moz-text-decoration-style', + '-moz-text-size-adjust', + '-moz-transform', + '-moz-transform-origin', + '-moz-transform-style', + '-moz-transition', + '-moz-transition-delay', + '-moz-transition-duration', + '-moz-transition-property', + '-moz-transition-timing-function', + '-moz-user-select', + '-moz-initial', + '-moz-appearance', + '-moz-linear-gradient', + '-moz-radial-gradient', + '-moz-element', + '-moz-image-rect', + '::-moz-anonymous-block', + '::-moz-anonymous-positioned-block', + ':-moz-any', + ':-moz-any-link', + ':-moz-broken', + '::-moz-canvas', + '::-moz-color-swatch', + '::-moz-cell-content', + ':-moz-drag-over', + ':-moz-first-node', + '::-moz-focus-inner', + '::-moz-focus-outer', + ':-moz-full-screen', + ':-moz-full-screen-ancestor', + ':-moz-handler-blocked', + ':-moz-handler-crashed', + ':-moz-handler-disabled', + '::-moz-inline-table', + ':-moz-last-node', + '::-moz-list-bullet', + '::-moz-list-number', + ':-moz-loading', + ':-moz-locale-dir', + ':-moz-locale-dir', + ':-moz-lwtheme', + ':-moz-lwtheme-brighttext', + ':-moz-lwtheme-darktext', + '::-moz-meter-bar', + ':-moz-native-anonymous', + ':-moz-only-whitespace', + '::-moz-pagebreak', + '::-moz-pagecontent', + ':-moz-placeholder', + '::-moz-placeholder', + '::-moz-progress-bar', + '::-moz-range-progress', + '::-moz-range-thumb', + '::-moz-range-track', + ':-moz-read-only', + ':-moz-read-write', + '::-moz-scrolled-canvas', + '::-moz-scrolled-content', + '::-moz-selection', + ':-moz-submit-invalid', + ':-moz-suppressed', + '::-moz-svg-foreign-content', + '::-moz-table', + '::-moz-table-cell', + '::-moz-table-column', + '::-moz-table-column-group', + '::-moz-table-outer', + '::-moz-table-row', + '::-moz-table-row-group', + ':-moz-ui-invalid', + ':-moz-ui-valid', + ':-moz-user-disabled', + '::-moz-viewport', + '::-moz-viewport-scroll', + ':-moz-window-inactive', + '-moz-device-pixel-ratio', + '-moz-os-version', + '-moz-touch-enabled', + '-moz-windows-glass', + '-moz-alt-content' + ]); function isLength(dimension) { return 'unit' in dimension && dimensionUnits.has(dimension.unit.toLowerCase()); } @@ -5954,7 +6318,13 @@ return count; } function pushToken(token, parseInfo, hint) { - const result = { token, hint, position: { ...parseInfo.position }, bytesIn: parseInfo.currentPosition.ind + 1 }; + const result = { + token, + len: parseInfo.currentPosition.ind - parseInfo.position.ind, + hint, + position: { ...parseInfo.position }, + bytesIn: parseInfo.currentPosition.ind + 1 + }; parseInfo.position.ind = parseInfo.currentPosition.ind; parseInfo.position.lin = parseInfo.currentPosition.lin; parseInfo.position.col = Math.max(parseInfo.currentPosition.col, 1); @@ -6074,6 +6444,10 @@ } return char; } + /** + * tokenize css string + * @param stream + */ function* tokenize$1(stream) { const parseInfo = { stream, @@ -6122,8 +6496,10 @@ buffer += value; } } - yield pushToken(buffer, parseInfo, exports.EnumToken.BadCommentTokenType); - buffer = ''; + if (buffer.length > 0) { + yield pushToken(buffer, parseInfo, exports.EnumToken.BadCommentTokenType); + buffer = ''; + } } break; case '&': @@ -7499,9 +7875,6 @@ "inline-size": { syntax: "<'width'>" }, - "input-security": { - syntax: "auto | none" - }, inset: { syntax: "<'top'>{1,4}" }, @@ -8060,6 +8433,9 @@ "shape-rendering": { syntax: "auto | optimizeSpeed | crispEdges | geometricPrecision" }, + "speak-as": { + syntax: "normal | spell-out || digits || [ literal-punctuation | no-punctuation ]" + }, "stop-color": { syntax: "<'color'>" }, @@ -8172,7 +8548,7 @@ syntax: "none | auto | " }, "text-spacing-trim": { - syntax: "space-all | normal | space-first | trim-start | trim-both | trim-all | auto" + syntax: "space-all | normal | space-first | trim-start" }, "text-transform": { syntax: "none | capitalize | uppercase | lowercase | full-width | full-size-kana" @@ -8268,7 +8644,7 @@ syntax: "normal | pre | nowrap | pre-wrap | pre-line | break-spaces | [ <'white-space-collapse'> || <'text-wrap'> ]" }, "white-space-collapse": { - syntax: "collapse | discard | preserve | preserve-breaks | preserve-spaces | break-spaces" + syntax: "collapse | preserve | preserve-breaks | preserve-spaces | break-spaces" }, widows: { syntax: "" @@ -8330,10 +8706,10 @@ syntax: "attr( ? [, ]? )" }, blur: { - syntax: "blur( )" + syntax: "blur( ? )" }, brightness: { - syntax: "brightness( )" + syntax: "brightness( [ | ]? )" }, calc: { syntax: "calc( )" @@ -8357,7 +8733,7 @@ syntax: "conic-gradient( [ from ]? [ at ]?, )" }, contrast: { - syntax: "contrast( [ ] )" + syntax: "contrast( [ | ]? )" }, cos: { syntax: "cos( )" @@ -8372,7 +8748,7 @@ syntax: "cross-fade( , ? )" }, "drop-shadow": { - syntax: "drop-shadow( {2,3} ? )" + syntax: "drop-shadow( [ ? && {2,3} ] )" }, element: { syntax: "element( )" @@ -8390,7 +8766,7 @@ syntax: "fit-content( )" }, grayscale: { - syntax: "grayscale( )" + syntax: "grayscale( [ | ]? )" }, hsl: { syntax: "hsl( [ / ]? ) | hsl( , , , ? )" @@ -8399,7 +8775,7 @@ syntax: "hsla( [ / ]? ) | hsla( , , , ? )" }, "hue-rotate": { - syntax: "hue-rotate( )" + syntax: "hue-rotate( [ | ]? )" }, hwb: { syntax: "hwb( [ | none] [ | none] [ | none] [ / [ | none] ]? )" @@ -8417,7 +8793,7 @@ syntax: "inset( {1,4} [ round <'border-radius'> ]? )" }, invert: { - syntax: "invert( )" + syntax: "invert( [ | ]? )" }, lab: { syntax: "lab( [ | | none] [ | | none] [ | | none] [ / [ | none] ]? )" @@ -8465,7 +8841,7 @@ syntax: "oklch( [ | | none] [ | | none] [ | none] [ / [ | none] ]? )" }, opacity: { - syntax: "opacity( [ ] )" + syntax: "opacity( [ | ]? )" }, paint: { syntax: "paint( , ? )" @@ -8474,13 +8850,13 @@ syntax: "palette-mix( , [ [normal | light | dark | | ] && ? ]#{2})" }, path: { - syntax: "path( [ , ]? )" + syntax: "path( <'fill-rule'>? , )" }, perspective: { syntax: "perspective( [ | none ] )" }, polygon: { - syntax: "polygon( ? , [ ]# )" + syntax: "polygon( <'fill-rule'>? , [ ]# )" }, pow: { syntax: "pow( , )" @@ -8528,7 +8904,7 @@ syntax: "round( ?, , )" }, saturate: { - syntax: "saturate( )" + syntax: "saturate( [ | ]? )" }, scale: { syntax: "scale( [ | ]#{1,2} )" @@ -8549,7 +8925,7 @@ syntax: "scroll( [ || ]? )" }, sepia: { - syntax: "sepia( )" + syntax: "sepia( [ | ]? )" }, sign: { syntax: "sign( )" @@ -8704,10 +9080,10 @@ syntax: "normal | multiply | screen | overlay | darken | lighten | color-dodge | color-burn | hard-light | soft-light | difference | exclusion | hue | saturation | color | luminosity" }, "blur()": { - syntax: "blur( )" + syntax: "blur( ? )" }, "brightness()": { - syntax: "brightness( )" + syntax: "brightness( [ | ]? )" }, "calc()": { syntax: "calc( )" @@ -8749,7 +9125,13 @@ syntax: "" }, color: { - syntax: " | | | | | | | | | | | | | | currentcolor | transparent" + syntax: " | currentColor | | | " + }, + "color-base": { + syntax: " | | | | transparent" + }, + "color-function": { + syntax: " | | | | | | | | | " }, "color()": { syntax: "color( [from ]? [ / [ | none ] ]? )" @@ -8821,7 +9203,7 @@ syntax: "[ contextual | no-contextual ]" }, "contrast()": { - syntax: "contrast( [ ] )" + syntax: "contrast( [ | ]? )" }, "coord-box": { syntax: " | view-box" @@ -8863,7 +9245,7 @@ syntax: "[ [ | ]+ ]#" }, "deprecated-system-color": { - syntax: "ActiveBorder | ActiveCaption | AppWorkspace | Background | ButtonFace | ButtonHighlight | ButtonShadow | ButtonText | CaptionText | GrayText | Highlight | HighlightText | InactiveBorder | InactiveCaption | InactiveCaptionText | InfoBackground | InfoText | Menu | MenuText | Scrollbar | ThreeDDarkShadow | ThreeDFace | ThreeDHighlight | ThreeDLightShadow | ThreeDShadow | Window | WindowFrame | WindowText" + syntax: "ActiveBorder | ActiveCaption | AppWorkspace | Background | ButtonHighlight | ButtonShadow | CaptionText | InactiveBorder | InactiveCaption | InactiveCaptionText | InfoBackground | InfoText | Menu | MenuText | Scrollbar | ThreeDDarkShadow | ThreeDFace | ThreeDHighlight | ThreeDLightShadow | ThreeDShadow | Window | WindowFrame | WindowText" }, "discretionary-lig-values": { syntax: "[ discretionary-ligatures | no-discretionary-ligatures ]" @@ -8887,7 +9269,7 @@ syntax: "block | inline | run-in" }, "drop-shadow()": { - syntax: "drop-shadow( {2,3} ? )" + syntax: "drop-shadow( [ ? && {2,3} ] )" }, "easing-function": { syntax: "linear | | " @@ -8940,9 +9322,6 @@ "feature-value-name": { syntax: "" }, - "fill-rule": { - syntax: "nonzero | evenodd" - }, "filter-function": { syntax: " | | | | | | | | | " }, @@ -8992,7 +9371,7 @@ syntax: " | | | | | " }, "grayscale()": { - syntax: "grayscale( )" + syntax: "grayscale( [ | ]? )" }, "grid-line": { syntax: "auto | | [ && ? ] | [ span && [ || ] ]" @@ -9013,7 +9392,7 @@ syntax: "[ shorter | longer | increasing | decreasing ] hue" }, "hue-rotate()": { - syntax: "hue-rotate( )" + syntax: "hue-rotate( [ | ]? )" }, "hwb()": { syntax: "hwb( [ | none] [ | none] [ | none] [ / [ | none] ]? )" @@ -9034,7 +9413,7 @@ syntax: "image-set( # )" }, "image-set-option": { - syntax: "[ | ]x [ || type() ]" + syntax: "[ | ] [ || type() ]" }, "image-src": { syntax: " | " @@ -9049,7 +9428,7 @@ syntax: "inset( {1,4} [ round <'border-radius'> ]? )" }, "invert()": { - syntax: "invert( )" + syntax: "invert( [ | ]? )" }, "keyframe-block": { syntax: "# {\n \n}" @@ -9193,7 +9572,7 @@ syntax: "repeat( [ | auto-fill ], + )" }, "named-color": { - syntax: "transparent | aliceblue | antiquewhite | aqua | aquamarine | azure | beige | bisque | black | blanchedalmond | blue | blueviolet | brown | burlywood | cadetblue | chartreuse | chocolate | coral | cornflowerblue | cornsilk | crimson | cyan | darkblue | darkcyan | darkgoldenrod | darkgray | darkgreen | darkgrey | darkkhaki | darkmagenta | darkolivegreen | darkorange | darkorchid | darkred | darksalmon | darkseagreen | darkslateblue | darkslategray | darkslategrey | darkturquoise | darkviolet | deeppink | deepskyblue | dimgray | dimgrey | dodgerblue | firebrick | floralwhite | forestgreen | fuchsia | gainsboro | ghostwhite | gold | goldenrod | gray | green | greenyellow | grey | honeydew | hotpink | indianred | indigo | ivory | khaki | lavender | lavenderblush | lawngreen | lemonchiffon | lightblue | lightcoral | lightcyan | lightgoldenrodyellow | lightgray | lightgreen | lightgrey | lightpink | lightsalmon | lightseagreen | lightskyblue | lightslategray | lightslategrey | lightsteelblue | lightyellow | lime | limegreen | linen | magenta | maroon | mediumaquamarine | mediumblue | mediumorchid | mediumpurple | mediumseagreen | mediumslateblue | mediumspringgreen | mediumturquoise | mediumvioletred | midnightblue | mintcream | mistyrose | moccasin | navajowhite | navy | oldlace | olive | olivedrab | orange | orangered | orchid | palegoldenrod | palegreen | paleturquoise | palevioletred | papayawhip | peachpuff | peru | pink | plum | powderblue | purple | rebeccapurple | red | rosybrown | royalblue | saddlebrown | salmon | sandybrown | seagreen | seashell | sienna | silver | skyblue | slateblue | slategray | slategrey | snow | springgreen | steelblue | tan | teal | thistle | tomato | turquoise | violet | wheat | white | whitesmoke | yellow | yellowgreen" + syntax: "aliceblue | antiquewhite | aqua | aquamarine | azure | beige | bisque | black | blanchedalmond | blue | blueviolet | brown | burlywood | cadetblue | chartreuse | chocolate | coral | cornflowerblue | cornsilk | crimson | cyan | darkblue | darkcyan | darkgoldenrod | darkgray | darkgreen | darkgrey | darkkhaki | darkmagenta | darkolivegreen | darkorange | darkorchid | darkred | darksalmon | darkseagreen | darkslateblue | darkslategray | darkslategrey | darkturquoise | darkviolet | deeppink | deepskyblue | dimgray | dimgrey | dodgerblue | firebrick | floralwhite | forestgreen | fuchsia | gainsboro | ghostwhite | gold | goldenrod | gray | green | greenyellow | grey | honeydew | hotpink | indianred | indigo | ivory | khaki | lavender | lavenderblush | lawngreen | lemonchiffon | lightblue | lightcoral | lightcyan | lightgoldenrodyellow | lightgray | lightgreen | lightgrey | lightpink | lightsalmon | lightseagreen | lightskyblue | lightslategray | lightslategrey | lightsteelblue | lightyellow | lime | limegreen | linen | magenta | maroon | mediumaquamarine | mediumblue | mediumorchid | mediumpurple | mediumseagreen | mediumslateblue | mediumspringgreen | mediumturquoise | mediumvioletred | midnightblue | mintcream | mistyrose | moccasin | navajowhite | navy | oldlace | olive | olivedrab | orange | orangered | orchid | palegoldenrod | palegreen | paleturquoise | palevioletred | papayawhip | peachpuff | peru | pink | plum | powderblue | purple | rebeccapurple | red | rosybrown | royalblue | saddlebrown | salmon | sandybrown | seagreen | seashell | sienna | silver | skyblue | slateblue | slategray | slategrey | snow | springgreen | steelblue | tan | teal | thistle | tomato | turquoise | violet | wheat | white | whitesmoke | yellow | yellowgreen" }, "namespace-prefix": { syntax: "" @@ -9226,7 +9605,7 @@ syntax: "oklch( [ | | none] [ | | none] [ | none] [ / [ | none] ]? )" }, "opacity()": { - syntax: "opacity( [ ] )" + syntax: "opacity( [ | ]? )" }, "opacity-value": { syntax: " | " @@ -9274,7 +9653,7 @@ syntax: "palette-mix( , [ [normal | light | dark | | ] && ? ]#{2})" }, "path()": { - syntax: "path( [ , ]? )" + syntax: "path( <'fill-rule'>? , )" }, "perspective()": { syntax: "perspective( [ | none ] )" @@ -9283,7 +9662,7 @@ syntax: "hsl | hwb | lch | oklch" }, "polygon()": { - syntax: "polygon( ? , [ ]# )" + syntax: "polygon( <'fill-rule'>? , [ ]# )" }, position: { syntax: "[ [ left | center | right ] || [ top | center | bottom ] | [ left | center | right | ] [ top | center | bottom | ]? | [ [ left | right ] ] && [ [ top | bottom ] ] ]" @@ -9382,7 +9761,7 @@ syntax: "nearest | up | down | to-zero" }, "saturate()": { - syntax: "saturate( )" + syntax: "saturate( [ | ]? )" }, "scale()": { syntax: "scale( [ | ]#{1,2} )" @@ -9418,7 +9797,7 @@ syntax: "center | start | end | self-start | self-end | flex-start | flex-end" }, "sepia()": { - syntax: "sepia( )" + syntax: "sepia( [ | ]? )" }, shadow: { syntax: "inset? && {2,4} && ?" @@ -9983,19 +10362,103 @@ syntax: "@charset \"\";" }, "@counter-style": { - syntax: "@counter-style {\n [ system: ; ] ||\n [ symbols: ; ] ||\n [ additive-symbols: ; ] ||\n [ negative: ; ] ||\n [ prefix: ; ] ||\n [ suffix: ; ] ||\n [ range: ; ] ||\n [ pad: ; ] ||\n [ speak-as: ; ] ||\n [ fallback: ; ]\n}" + syntax: "@counter-style {\n [ system: ; ] ||\n [ symbols: ; ] ||\n [ additive-symbols: ; ] ||\n [ negative: ; ] ||\n [ prefix: ; ] ||\n [ suffix: ; ] ||\n [ range: ; ] ||\n [ pad: ; ] ||\n [ speak-as: ; ] ||\n [ fallback: ; ]\n}", + descriptors: { + "additive-symbols": { + syntax: "[ && ]#" + }, + fallback: { + syntax: "" + }, + negative: { + syntax: " ?" + }, + pad: { + syntax: " && " + }, + prefix: { + syntax: "" + }, + range: { + syntax: "[ [ | infinite ]{2} ]# | auto" + }, + "speak-as": { + syntax: "auto | bullets | numbers | words | spell-out | " + }, + suffix: { + syntax: "" + }, + symbols: { + syntax: "+" + }, + system: { + syntax: "cyclic | numeric | alphabetic | symbolic | additive | [ fixed ? ] | [ extends ]" + } + } }, "@document": { syntax: "@document [ | url-prefix() | domain() | media-document() | regexp() ]# {\n \n}" }, "@font-face": { - syntax: "@font-face {\n [ font-family: ; ] ||\n [ src: ; ] ||\n [ unicode-range: ; ] ||\n [ font-variant: ; ] ||\n [ font-feature-settings: ; ] ||\n [ font-variation-settings: ; ] ||\n [ font-stretch: ; ] ||\n [ font-weight: ; ] ||\n [ font-style: ; ] ||\n [ size-adjust: ; ] ||\n [ ascent-override: ; ] ||\n [ descent-override: ; ] ||\n [ line-gap-override: ; ]\n}" + syntax: "@font-face {\n [ font-family: ; ] ||\n [ src: ; ] ||\n [ unicode-range: ; ] ||\n [ font-variant: ; ] ||\n [ font-feature-settings: ; ] ||\n [ font-variation-settings: ; ] ||\n [ font-stretch: ; ] ||\n [ font-weight: ; ] ||\n [ font-style: ; ] ||\n [ size-adjust: ; ] ||\n [ ascent-override: ; ] ||\n [ descent-override: ; ] ||\n [ line-gap-override: ; ]\n}", + descriptors: { + "ascent-override": { + syntax: "normal | " + }, + "descent-override": { + syntax: "normal | " + }, + "font-display": { + syntax: "[ auto | block | swap | fallback | optional ]" + }, + "font-family": { + syntax: "" + }, + "font-feature-settings": { + syntax: "normal | #" + }, + "font-stretch": { + syntax: "{1,2}" + }, + "font-style": { + syntax: "normal | italic | oblique {0,2}" + }, + "font-variation-settings": { + syntax: "normal | [ ]#" + }, + "font-weight": { + syntax: "{1,2}" + }, + "line-gap-override": { + syntax: "normal | " + }, + "size-adjust": { + syntax: "" + }, + src: { + syntax: "[ [ format( # ) ]? | local( ) ]#" + }, + "unicode-range": { + syntax: "#" + } + } }, "@font-feature-values": { syntax: "@font-feature-values # {\n \n}" }, "@font-palette-values": { - syntax: "@font-palette-values {\n \n}" + syntax: "@font-palette-values {\n \n}", + descriptors: { + "base-palette": { + syntax: "light | dark | " + }, + "font-family": { + syntax: "#" + }, + "override-colors": { + syntax: "[ ]#" + } + } }, "@import": { syntax: "@import [ | ]\n [ layer | layer() ]?\n [ supports( [ | ] ) ]?\n ? ;" @@ -10013,13 +10476,38 @@ syntax: "@namespace ? [ | ];" }, "@page": { - syntax: "@page {\n \n}" + syntax: "@page {\n \n}", + descriptors: { + bleed: { + syntax: "auto | " + }, + marks: { + syntax: "none | [ crop || cross ]" + }, + "page-orientation": { + syntax: "upright | rotate-left | rotate-right " + }, + size: { + syntax: "{1,2} | auto | [ || [ portrait | landscape ] ]" + } + } }, "@position-try": { syntax: "@position-try {\n \n}" }, "@property": { - syntax: "@property {\n \n}" + syntax: "@property {\n \n}", + descriptors: { + inherits: { + syntax: "true | false" + }, + "initial-value": { + syntax: "?" + }, + syntax: { + syntax: "" + } + } }, "@scope": { syntax: "@scope [()]? [to ()]? {\n \n}" @@ -10031,7 +10519,15 @@ syntax: "@supports {\n \n}" }, "@view-transition": { - syntax: "@view-transition {\n \n}" + syntax: "@view-transition {\n \n}", + descriptors: { + navigation: { + syntax: "auto | none" + }, + types: { + syntax: "none | +" + } + } } }; var config$3 = { @@ -10085,6 +10581,7 @@ ValidationTokenEnum[ValidationTokenEnum["DeclarationDefinitionToken"] = 37] = "DeclarationDefinitionToken"; ValidationTokenEnum[ValidationTokenEnum["SemiColon"] = 38] = "SemiColon"; ValidationTokenEnum[ValidationTokenEnum["Character"] = 39] = "Character"; + ValidationTokenEnum[ValidationTokenEnum["ColumnArrayToken"] = 40] = "ColumnArrayToken"; })(ValidationTokenEnum || (ValidationTokenEnum = {})); var ValidationSyntaxGroupEnum; (function (ValidationSyntaxGroupEnum) { @@ -10095,6 +10592,20 @@ ValidationSyntaxGroupEnum["AtRules"] = "atRules"; })(ValidationSyntaxGroupEnum || (ValidationSyntaxGroupEnum = {})); + var WalkValidationTokenEnum; + (function (WalkValidationTokenEnum) { + WalkValidationTokenEnum[WalkValidationTokenEnum["IgnoreChildren"] = 0] = "IgnoreChildren"; + WalkValidationTokenEnum[WalkValidationTokenEnum["IgnoreNode"] = 1] = "IgnoreNode"; + WalkValidationTokenEnum[WalkValidationTokenEnum["IgnoreAll"] = 2] = "IgnoreAll"; + })(WalkValidationTokenEnum || (WalkValidationTokenEnum = {})); + var WalkValidationTokenKeyTypeEnum; + (function (WalkValidationTokenKeyTypeEnum) { + WalkValidationTokenKeyTypeEnum["Array"] = "array"; + WalkValidationTokenKeyTypeEnum["Children"] = "chi"; + WalkValidationTokenKeyTypeEnum["Left"] = "l"; + WalkValidationTokenKeyTypeEnum["Right"] = "r"; + WalkValidationTokenKeyTypeEnum["Prelude"] = "prelude"; + })(WalkValidationTokenKeyTypeEnum || (WalkValidationTokenKeyTypeEnum = {})); const skipped = [ ValidationTokenEnum.Star, ValidationTokenEnum.HashMark, @@ -10261,13 +10772,66 @@ yield getTokenType$1(buffer, position, currentPosition); } } + function columnCallback(token, parent, key) { + if (key == WalkValidationTokenKeyTypeEnum.Prelude) { + return WalkValidationTokenEnum.IgnoreAll; + } + if (token.typ == ValidationTokenEnum.ColumnToken || token.typ == ValidationTokenEnum.Whitespace) { + return WalkValidationTokenEnum.IgnoreNode; + } + return WalkValidationTokenEnum.IgnoreChildren; + } + function toColumnArray(ast, parent) { + const result = new Map; + // @ts-ignore + for (const { token } of walkValidationToken(ast, null, columnCallback)) { + result.set(JSON.stringify(token), token); + } + const node = { + typ: ValidationTokenEnum.ColumnArrayToken, + chi: [...result.values()] + }; + if (parent != null) { + replaceNode(parent, ast, node); + } + return node; + } + function replaceNode(parent, target, node) { + if ('l' in parent && parent.l == target) { + parent.l = node; + } + if ('r' in parent && parent.r == target) { + parent.r = node; + } + if ('chi' in parent) { + // @ts-ignore + for (let i = 0; i < parent.chi.length; i++) { + // @ts-ignore + if (parent.chi[i] == target) { + // @ts-ignore + parent.chi[i] = node; + break; + } + } + } + } + function transform$1(context) { + for (const { token, parent } of walkValidationToken(context)) { + switch (token.typ) { + case ValidationTokenEnum.ColumnToken: + toColumnArray(token, parent); + break; + } + } + return context; + } function parseSyntax(syntax) { const root = Object.defineProperty({ typ: ValidationTokenEnum.Root, chi: [] }, 'pos', { ...objectProperties, value: { ind: 0, lin: 1, col: 0 } }); // return minify(doParseSyntax(syntaxes, tokenize(syntaxes), root)) as ValidationRootToken; - return minify$1(doParseSyntax(syntax, tokenize(syntax), root)); + return minify$1(transform$1(doParseSyntax(syntax, tokenize(syntax), root))); } function matchParens(syntax, iterator) { let item; @@ -11035,6 +11599,10 @@ (token.chi == null ? '' : ' {\n' + token.chi.reduce((acc, curr) => acc + renderSyntax(curr), '')).slice(1, -1) + '\n}'; case ValidationTokenEnum.Block: return '{' + token.chi.reduce((acc, t) => acc + renderSyntax(t), '') + '}'; + case ValidationTokenEnum.DeclarationDefinitionToken: + return token.nam + ': ' + renderSyntax(token.val); + case ValidationTokenEnum.ColumnArrayToken: + return token.chi.reduce((acc, curr) => acc + (acc.trim().length > 0 ? '||' : '') + renderSyntax(curr), ''); default: throw new Error('Unhandled token: ' + JSON.stringify({ token })); } @@ -11126,35 +11694,42 @@ } return ast; } - function* walkValidationToken(token, parent, callback) { + function* walkValidationToken(token, parent, callback, key) { if (Array.isArray(token)) { for (const child of token) { - yield* walkValidationToken(child, parent); + yield* walkValidationToken(child, parent, callback, WalkValidationTokenKeyTypeEnum.Array); } return; } - yield { token, parent }; - if ('prelude' in token) { + let action = null; + if (callback != null) { + // @ts-ignore + action = callback(token, parent, key); + } + if (action != WalkValidationTokenEnum.IgnoreNode && action != WalkValidationTokenEnum.IgnoreAll) { + yield { token, parent }; + } + if (action != WalkValidationTokenEnum.IgnoreChildren && action != WalkValidationTokenEnum.IgnoreAll && 'prelude' in token) { for (const child of token.prelude) { - yield* walkValidationToken(child, token); + yield* walkValidationToken(child, token, callback, WalkValidationTokenKeyTypeEnum.Prelude); } } - if ('chi' in token) { + if (action != WalkValidationTokenEnum.IgnoreChildren && 'chi' in token) { // @ts-ignore for (const child of token.chi) { - yield* walkValidationToken(child, token); + yield* walkValidationToken(child, token, callback, WalkValidationTokenKeyTypeEnum.Children); } } - if ('l' in token) { + if (action != WalkValidationTokenEnum.IgnoreChildren && action != WalkValidationTokenEnum.IgnoreAll && 'l' in token) { // @ts-ignore for (const child of token.l) { - yield* walkValidationToken(child, token); + yield* walkValidationToken(child, token, callback, WalkValidationTokenKeyTypeEnum.Left); } } - if ('r' in token) { + if (action != WalkValidationTokenEnum.IgnoreChildren && action != WalkValidationTokenEnum.IgnoreAll && 'r' in token) { // @ts-ignore for (const child of token.r) { - yield* walkValidationToken(child, token); + yield* walkValidationToken(child, token, callback, WalkValidationTokenKeyTypeEnum.Right); } } } @@ -11166,25 +11741,30 @@ return config$3; } function getParsedSyntax(group, key) { - if (!(key in config$3[group])) { - const matches = key.match(/(@?)(-[a-zA-Z]+)-(.*?)$/); - if (matches != null) { - key = matches[1] + matches[3]; - } - if (!(key in config$3[group])) { - return null; + // @ts-ignore + let obj = config$3[group]; + const keys = Array.isArray(key) ? key : [key]; + for (let i = 0; i < keys.length; i++) { + key = keys[i]; + if (!(key in obj)) { + if ((i == 0 && key.charAt(0) == '@') || key.charAt(0) == '-') { + const matches = key.match(/^(@?)(-[a-zA-Z]+)-(.*?)$/); + if (matches != null) { + key = matches[1] + matches[3]; + } + } + if (!(key in obj)) { + return null; + } } + // @ts-ignore + obj = obj[key]; } - const index = group + '.' + key; + const index = group + '.' + keys.join('.'); // @ts-ignore if (!parsedSyntaxes.has(index)) { // @ts-ignore - const syntax = parseSyntax(config$3[group][key].syntax); - for (const node of syntax.chi) { - for (const { token, parent } of walkValidationToken(node)) { - token.text = renderSyntax(token); - } - } + const syntax = parseSyntax(obj.syntax); // @ts-ignore parsedSyntaxes.set(index, syntax.chi); } @@ -11262,12 +11842,12 @@ return true; } - function splitTokenList(tokenList) { + function splitTokenList(tokenList, split = [exports.EnumToken.CommaTokenType]) { return tokenList.reduce((acc, curr) => { if (curr.typ == exports.EnumToken.CommentTokenType) { return acc; } - if (curr.typ == exports.EnumToken.CommaTokenType) { + if (split.includes(curr.typ)) { acc.push([]); } else { @@ -11358,38 +11938,27 @@ }; } - const combinatorsTokens = [exports.EnumToken.ChildCombinatorTokenType, exports.EnumToken.ColumnCombinatorTokenType, - exports.EnumToken.DescendantCombinatorTokenType, exports.EnumToken.NextSiblingCombinatorTokenType, exports.EnumToken.SubsequentSiblingCombinatorTokenType]; - // [ ? ]* - function validateComplexSelector(tokens, root, options) { - // [ ? * [ * ]* ]! - tokens = tokens.slice(); - consumeWhitespace(tokens); + function validateCompoundSelector(tokens, root, options) { if (tokens.length == 0) { + // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], // @ts-ignore node: root, + // @ts-ignore syntax: null, error: 'expected selector', tokens }; } + tokens = tokens.slice(); + consumeWhitespace(tokens); + const config = getSyntaxConfig(); + let match = 0; + let length = tokens.length; while (tokens.length > 0) { - if (combinatorsTokens.includes(tokens[0].typ)) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - // @ts-ignore - node: tokens[0], - syntax: null, - error: 'unexpected combinator', - tokens - }; - } - if (tokens[0].typ == exports.EnumToken.NestingSelectorTokenType) { + while (tokens.length > 0 && tokens[0].typ == exports.EnumToken.NestingSelectorTokenType) { if (!options?.nestedSelector) { // @ts-ignore return { @@ -11402,28 +11971,28 @@ tokens }; } - while (tokens.length > 0 && tokens[0].typ == exports.EnumToken.NestingSelectorTokenType) { - tokens.shift(); - consumeWhitespace(tokens); - } - if (tokens.length == 0) { - break; - } - } - if (exports.EnumToken.IdenTokenType == tokens[0].typ) { + match++; tokens.shift(); consumeWhitespace(tokens); - if (tokens.length == 0) { - break; - } } - if (exports.EnumToken.UniversalSelectorTokenType == tokens[0].typ) { + // + while (tokens.length > 0 && + [ + exports.EnumToken.IdenTokenType, + exports.EnumToken.NameSpaceAttributeTokenType, + exports.EnumToken.ClassSelectorTokenType, + exports.EnumToken.HashTokenType, + exports.EnumToken.UniversalSelectorTokenType + ].includes(tokens[0].typ)) { + match++; tokens.shift(); consumeWhitespace(tokens); } - while (tokens.length > 0) { - if (tokens[0].typ == exports.EnumToken.PseudoClassFuncTokenType) { - if (tokens[0].val.startsWith(':-webkit-')) { + while (tokens.length > 0 && tokens[0].typ == exports.EnumToken.PseudoClassFuncTokenType) { + if (!mozExtensions.has(tokens[0].val + '()') && + !webkitExtensions.has(tokens[0].val + '()') && + !((tokens[0].val + '()') in config.selectors)) { + if (!options?.lenient || /^(:?)-webkit-/.test(tokens[0].val)) { // @ts-ignore return { valid: ValidationLevel.Drop, @@ -11431,23 +12000,26 @@ // @ts-ignore node: tokens[0], syntax: null, - error: 'invalid pseudo-class', + error: 'unknown pseudo-class: ' + tokens[0].val + '()', tokens }; } } - if ([ - exports.EnumToken.ClassSelectorTokenType, - exports.EnumToken.HashTokenType, - exports.EnumToken.PseudoClassTokenType, - exports.EnumToken.PseudoClassFuncTokenType - ].includes(tokens[0].typ)) { - tokens.shift(); - consumeWhitespace(tokens); - continue; - } - if (tokens[0].typ == exports.EnumToken.NestingSelectorTokenType) { - if (!options?.nestedSelector) { + match++; + tokens.shift(); + consumeWhitespace(tokens); + } + while (tokens.length > 0 && tokens[0].typ == exports.EnumToken.PseudoClassTokenType) { + const isPseudoElement = tokens[0].val.startsWith('::'); + if ( + // https://developer.mozilla.org/en-US/docs/Web/CSS/WebKit_Extensions#pseudo-elements + !(isPseudoElement && tokens[0].val.startsWith('::-webkit-')) && + !mozExtensions.has(tokens[0].val) && + !webkitExtensions.has(tokens[0].val) && + !(tokens[0].val in config.selectors) && + !(!isPseudoElement && + (':' + tokens[0].val) in config.selectors)) { + if (!options?.lenient || /^(:?)-webkit-/.test(tokens[0].val)) { // @ts-ignore return { valid: ValidationLevel.Drop, @@ -11455,37 +12027,68 @@ // @ts-ignore node: tokens[0], syntax: null, - error: 'nested selector not allowed', + error: 'unknown pseudo-class: ' + tokens[0].val, tokens }; } - tokens.shift(); - consumeWhitespace(tokens); - continue; } - // validate namespace - if (tokens[0].typ == exports.EnumToken.NameSpaceAttributeTokenType) { - if (!((tokens[0].l == null || tokens[0].l.typ == exports.EnumToken.IdenTokenType || (tokens[0].l.typ == exports.EnumToken.LiteralTokenType && tokens[0].l.val == '*')) && - tokens[0].r.typ == exports.EnumToken.IdenTokenType)) { - // @ts-ignore + match++; + tokens.shift(); + consumeWhitespace(tokens); + } + while (tokens.length > 0 && tokens[0].typ == exports.EnumToken.AttrTokenType) { + const children = tokens[0].chi.slice(); + consumeWhitespace(children); + if (children.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: null, + error: 'invalid attribute selector', + tokens + }; + } + if (![ + exports.EnumToken.IdenTokenType, + exports.EnumToken.NameSpaceAttributeTokenType, + exports.EnumToken.MatchExpressionTokenType + ].includes(children[0].typ)) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: null, + error: 'invalid attribute selector', + tokens + }; + } + if (children[0].typ == exports.EnumToken.MatchExpressionTokenType) { + if (children.length != 1) { return { valid: ValidationLevel.Drop, matches: [], node: tokens[0], syntax: null, - error: 'expecting wq-name', + error: 'invalid ', tokens }; } - tokens.shift(); - consumeWhitespace(tokens); - continue; - } - // validate attribute - else if (tokens[0].typ == exports.EnumToken.AttrTokenType) { - const children = tokens[0].chi.slice(); - consumeWhitespace(children); - if (children.length == 0) { + if (![ + exports.EnumToken.IdenTokenType, + exports.EnumToken.NameSpaceAttributeTokenType + ].includes(children[0].l.typ) || + ![ + exports.EnumToken.EqualMatchTokenType, exports.EnumToken.DashMatchTokenType, + exports.EnumToken.StartMatchTokenType, exports.EnumToken.ContainMatchTokenType, + exports.EnumToken.EndMatchTokenType, exports.EnumToken.IncludeMatchTokenType + ].includes(children[0].op.typ) || + ![ + exports.EnumToken.StringTokenType, + exports.EnumToken.IdenTokenType + ].includes(children[0].r.typ)) { // @ts-ignore return { valid: ValidationLevel.Drop, @@ -11496,11 +12099,7 @@ tokens }; } - if (![ - exports.EnumToken.IdenTokenType, - exports.EnumToken.NameSpaceAttributeTokenType, - exports.EnumToken.MatchExpressionTokenType - ].includes(children[0].typ)) { + if (children[0].attr != null && !['i', 's'].includes(children[0].attr)) { // @ts-ignore return { valid: ValidationLevel.Drop, @@ -11511,122 +12110,85 @@ tokens }; } - if (children[0].typ == exports.EnumToken.MatchExpressionTokenType) { - if (![exports.EnumToken.IdenTokenType, - exports.EnumToken.NameSpaceAttributeTokenType].includes(children[0].l.typ) || - ![ - exports.EnumToken.EqualMatchTokenType, exports.EnumToken.DashMatchTokenType, - exports.EnumToken.StartMatchTokenType, exports.EnumToken.ContainMatchTokenType, - exports.EnumToken.EndMatchTokenType, exports.EnumToken.IncludeMatchTokenType - ].includes(children[0].op.typ) || - ![exports.EnumToken.StringTokenType, - exports.EnumToken.IdenTokenType].includes(children[0].r.typ)) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0], - syntax: null, - error: 'invalid attribute selector', - tokens - }; - } - if (children[0].attr != null && !['i', 's'].includes(children[0].attr)) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0], - syntax: null, - error: 'invalid attribute selector', - tokens - }; - } - } - children.shift(); - consumeWhitespace(children); - if (children.length > 0) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: children[0], - syntax: null, - error: 'unexpected token', - tokens - }; - } - tokens.shift(); - consumeWhitespace(tokens); - continue; } - break; - } - if (tokens.length == 0) { - break; + match++; + tokens.shift(); + consumeWhitespace(tokens); } - // combinator - if (!combinatorsTokens.includes(tokens[0].typ)) { - if (tokens[0].typ == exports.EnumToken.NestingSelectorTokenType) { - if (!options?.nestedSelector) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - // @ts-ignore - node: tokens[0], - syntax: null, - error: 'nested selector not allowed', - tokens - }; - } - tokens.shift(); - consumeWhitespace(tokens); - continue; - } - if (tokens.length > 0 && - [ - exports.EnumToken.IdenTokenType, - exports.EnumToken.AttrTokenType, - exports.EnumToken.NameSpaceAttributeTokenType, - exports.EnumToken.ClassSelectorTokenType, - exports.EnumToken.HashTokenType, - exports.EnumToken.PseudoClassTokenType, - exports.EnumToken.PseudoClassFuncTokenType - ].includes(tokens[0].typ)) { - continue; - } - // @ts-ignore + if (length == tokens.length) { return { valid: ValidationLevel.Drop, matches: [], + // @ts-ignore node: tokens[0], + // @ts-ignore syntax: null, - error: 'expecting combinator or subclass-selector', + error: 'expected compound selector', tokens }; } - const token = tokens.shift(); - consumeWhitespace(tokens); - if (tokens.length == 0) { + length = tokens.length; + } + return match == 0 ? { + valid: ValidationLevel.Drop, + matches: [], + // @ts-ignore + node: root, + // @ts-ignore + syntax: null, + error: 'expected compound selector', + tokens + } : + // @ts-ignore + { + valid: ValidationLevel.Valid, + matches: [], // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: token, - syntax: null, - error: 'expected compound-selector', - tokens - }; + node: root, + // @ts-ignore + syntax: null, + error: null, + tokens + }; + } + + const combinatorsTokens = [exports.EnumToken.ChildCombinatorTokenType, exports.EnumToken.ColumnCombinatorTokenType, + // EnumToken.DescendantCombinatorTokenType, + exports.EnumToken.NextSiblingCombinatorTokenType, exports.EnumToken.SubsequentSiblingCombinatorTokenType]; + // [ ? ]* + function validateComplexSelector(tokens, root, options) { + // [ ? * [ * ]* ]! + tokens = tokens.slice(); + consumeWhitespace(tokens); + if (tokens.length == 0) { + return { + valid: ValidationLevel.Drop, + matches: [], + // @ts-ignore + node: root, + syntax: null, + error: 'expected selector', + tokens + }; + } + // const config = getSyntaxConfig(); + // + // let match: number = 0; + let result = null; + // const combinators: EnumToken[] = combinatorsTokens.filter((t: EnumToken) => t != EnumToken.DescendantCombinatorTokenType); + for (const t of splitTokenList(tokens, combinatorsTokens)) { + result = validateCompoundSelector(t, root, options); + if (result.valid == ValidationLevel.Drop) { + return result; } } // @ts-ignore - return { - valid: ValidationLevel.Valid, + return result ?? { + valid: ValidationLevel.Drop, matches: [], - node: null, + node: root, syntax: null, - error: '', + error: 'expecting compound-selector', tokens }; } @@ -11658,20 +12220,48 @@ } function validateRelativeSelectorList(tokens, root, options) { - let i = 0; - let j = 0; - let result = null; - while (i + 1 < tokens.length) { - if (tokens[++i].typ == exports.EnumToken.CommaTokenType) { - result = validateRelativeSelector(tokens.slice(j, i), root, options); - if (result.valid == ValidationLevel.Drop) { - return result; - } - j = i + 1; - i = j; + tokens = tokens.slice(); + consumeWhitespace(tokens); + if (tokens.length == 0) { + return { + valid: ValidationLevel.Drop, + matches: [], + // @ts-ignore + node: root, + // @ts-ignore + syntax: null, + error: 'expecting relative selector list', + tokens + }; + } + for (const t of splitTokenList(tokens)) { + if (t.length == 0) { + return { + valid: ValidationLevel.Drop, + matches: [], + // @ts-ignore + node: root, + // @ts-ignore + syntax: null, + error: 'unexpected comma', + tokens + }; + } + const result = validateRelativeSelector(t, root, options); + if (result.valid == ValidationLevel.Drop) { + return result; } } - return validateRelativeSelector(i == j ? tokens.slice(i) : tokens.slice(j, i + 1), root, options); + return { + valid: ValidationLevel.Valid, + matches: [], + // @ts-ignore + node: root, + // @ts-ignore + syntax: null, + error: '', + tokens + }; } function validateComplexSelectorList(tokens, root, options) { @@ -11688,23 +12278,26 @@ tokens }; } - let i = -1; - let j = 0; let result = null; - while (i + 1 < tokens.length) { - if (tokens[++i].typ == exports.EnumToken.CommaTokenType) { - result = validateSelector$1(tokens.slice(j, i), root, options); - if (result.valid == ValidationLevel.Drop) { - return result; - } - j = i + 1; - i = j; + for (const t of splitTokenList(tokens)) { + result = validateSelector$1(t, root, options); + if (result.valid == ValidationLevel.Drop) { + return result; } } - return validateSelector$1(i == j ? tokens.slice(i) : tokens.slice(j, i + 1), root, options); + // @ts-ignore + return result ?? { + valid: ValidationLevel.Drop, + matches: [], + // @ts-ignore + node: root, + syntax: null, + error: 'expecting complex selector list', + tokens + }; } - function validateKeyframeSelector(tokens, atRule) { + function validateKeyframeSelector(tokens, atRule, options) { consumeWhitespace(tokens); if (tokens.length == 0) { // @ts-ignore @@ -11831,7 +12424,7 @@ }; } - function validateKeyframeBlockList(tokens, atRule) { + function validateKeyframeBlockList(tokens, atRule, options) { let i = 0; let j = 0; let result = null; @@ -11848,1079 +12441,969 @@ return validateKeyframeSelector(i == j ? tokens.slice(i) : tokens.slice(j, i + 1), atRule); } - const validateSelectorList = validateComplexSelectorList; - - function validateSelector(selector, options, root) { - if (root == null) { - return validateRelativeSelectorList(selector, root); + const config$2 = getSyntaxConfig(); + function consumeToken(tokens) { + tokens.shift(); + } + function consumeSyntax(syntaxes) { + syntaxes.shift(); + } + function splice(tokens, matches) { + if (matches.length == 0) { + return tokens; } // @ts-ignore - if (root.typ == exports.EnumToken.AtRuleNodeType && root.nam.match(/^(-[a-z]+-)?keyframes$/)) { - return validateKeyframeBlockList(selector, root); - } - let isNested = root.typ == exports.EnumToken.RuleNodeType ? 1 : 0; - let currentRoot = root.parent; - while (currentRoot != null && isNested == 0) { - if (currentRoot.typ == exports.EnumToken.RuleNodeType) { - isNested++; - if (isNested > 0) { - // @ts-ignore - return validateRelativeSelectorList(selector, root, { nestedSelector: true }); - } - } - currentRoot = currentRoot.parent; + const index = tokens.indexOf(matches.at(-1)); + if (index > -1) { + tokens.splice(0, index + 1); } - const nestedSelector = isNested > 0; - // @ts-ignore - return nestedSelector ? validateRelativeSelectorList(selector, root, { nestedSelector }) : validateSelectorList(selector, root, { nestedSelector }); + return tokens; } - - function validateAtRuleMedia(atRule, options, root) { - // media-query-list - if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { + function validateSyntax(syntaxes, tokens, root, options, context = { level: 0 }) { + console.error(JSON.stringify({ + syntax: syntaxes.reduce((acc, curr) => acc + renderSyntax(curr), ''), + syntaxes, + tokens, + s: new Error('bar').stack + }, null, 1)); + if (syntaxes == null) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], - node: atRule, - syntax: '@media', - error: 'expected media query list', - tokens: [] + node: tokens[0] ?? null, + syntax: null, + error: 'no matching syntaxes found', + tokens }; } - const result = validateAtRuleMediaQueryList(atRule.tokens, atRule); - if (result.valid == ValidationLevel.Drop) { - return result; + let token = null; + let syntax; + let result = null; + let validSyntax = false; + let matched = false; + const matches = []; + tokens = tokens.slice(); + syntaxes = syntaxes.slice(); + tokens = tokens.slice(); + if (context.cache == null) { + context.cache = new WeakMap; } - if (!('chi' in atRule)) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: atRule, - syntax: '@media', - error: 'expected at-rule body', - tokens: [] - }; + if (context.tokens == null) { + context.tokens = tokens.slice(); } - // @ts-ignore - return { - valid: ValidationLevel.Valid, - matches: [], - node: atRule, - syntax: '@media', - error: '', - tokens: [] - }; - } - function validateAtRuleMediaQueryList(tokenList, atRule) { - for (const tokens of splitTokenList(tokenList)) { - if (tokens.length == 0) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0] ?? atRule, - syntax: '@media', - error: 'unexpected token', - tokens: [] - }; + context = { ...context }; + main: while (tokens.length > 0) { + if (syntaxes.length == 0) { + break; } - let previousToken = null; - while (tokens.length > 0) { - // media-condition - if (validateMediaCondition(tokens[0])) { - previousToken = tokens[0]; - tokens.shift(); - } - // media-type - else if (validateMediaFeature(tokens[0])) { - previousToken = tokens[0]; + token = tokens[0]; + syntax = syntaxes[0]; + // @ts-ignore + context.position = context.tokens.indexOf(token); + const cached = context.cache.get(token)?.get(syntax.text) ?? null; + if (cached != null) { + if (cached.error.length > 0) { + return { ...cached, tokens, node: cached.valid == ValidationLevel.Valid ? null : token }; + } + syntaxes.shift(); + tokens.shift(); + continue; + } + if (token.typ == exports.EnumToken.DescendantCombinatorTokenType) { + tokens.shift(); + if (syntax.typ == ValidationTokenEnum.Whitespace) { + syntaxes.shift(); + } + continue; + } + else if (syntax.typ == ValidationTokenEnum.Whitespace) { + syntaxes.shift(); + if (token.typ == exports.EnumToken.WhitespaceTokenType) { tokens.shift(); } - if (tokens.length == 0) { - break; + continue; + } + else if (syntax.typ == ValidationTokenEnum.Block && exports.EnumToken.AtRuleTokenType == token.typ && ('chi' in token)) { + syntaxes.shift(); + tokens.shift(); + // @ts-ignore + matches.push(token); + continue; + } + if (syntax.isOptional) { + if (!context.cache.has(token)) { + context.cache.set(token, new Map); } - if (!consumeWhitespace(tokens)) { - if (previousToken?.typ != exports.EnumToken.ParensTokenType) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0] ?? atRule, - syntax: '@media', - error: 'expected media query list', - tokens: [] - }; - } + if (context.cache.get(token).has(syntax.text)) { + result = context.cache.get(token).get(syntax.text); + return { ...result, tokens, node: result.valid == ValidationLevel.Valid ? null : token }; } - if (![exports.EnumToken.MediaFeatureOrTokenType, exports.EnumToken.MediaFeatureAndTokenType].includes(tokens[0].typ)) { + // @ts-ignore + const { isOptional, ...c } = syntax; + // @ts-ignore + let result2; + // @ts-ignore + result2 = validateSyntax([c], tokens, root, options, context); + if (result2.valid == ValidationLevel.Valid && result2.matches.length > 0) { + tokens = result2.tokens; + // splice(tokens, result2.matches); + // tokens = result2.tokens; // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0] ?? atRule, - syntax: '@media', - error: 'expected and/or', - tokens: [] - }; + matches.push(...result2.matches); + matched = true; + result = result2; } - if (tokens.length == 1) { + else { + syntaxes.shift(); + continue; + } + syntaxes.shift(); + if (syntaxes.length == 0) { // @ts-ignore return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0] ?? atRule, - syntax: '@media', - error: 'expected media-condition', - tokens: [] + valid: ValidationLevel.Valid, + matches: result2.matches, + node: result2.node, + syntax: result2.syntax, + error: result2.error, + tokens }; } - tokens.shift(); - if (!consumeWhitespace(tokens)) { + continue; + } + if (syntax.isList) { + let index = -1; + // @ts-ignore + let { isList, ...c } = syntax; + // const c: ValidationToken = {...syntaxes, isList: false} as ValidationToken; + let result2 = null; + validSyntax = false; + do { + for (let i = index + 1; i < tokens.length; i++) { + if (tokens[i].typ == exports.EnumToken.CommaTokenType) { + index = i; + break; + } + } + if (tokens[index + 1]?.typ == exports.EnumToken.CommaTokenType) { + return { + valid: ValidationLevel.Drop, + matches, + node: tokens[0], + syntax, + error: 'unexpected token', + tokens + }; + } + if (index == -1) { + index = tokens.length; + } + if (index == 0) { + break; + } // @ts-ignore + result2 = validateSyntax([c], tokens.slice(0, index), root, options, context); + matched = result2.valid == ValidationLevel.Valid && result2.matches.length > 0; + if (matched) { + const l = tokens.length; + validSyntax = true; + // @ts-ignore + // matches.push(...result2.matches); + // splice(tokens, result2.matches); + if (tokens.length == 1 && tokens[0].typ == exports.EnumToken.CommaTokenType) { + return { + valid: ValidationLevel.Drop, + matches, + node: tokens[0], + syntax, + error: 'unexpected token', + tokens + }; + } + tokens = tokens.slice(index); + result = result2; + // @ts-ignore + matches.push(...result2.matches); + if (result.tokens.length > 0) { + if (index == -1) { + tokens = result.tokens; + } + else { + tokens = tokens.slice(index - result.tokens.length); + } + } + else if (index > 0) { + tokens = tokens.slice(index); + } + index = -1; + if (l == tokens.length) { + break; + } + } + else { + break; + } + } while (tokens.length > 0); + // if (level == 0) { + // } + if (!matched) { return { valid: ValidationLevel.Drop, - matches: [], - node: tokens[0] ?? atRule, - syntax: '@media', - error: 'expected whitespace', - tokens: [] + // @ts-ignore + matches: [...new Set(matches)], + node: token, + syntax, + error: 'unexpected token', + tokens }; } + syntaxes.shift(); + continue; } - } - // @ts-ignore - return { - valid: ValidationLevel.Valid, - matches: [], - node: atRule, - syntax: '@media', - error: '', - tokens: [] - }; - } - function validateMediaCondition(token) { - if (token.typ == exports.EnumToken.MediaFeatureNotTokenType) { - return validateMediaCondition(token.val); - } - if (token.typ != exports.EnumToken.ParensTokenType) { - return false; - } - const chi = token.chi.filter((t) => t.typ != exports.EnumToken.CommentTokenType && t.typ != exports.EnumToken.WhitespaceTokenType); - if (chi.length != 1) { - return false; - } - if (chi[0].typ == exports.EnumToken.IdenTokenType) { - return true; - } - if (chi[0].typ == exports.EnumToken.MediaFeatureNotTokenType) { - return validateMediaCondition(chi[0].val); - } - if (chi[0].typ == exports.EnumToken.MediaQueryConditionTokenType) { - return chi[0].l.typ == exports.EnumToken.IdenTokenType; - } - return false; - } - function validateMediaFeature(token) { - let val = token; - if (token.typ == exports.EnumToken.MediaFeatureOnlyTokenType || token.typ == exports.EnumToken.MediaFeatureNotTokenType) { - val = token.val; - } - return val.typ == exports.EnumToken.MediaFeatureTokenType; - } - - function validateAtRuleCounterStyle(atRule, options, root) { - // media-query-list - if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: atRule, - syntax: '@counter-style', - error: 'expected media query list', - tokens: [] - }; - } - const tokens = atRule.tokens.filter((t) => ![exports.EnumToken.WhitespaceTokenType, exports.EnumToken.CommentTokenType].includes(t.typ)); - if (tokens.length == 0) { - // @ts-ignore - return { - valid: ValidationLevel.Valid, - matches: [], - node: atRule, - syntax: '@counter-style', - error: 'expected counter style name', - tokens - }; - } - if (tokens.length > 1) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[1] ?? atRule, - syntax: '@counter-style', - error: 'unexpected token', - tokens - }; - } - if (![exports.EnumToken.IdenTokenType, exports.EnumToken.DashedIdenTokenType].includes(tokens[0].typ)) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0], - syntax: '@counter-style', - error: 'expected counter style name', - tokens - }; - } - if (!('chi' in atRule)) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: atRule, - syntax: '@counter-style', - error: 'expected counter style body', - tokens - }; - } - // @ts-ignore - return { - valid: ValidationLevel.Valid, - matches: [], - node: atRule, - syntax: '@counter-style', - error: '', - tokens - }; - } - - function validateAtRulePage(atRule, options, root) { - // media-query-list - if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { - // @ts-ignore - return { - valid: ValidationLevel.Valid, - matches: [], - node: null, - syntax: '@page', - error: '', - tokens: [] - }; - } - // page-selector-list - for (const tokens of splitTokenList(atRule.tokens)) { - if (tokens.length == 0) { + if (syntax.isRepeatable) { // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0] ?? atRule, - syntax: '@page', - error: 'unexpected token', - tokens: [] - }; + let { isRepeatable, ...c } = syntax; + let result2 = null; + validSyntax = false; + let l = tokens.length; + let tok = null; + do { + // @ts-ignore + result2 = validateSyntax([c], tokens, root, options, context); + if (result2.matches.length == 0 && result2.error.length > 0) { + syntaxes.shift(); + break main; + } + if (result2.valid == ValidationLevel.Valid) { + tokens = result2.tokens; + // @ts-ignore + matches.push(...result2.matches); + result = result2; + if (l == tokens.length) { + if (tok == tokens[0]) { + break; + } + if (result2.matches.length == 0 && tokens.length > 0) { + tokens = result2.tokens; + tok = tokens[0]; + continue; + } + break; + } + if (matches.length == 0) { + tokens = result2.tokens; + } + l = tokens.length; + continue; + } + break; + } while (result2.valid == ValidationLevel.Valid && tokens.length > 0); + // if (lastResult != null) { + // + // splice(tokens, lastResult.matches); + // // tokens = lastResult.tokens; + // } + syntaxes.shift(); + continue; } - // + | * - // ident pseudo-page* | pseudo-page+ - if (tokens[0].typ == exports.EnumToken.IdenTokenType) { - tokens.shift(); - if (tokens.length == 0) { - continue; - } - // @ts-ignore - if (tokens[0].typ != exports.EnumToken.WhitespaceTokenType) { + // at least one match + if (syntax.isRepeatableGroup) { + validSyntax = false; + let count = 0; + let l = tokens.length; + let result2 = null; + do { + // @ts-ignore + const { isRepeatableGroup, ...c } = syntax; + // @ts-ignore + result2 = validateSyntax([c], tokens, root, options, context); + if (result2.valid == ValidationLevel.Drop || result2.matches.length == 0) { + if (count > 0) { + syntaxes.shift(); + // if (result2.matches.length == 0) { + tokens = result2.tokens; + // break main; + if (syntaxes.length == 0) { + return result2; + } + break main; + } + return result2; + } + if (result2.valid == ValidationLevel.Valid && result2.matches.length > 0) { + count++; + // lastResult = result; + validSyntax = true; + tokens = result2.tokens; + // splice(tokens, result2.matches); + // tokens = result2.tokens; + // @ts-ignore + matches.push(...result2.matches); + result = result2; + if (l == tokens.length) { + break; + } + l = tokens.length; + } + else { + break; + } + } while (tokens.length > 0 && result.valid == ValidationLevel.Valid); + // if (lastResult != null) { + // + // splice(tokens, lastResult.matches); + // // tokens = lastResult.tokens; + // } + // at least one match is expected + if (!validSyntax /* || result.matches.length == 0 */) { // @ts-ignore return { valid: ValidationLevel.Drop, - matches: [], - node: tokens[0] ?? atRule, - syntax: '@page', + node: token, + tokens, + syntax, error: 'unexpected token', - tokens: [] + matches: [] }; } + syntaxes.shift(); + continue; } - while (tokens.length > 0) { - if (tokens[0].typ == exports.EnumToken.PseudoPageTokenType) { - tokens.shift(); - if (tokens.length == 0) { - continue; + if (syntax.atLeastOnce) { + const { atLeastOnce, ...c } = syntax; + result = validateSyntax([c], tokens, root, options, context); + if (result.valid == ValidationLevel.Drop) { + return result; + } + splice(tokens, result.matches); + // tokens = result.tokens; + // @ts-ignore + matches.push(...result.matches); + let l = tokens.length; + let r = validateSyntax([c], tokens, root, options, context); + while (r.valid == ValidationLevel.Valid) { + splice(tokens, r.matches); + // tokens = r.tokens; + r = validateSyntax([c], tokens, root, options, context); + if (l == tokens.length) { + break; } - // @ts-ignore - if (tokens[0].typ != exports.EnumToken.WhitespaceTokenType) { + if (r.valid == ValidationLevel.Valid && r.matches.length > 0) { // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0] ?? atRule, - syntax: '@page', - error: 'unexpected token', - tokens: [] - }; + matches.push(...result.matches); } + l = tokens.length; } + syntaxes.shift(); + continue; } - } - // @ts-ignore - return { - valid: ValidationLevel.Valid, - matches: [], - node: atRule, - syntax: '@page', - error: '', - tokens: [] - }; - } - - function validateAtRulePageMarginBox(atRule, options, root) { - if (Array.isArray(atRule.tokens) && atRule.tokens.length > 0) { - // @ts-ignore - return { - valid: ValidationLevel.Valid, - matches: [], - node: null, - syntax: '@' + atRule.nam, - error: '', - tokens: [] - }; - } - if (!('chi' in atRule)) { // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: atRule, - syntax: '@' + atRule.nam, - error: 'expected margin-box body', - tokens: [] - }; - } - for (const token of atRule.chi) { - if (![exports.EnumToken.DeclarationNodeType, exports.EnumToken.CommentNodeType, exports.EnumToken.WhitespaceTokenType].includes(token.typ)) { + if (syntax.occurence != null) { // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: token, - syntax: 'declaration-list', - error: 'expected margin-box body', - tokens: [] - }; - } - } - // @ts-ignore - return { - valid: ValidationLevel.Valid, - matches: [], - node: null, - syntax: '@' + atRule.nam, - error: '', - tokens: [] - }; - } - - const config$2 = getSyntaxConfig(); - function consumeToken(tokens) { - tokens.shift(); - } - function consumeSyntax(syntaxes) { - syntaxes.shift(); - } - function splice(tokens, matches) { - if (matches.length == 0) { - return tokens; - } - // @ts-ignore - const index = tokens.indexOf(matches.at(-1)); - if (index > -1) { - tokens.splice(0, index + 1); - } - return tokens; - } - function validateSyntax(syntaxes, tokens, root, options, context = { level: 0 }) { - if (syntaxes == null) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0] ?? null, - syntax: null, - error: 'no matching syntaxes found', - tokens - }; - } - let token = null; - let syntax; - let result = null; - let validSyntax = false; - let matched = false; - const matches = []; - tokens = tokens.slice(); - syntaxes = syntaxes.slice(); - tokens = tokens.slice(); - if (context.cache == null) { - context.cache = new WeakMap; - } - if (context.tokens == null) { - context.tokens = tokens.slice(); - } - context = { ...context }; - main: while (tokens.length > 0) { - if (syntaxes.length == 0) { - break; - } - token = tokens[0]; - syntax = syntaxes[0]; - // @ts-ignore - context.position = context.tokens.indexOf(token); - const cached = context.cache.get(token)?.get(syntax.text) ?? null; - if (cached != null) { - if (cached.error.length > 0) { - return { ...cached, tokens, node: cached.valid == ValidationLevel.Valid ? null : token }; + const { occurence, ...c } = syntax; + // && syntaxes.occurence.max != null + // consume all tokens + let match = 1; + // @ts-ignore + result = validateSyntax([c], tokens, root, options, context); + if (result.valid == ValidationLevel.Drop) { + return result; } - syntaxes.shift(); - tokens.shift(); - continue; - } - if (token.typ == exports.EnumToken.DescendantCombinatorTokenType) { - tokens.shift(); - if (syntax.typ == ValidationTokenEnum.Whitespace) { + if (result.matches.length == 0) { syntaxes.shift(); + continue; + } + // splice(tokens, result.matches); + // tokens = result.tokens; + // @ts-ignore + matches.push(...result.matches); + matched = true; + tokens = result.tokens; + while (occurence.max == null || match < occurence.max) { + // trim whitespace + if (tokens[0]?.typ == exports.EnumToken.WhitespaceTokenType) { + tokens.shift(); + } + // @ts-ignore + let r = validateSyntax([c], tokens, root, options, context); + if (r.valid != ValidationLevel.Valid || r.matches.length == 0) { + break; + } + result = r; + // splice(tokens, r.matches); + // tokens = r.tokens; + // @ts-ignore + matches.push(...result.matches); + match++; + tokens = r.tokens; + result = r; + if (tokens.length == 0 || (occurence.max != null && match >= occurence.max)) { + break; + } + // @ts-ignore + // r = validateSyntax([c], tokens, root, options, context); } + syntaxes.shift(); continue; } - else if (syntax.typ == ValidationTokenEnum.Whitespace) { - syntaxes.shift(); + // @ts-ignore + if (syntax.typ == ValidationTokenEnum.Whitespace) { if (token.typ == exports.EnumToken.WhitespaceTokenType) { tokens.shift(); } + syntaxes.shift(); continue; } - else if (syntax.typ == ValidationTokenEnum.Block && exports.EnumToken.AtRuleTokenType == token.typ && ('chi' in token)) { - syntaxes.shift(); - tokens.shift(); + // @ts-ignore + if (token.val != null && specialValues.includes(token.val)) { + matched = true; + result = { + valid: ValidationLevel.Valid, + matches: [token], + node: null, + syntax, + error: '', + tokens + }; // @ts-ignore - matches.push(token); - continue; + matches.push(...result.matches); } - if (syntax.isOptional) { - if (!context.cache.has(token)) { - context.cache.set(token, new Map); - } - if (context.cache.get(token).has(syntax.text)) { - result = context.cache.get(token).get(syntax.text); - return { ...result, tokens, node: result.valid == ValidationLevel.Valid ? null : token }; + else { + result = doValidateSyntax(syntax, token, tokens, root, options, context); + matched = result.valid == ValidationLevel.Valid && result.matches.length > 0; + if (matched) { + // splice(tokens, result.matches); + tokens = result.tokens; + // @ts-ignore + matches.push(...result.matches); } + } + if (result.valid == ValidationLevel.Drop) { // @ts-ignore - const { isOptional, ...c } = syntax; + return { ...result, matches, tokens, node: result.valid == ValidationLevel.Valid ? null : token }; + } + consumeSyntax(syntaxes); + if (tokens.length == 0) { + return result; + } + } + if (result?.valid == ValidationLevel.Valid) { + // splice(tokens, result.matches); + tokens = result.tokens; + // @ts-ignore + matches.push(...result.matches); + } + if ( /* result == null && */tokens.length == 0 && syntaxes.length > 0) { + validSyntax = isOptionalSyntax(syntaxes); + } + if (result == null) { + result = { + valid: validSyntax ? ValidationLevel.Valid : ValidationLevel.Drop, + matches, + node: validSyntax ? null : tokens[0] ?? null, // @ts-ignore - let result2; + syntax, + error: validSyntax ? '' : 'unexpected token', + tokens + }; + } + if (token != null) { + if (!context.cache.has(token)) { + context.cache.set(token, new Map); + } + context.cache.get(token).set(syntax.text, result); + } + if (result != null) { + // @ts-ignore + return { ...result, matches: [...(new Set(matches))] }; + } + return result; + } + function isOptionalSyntax(syntaxes) { + return syntaxes.length > 0 && syntaxes.every(t => t.typ == ValidationTokenEnum.Whitespace || t.isOptional || t.isRepeatable || (t.typ == ValidationTokenEnum.PropertyType && isOptionalSyntax(getParsedSyntax("syntaxes" /* ValidationSyntaxGroupEnum.Syntaxes */, t.val) ?? getParsedSyntax("declarations" /* ValidationSyntaxGroupEnum.Declarations */, t.val) ?? []))); + } + function doValidateSyntax(syntax, token, tokens, root, options, context) { + let valid = false; + let result; + let children; + let queue; + let matches; + let child; + let astNodes = new Set; + if (token.typ == exports.EnumToken.NestingSelectorTokenType && syntax.typ == 2) { + valid = root != null && 'relative-selector' == syntax.val; + return { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], + node: valid ? null : token, + syntax, + error: valid ? '' : 'unexpected token', + tokens + }; + } + switch (syntax.typ) { + case ValidationTokenEnum.Comma: + valid = token.typ === exports.EnumToken.CommaTokenType; // @ts-ignore - result2 = validateSyntax([c], tokens, root, options, context); - if (result2.valid == ValidationLevel.Valid && result2.matches.length > 0) { - tokens = result2.tokens; - // splice(tokens, result2.matches); - // tokens = result2.tokens; + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], + node: valid ? null : token, + syntax, + error: valid ? '' : 'unexpected token', + tokens + }; + break; + case ValidationTokenEnum.AtRule: + if (token.typ != exports.EnumToken.AtRuleNodeType) { // @ts-ignore - matches.push(...result2.matches); - matched = true; - result = result2; - } - else { - syntaxes.shift(); - continue; + return { + valid: ValidationLevel.Drop, + matches: [], + node: token, + syntax, + error: 'expecting at-rule', + tokens + }; } - syntaxes.shift(); - if (syntaxes.length == 0) { + if (token.nam != syntax.val) { // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: token, + syntax, + error: `expecting '@${syntax.val}' but found '@${token.nam}'`, + tokens + }; + } + if (root == null) { return { valid: ValidationLevel.Valid, - matches: result2.matches, - node: result2.node, - syntax: result2.syntax, - error: result2.error, + matches: [token], + node: null, + syntax, + error: '', + tokens + }; + } + if (root.typ != exports.EnumToken.AtRuleNodeType) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: token, + syntax, + error: 'not allowed here', + tokens + }; + } + if (!('chi' in token)) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: token, + syntax, + error: '@at-rule must have children', tokens }; } - continue; - } - if (syntax.isList) { - let index = -1; // @ts-ignore - let { isList, ...c } = syntax; - // const c: ValidationToken = {...syntaxes, isList: false} as ValidationToken; - let result2 = null; - validSyntax = false; - do { - for (let i = index + 1; i < tokens.length; i++) { - if (tokens[i].typ == exports.EnumToken.CommaTokenType) { - index = i; - break; - } - } - if (tokens[index + 1]?.typ == exports.EnumToken.CommaTokenType) { + result = { + valid: ValidationLevel.Valid, + matches: [token], + node: null, + syntax, + error: '', + tokens + }; + break; + case ValidationTokenEnum.AtRuleDefinition: + if (token.typ != exports.EnumToken.AtRuleNodeType) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: token, + syntax, + error: 'expecting at-rule', + tokens + }; + } + if ('chi' in syntax && !('chi' in token)) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: token, + syntax, + error: '@at-rule must have children', + tokens + }; + } + if ('chi' in token && !('chi' in token)) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: token, + syntax, + error: 'children not allowed here', + tokens + }; + } + const s = getParsedSyntax("atRules" /* ValidationSyntaxGroupEnum.AtRules */, '@' + token.nam); + if ('prelude' in syntax) { + if (!('tokens' in token)) { + // @ts-ignore return { valid: ValidationLevel.Drop, - matches, - node: tokens[0], + matches: [], + node: token, syntax, - error: 'unexpected token', + error: 'expected at-rule prelude', tokens }; } - if (index == -1) { - index = tokens.length; - } - if (index == 0) { - break; + result = validateSyntax(s[0].prelude, token.tokens, root, options, { + ...context, + tokens: null, + level: context.level + 1 + }); + if (result.valid == ValidationLevel.Drop) { + return result; } - // @ts-ignore - result2 = validateSyntax([c], tokens.slice(0, index), root, options, context); - matched = result2.valid == ValidationLevel.Valid && result2.matches.length > 0; - if (matched) { - const l = tokens.length; - validSyntax = true; - // @ts-ignore - // matches.push(...result2.matches); - // splice(tokens, result2.matches); - if (tokens.length == 1 && tokens[0].typ == exports.EnumToken.CommaTokenType) { - return { - valid: ValidationLevel.Drop, - matches, - node: tokens[0], - syntax, - error: 'unexpected token', - tokens - }; - } - tokens = tokens.slice(index); - result = result2; + } + const hasBody = 'chi' in s[0]; + if ('chi' in token) { + if (!hasBody) { // @ts-ignore - matches.push(...result2.matches); - if (result.tokens.length > 0) { - if (index == -1) { - tokens = result.tokens; - } - else { - tokens = tokens.slice(index - result.tokens.length); - } - } - else if (index > 0) { - tokens = tokens.slice(index); - } - index = -1; - if (l == tokens.length) { - break; - } - } - else { - break; + return { + valid: ValidationLevel.Drop, + matches: [], + node: token, + syntax, + error: 'unexpected at-rule body', + tokens + }; } - } while (tokens.length > 0); - // if (level == 0) { - // } - if (!matched) { + } + else if (hasBody) { + // @ts-ignore return { valid: ValidationLevel.Drop, - // @ts-ignore - matches: [...new Set(matches)], + matches: [], node: token, syntax, - error: 'unexpected token', + error: 'expecting at-rule body', tokens }; } - syntaxes.shift(); - continue; - } - if (syntax.isRepeatable) { + break; + case ValidationTokenEnum.DeclarationType: // @ts-ignore - let { isRepeatable, ...c } = syntax; - let result2 = null; - validSyntax = false; - let l = tokens.length; - let tok = null; - do { - // @ts-ignore - result2 = validateSyntax([c], tokens, root, options, context); - if (result2.matches.length == 0 && result2.error.length > 0) { - syntaxes.shift(); - break main; + result = validateSyntax(getParsedSyntax("declarations" /* ValidationSyntaxGroupEnum.Declarations */, syntax.val), [token], root, options, context); + break; + case ValidationTokenEnum.Keyword: + valid = (token.typ == exports.EnumToken.IdenTokenType && token.val.localeCompare(syntax.val, 'en', { sensitivity: 'base' }) == 0) || + (token.typ == exports.EnumToken.ColorTokenType && token.kin == 'lit' && syntax.val.localeCompare(token.val, 'en', { sensitivity: 'base' }) == 0); + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], + node: valid ? null : token, + syntax, + error: valid ? '' : 'unexpected token', + tokens + }; + break; + case ValidationTokenEnum.SemiColon: + valid = root == null || [exports.EnumToken.RuleNodeType, exports.EnumToken.AtRuleNodeType, exports.EnumToken.StyleSheetNodeType].includes(root.typ); + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], + node: valid ? null : token, + syntax, + error: valid ? '' : 'unexpected token', + tokens + }; + break; + case ValidationTokenEnum.Separator: + valid = token.typ == exports.EnumToken.LiteralTokenType && token.val != '/'; + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], + node: valid ? null : token, + syntax, + error: valid ? '' : 'unexpected token', + tokens + }; + break; + case ValidationTokenEnum.PropertyType: + // + if ('image' == syntax.val) { + valid = token.typ == exports.EnumToken.UrlFunctionTokenType || token.typ == exports.EnumToken.ImageFunctionTokenType; + if (!valid) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: token, + syntax, + error: 'unexpected ', + tokens + }; } - if (result2.valid == ValidationLevel.Valid) { - tokens = result2.tokens; - // @ts-ignore - matches.push(...result2.matches); - result = result2; - if (l == tokens.length) { - if (tok == tokens[0]) { - break; - } - if (result2.matches.length == 0 && tokens.length > 0) { - tokens = result2.tokens; - tok = tokens[0]; - continue; - } - break; - } - if (matches.length == 0) { - tokens = result2.tokens; + result = validateImage(token); + } + else if (['media-feature', 'mf-plain'].includes(syntax.val)) { + valid = token.typ == exports.EnumToken.DeclarationNodeType; + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], + node: valid ? null : token, + syntax, + error: valid ? '' : 'unexpected token', + tokens + }; + } + else if (syntax.val == 'pseudo-page') { + valid = token.typ == exports.EnumToken.PseudoClassTokenType && [':left', ':right', ':first', ':blank'].includes(token.val); + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], + node: valid ? null : token, + syntax, + error: valid ? '' : 'unexpected token', + tokens + }; + } + else if (syntax.val == 'page-body') { + if (token.typ == exports.EnumToken.DeclarationNodeType) { + valid = true; + // @ts-ignore + result = { + valid: ValidationLevel.Valid, + matches: [token], + node: null, + syntax, + error: '', + tokens + }; + while (tokens.length > 0 && [exports.EnumToken.DeclarationNodeType].includes(tokens[0].typ)) { + // @ts-ignore + result.matches.push(tokens.shift()); } - l = tokens.length; - continue; } - break; - } while (result2.valid == ValidationLevel.Valid && tokens.length > 0); - // if (lastResult != null) { - // - // splice(tokens, lastResult.matches); - // // tokens = lastResult.tokens; - // } - syntaxes.shift(); - continue; - } - // at least one match - if (syntax.isRepeatableGroup) { - validSyntax = false; - let count = 0; - let l = tokens.length; - let result2 = null; - do { - // @ts-ignore - const { isRepeatableGroup, ...c } = syntax; - // @ts-ignore - result2 = validateSyntax([c], tokens, root, options, context); - if (result2.valid == ValidationLevel.Drop || result2.matches.length == 0) { - if (count > 0) { - syntaxes.shift(); - // if (result2.matches.length == 0) { - tokens = result2.tokens; - // break main; - if (syntaxes.length == 0) { - return result2; - } - break main; - } - return result2; + else if (token.typ == exports.EnumToken.AtRuleNodeType) { + result = validateSyntax(getParsedSyntax("syntaxes" /* ValidationSyntaxGroupEnum.Syntaxes */, 'page-margin-box-type'), [token], root, options, context); } - if (result2.valid == ValidationLevel.Valid && result2.matches.length > 0) { - count++; - // lastResult = result; - validSyntax = true; - tokens = result2.tokens; - // splice(tokens, result2.matches); - // tokens = result2.tokens; - // @ts-ignore - matches.push(...result2.matches); - result = result2; - if (l == tokens.length) { - break; + } + else if (syntax.val == 'group-rule-body') { + valid = [exports.EnumToken.AtRuleNodeType, exports.EnumToken.RuleNodeType].includes(token.typ); + if (!valid && token.typ == exports.EnumToken.DeclarationNodeType && root?.typ == exports.EnumToken.AtRuleNodeType && root.nam == 'media') { + // allowed only if nested rule + let parent = root; + while (parent != null) { + if (parent.typ == exports.EnumToken.RuleNodeType) { + valid = true; + break; + } + // @ts-ignore + parent = parent.parent; } - l = tokens.length; } - else { - break; + // @ts-ignore + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: [], + node: valid ? null : token, + syntax, + error: valid ? '' : 'token is not allowed as a child', + tokens + }; + if (!valid) { + return result; } - } while (tokens.length > 0 && result.valid == ValidationLevel.Valid); - // if (lastResult != null) { + } // - // splice(tokens, lastResult.matches); - // // tokens = lastResult.tokens; - // } - // at least one match is expected - if (!validSyntax /* || result.matches.length == 0 */) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, + else if ('type-selector' == syntax.val) { + valid = (token.typ == exports.EnumToken.UniversalSelectorTokenType) || + token.typ == exports.EnumToken.IdenTokenType || (token.typ == exports.EnumToken.NameSpaceAttributeTokenType && + (token.l == null || token.l.typ == exports.EnumToken.IdenTokenType || + (token.l.typ == exports.EnumToken.LiteralTokenType && token.l.val == '*')) && + token.r.typ == exports.EnumToken.IdenTokenType); + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], node: token, - tokens, syntax, - error: 'unexpected token', - matches: [] + error: valid ? '' : 'unexpected token', + tokens }; } - syntaxes.shift(); - continue; - } - if (syntax.atLeastOnce) { - const { atLeastOnce, ...c } = syntax; - result = validateSyntax([c], tokens, root, options, context); - if (result.valid == ValidationLevel.Drop) { - return result; + else if ('wq-name' == syntax.val) { + valid = token.typ == exports.EnumToken.IdenTokenType || (token.typ == exports.EnumToken.NameSpaceAttributeTokenType && + (token.l == null || token.l.typ == exports.EnumToken.IdenTokenType || (token.l.typ == exports.EnumToken.LiteralTokenType && token.l.val == '*')) && + token.r.typ == exports.EnumToken.IdenTokenType); + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], + node: token, + syntax, + error: valid ? '' : 'unexpected token', + tokens + }; } - splice(tokens, result.matches); - // tokens = result.tokens; - // @ts-ignore - matches.push(...result.matches); - let l = tokens.length; - let r = validateSyntax([c], tokens, root, options, context); - while (r.valid == ValidationLevel.Valid) { - splice(tokens, r.matches); - // tokens = r.tokens; - r = validateSyntax([c], tokens, root, options, context); - if (l == tokens.length) { - break; + else if (exports.EnumToken.UniversalSelectorTokenType == token.typ && 'subclass-selector' == syntax.val) { + valid = true; + result = { + valid: ValidationLevel.Valid, + matches: [token], + node: null, + syntax, + error: '', + tokens + }; + } + else if ('attribute-selector' == syntax.val) { + valid = token.typ == exports.EnumToken.AttrTokenType && token.chi.length > 0; + if (valid) { + const children = token.chi.filter(t => t.typ != exports.EnumToken.WhitespaceTokenType && t.typ != exports.EnumToken.CommaTokenType); + valid = children.length == 1 && [ + exports.EnumToken.IdenTokenType, + exports.EnumToken.NameSpaceAttributeTokenType, + exports.EnumToken.MatchExpressionTokenType + ].includes(children[0].typ); + if (valid && children[0].typ == exports.EnumToken.MatchExpressionTokenType) { + const t = children[0]; + valid = [ + exports.EnumToken.IdenTokenType, + exports.EnumToken.NameSpaceAttributeTokenType + ].includes(t.l.typ) && + (t.op == null || ([ + exports.EnumToken.DelimTokenType, exports.EnumToken.DashMatchTokenType, + exports.EnumToken.StartMatchTokenType, exports.EnumToken.ContainMatchTokenType, + exports.EnumToken.EndMatchTokenType, exports.EnumToken.IncludeMatchTokenType + ].includes(t.op.typ) && + t.r != null && + [ + exports.EnumToken.StringTokenType, + exports.EnumToken.IdenTokenType + ].includes(t.r.typ))); + if (valid && t.attr != null) { + const s = getParsedSyntax("syntaxes" /* ValidationSyntaxGroupEnum.Syntaxes */, 'attr-modifier')[0]; + valid = s.chi.some((l) => l.some((r) => r.val == t.attr)); + } + } } - if (r.valid == ValidationLevel.Valid && r.matches.length > 0) { + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], + node: valid ? null : token, + syntax, + error: valid ? '' : 'unexpected token', + tokens + }; + if (!valid) { + return result; + } + } + else if ('combinator' == syntax.val) { + valid = [ + exports.EnumToken.DescendantCombinatorTokenType, + exports.EnumToken.SubsequentSiblingCombinatorTokenType, + exports.EnumToken.NextSiblingCombinatorTokenType, + exports.EnumToken.ChildCombinatorTokenType, + exports.EnumToken.ColumnCombinatorTokenType + ].includes(token.typ); + if (valid) { // @ts-ignore - matches.push(...result.matches); + const position = context.tokens.indexOf(token); + if (root == null) { + valid = position > 0 && context.tokens[position - 1]?.typ != exports.EnumToken.CommaTokenType; + } + } + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], + node: valid ? null : token, + syntax, + error: valid ? '' : 'unexpected token', + tokens + }; + if (!valid) { + return result; } - l = tokens.length; } - syntaxes.shift(); - continue; - } - // @ts-ignore - if (syntax.occurence != null) { - // @ts-ignore - const { occurence, ...c } = syntax; - // && syntaxes.occurence.max != null - // consume all tokens - let match = 1; - // @ts-ignore - result = validateSyntax([c], tokens, root, options, context); - if (result.valid == ValidationLevel.Drop) { - return result; - } - if (result.matches.length == 0) { - syntaxes.shift(); - continue; - } - // splice(tokens, result.matches); - // tokens = result.tokens; - // @ts-ignore - matches.push(...result.matches); - matched = true; - tokens = result.tokens; - while (occurence.max == null || match < occurence.max) { - // trim whitespace - if (tokens[0]?.typ == exports.EnumToken.WhitespaceTokenType) { - tokens.shift(); - } - // @ts-ignore - let r = validateSyntax([c], tokens, root, options, context); - if (r.valid != ValidationLevel.Valid || r.matches.length == 0) { - break; - } - result = r; - // splice(tokens, r.matches); - // tokens = r.tokens; - // @ts-ignore - matches.push(...result.matches); - match++; - tokens = r.tokens; - result = r; - if (tokens.length == 0 || (occurence.max != null && match >= occurence.max)) { - break; - } - // @ts-ignore - // r = validateSyntax([c], tokens, root, options, context); - } - syntaxes.shift(); - continue; - } - // @ts-ignore - if (syntax.typ == ValidationTokenEnum.Whitespace) { - if (token.typ == exports.EnumToken.WhitespaceTokenType) { - tokens.shift(); - } - syntaxes.shift(); - continue; - } - // @ts-ignore - if (token.val != null && specialValues.includes(token.val)) { - matched = true; - result = { - valid: ValidationLevel.Valid, - matches: [token], - node: null, - syntax, - error: '', - tokens - }; - // @ts-ignore - matches.push(...result.matches); - } - else { - result = doValidateSyntax(syntax, token, tokens, root, options, context); - matched = result.valid == ValidationLevel.Valid && result.matches.length > 0; - if (matched) { - // splice(tokens, result.matches); - tokens = result.tokens; - // @ts-ignore - matches.push(...result.matches); - } - } - if (result.valid == ValidationLevel.Drop) { - // @ts-ignore - return { ...result, matches, tokens, node: result.valid == ValidationLevel.Valid ? null : token }; - } - consumeSyntax(syntaxes); - if (tokens.length == 0) { - return result; - } - } - if (result?.valid == ValidationLevel.Valid) { - // splice(tokens, result.matches); - tokens = result.tokens; - // @ts-ignore - matches.push(...result.matches); - } - if ( /* result == null && */tokens.length == 0 && syntaxes.length > 0) { - validSyntax = isOptionalSyntax(syntaxes); - } - if (result == null) { - result = { - valid: validSyntax ? ValidationLevel.Valid : ValidationLevel.Drop, - matches, - node: validSyntax ? null : tokens[0] ?? null, - // @ts-ignore - syntax, - error: validSyntax ? '' : 'unexpected token', - tokens - }; - } - if (token != null) { - if (!context.cache.has(token)) { - context.cache.set(token, new Map); - } - context.cache.get(token).set(syntax.text, result); - } - if (result != null) { - // @ts-ignore - return { ...result, matches: [...(new Set(matches))] }; - } - return result; - } - function isOptionalSyntax(syntaxes) { - return syntaxes.every(t => t.typ == ValidationTokenEnum.Whitespace || t.isOptional || t.isRepeatable || (t.typ == ValidationTokenEnum.PropertyType && isOptionalSyntax(getParsedSyntax("syntaxes" /* ValidationSyntaxGroupEnum.Syntaxes */, t.val) ?? getParsedSyntax("declarations" /* ValidationSyntaxGroupEnum.Declarations */, t.val)))); - } - function doValidateSyntax(syntax, token, tokens, root, options, context) { - let valid = false; - let result; - let children; - let queue; - let matches; - let child; - let astNodes = new Set; - if (token.typ == exports.EnumToken.NestingSelectorTokenType && syntax.typ == 2) { - valid = root != null && 'relative-selector' == syntax.val; - return { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - } - switch (syntax.typ) { - case ValidationTokenEnum.Comma: - valid = token.typ === exports.EnumToken.CommaTokenType; - // @ts-ignore - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - break; - case ValidationTokenEnum.AtRule: - if (token.typ != exports.EnumToken.AtRuleNodeType) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: token, + else if ('ident-token' == syntax.val) { + valid = token.typ == exports.EnumToken.IdenTokenType; + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], + node: valid ? null : token, syntax, - error: 'expecting at-rule', + error: valid ? '' : 'unexpected token', tokens }; } - if (token.nam != syntax.val) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: token, + else if ('hex-color' == syntax.val) { + valid = token.typ == exports.EnumToken.ColorTokenType && token.kin == 'hex'; + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], + node: valid ? null : token, syntax, - error: `expecting '@${syntax.val}' but found '@${token.nam}'`, + error: valid ? '' : 'unexpected token', tokens }; } - if (root == null) { - return { - valid: ValidationLevel.Valid, - matches: [token], - node: null, + else if ('resolution' == syntax.val) { + valid = token.typ == exports.EnumToken.ResolutionTokenType; + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], + node: valid ? null : token, syntax, - error: '', + error: valid ? '' : 'unexpected token', tokens }; } - if (root.typ != exports.EnumToken.AtRuleNodeType) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: token, + else if ('angle' == syntax.val) { + valid = token.typ == exports.EnumToken.AngleTokenType || (token.typ == exports.EnumToken.NumberTokenType && token.val == '0'); + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], + node: valid ? null : token, syntax, - error: 'not allowed here', + error: valid ? '' : 'unexpected token', tokens }; } - if (!('chi' in token)) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: token, + else if ('time' == syntax.val) { + valid = token.typ == exports.EnumToken.TimingFunctionTokenType; + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], + node: valid ? null : token, syntax, - error: '@at-rule must have children', + error: valid ? '' : 'unexpected token', tokens }; } - // @ts-ignore - result = { - valid: ValidationLevel.Valid, - matches: [token], - node: null, - syntax, - error: '', - tokens - }; - break; - case ValidationTokenEnum.AtRuleDefinition: - if (token.typ != exports.EnumToken.AtRuleNodeType) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: token, + else if ('ident' == syntax.val) { + valid = token.typ == exports.EnumToken.IdenTokenType; + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], + node: valid ? null : token, syntax, - error: 'expecting at-rule', + error: valid ? '' : 'unexpected token', tokens }; } - if ('chi' in syntax && !('chi' in token)) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: token, - syntax, - error: '@at-rule must have children', - tokens - }; - } - if ('chi' in token && !('chi' in token)) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: token, - syntax, - error: 'children not allowed here', - tokens - }; - } - const s = getParsedSyntax("atRules" /* ValidationSyntaxGroupEnum.AtRules */, '@' + token.nam); - if ('prelude' in syntax) { - if (!('tokens' in token)) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: token, - syntax, - error: 'expected at-rule prelude', - tokens - }; - } - result = validateSyntax(s[0].prelude, token.tokens, root, options, { - ...context, - tokens: null, - level: context.level + 1 - }); - if (result.valid == ValidationLevel.Drop) { - return result; - } - } - const hasBody = 'chi' in s[0]; - if ('chi' in token) { - if (!hasBody) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: token, - syntax, - error: 'unexpected at-rule body', - tokens - }; - } - } - else if (hasBody) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: token, - syntax, - error: 'expecting at-rule body', - tokens - }; - } - break; - case ValidationTokenEnum.DeclarationType: - // @ts-ignore - result = validateSyntax(getParsedSyntax("declarations" /* ValidationSyntaxGroupEnum.Declarations */, syntax.val), [token], root, options, context); - break; - case ValidationTokenEnum.Keyword: - valid = (token.typ == exports.EnumToken.IdenTokenType && token.val.localeCompare(syntax.val, 'en', { sensitivity: 'base' }) == 0) || - (token.typ == exports.EnumToken.ColorTokenType && token.kin == 'lit' && syntax.val.localeCompare(token.val, 'en', { sensitivity: 'base' }) == 0); - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - break; - case ValidationTokenEnum.SemiColon: - valid = root == null || [exports.EnumToken.RuleNodeType, exports.EnumToken.AtRuleNodeType, exports.EnumToken.StyleSheetNodeType].includes(root.typ); - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - break; - case ValidationTokenEnum.Separator: - valid = token.typ == exports.EnumToken.LiteralTokenType && token.val != '/'; - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - break; - case ValidationTokenEnum.PropertyType: - // - if (['media-feature', 'mf-plain'].includes(syntax.val)) { - valid = token.typ == exports.EnumToken.DeclarationNodeType; + else if (['id-selector', 'hash-token'].includes(syntax.val)) { + valid = token.typ == exports.EnumToken.HashTokenType; result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], @@ -12930,8 +13413,14 @@ tokens }; } - else if (syntax.val == 'pseudo-page') { - valid = token.typ == exports.EnumToken.PseudoClassTokenType && [':left', ':right', ':first', ':blank'].includes(token.val); + else if (['integer', 'number'].includes(syntax.val)) { + // valid = token.typ == EnumToken.NumberTokenType; + valid = token.typ == exports.EnumToken.NumberTokenType && ('integer' != syntax.val || Number.isInteger(+token.val)); + if (valid && 'range' in syntax) { + const value = Number(token.val); + const range = syntax.range; + valid = value >= range[0] && (range[1] == null || value <= range[1]); + } result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], @@ -12941,113 +13430,31 @@ tokens }; } - else if (syntax.val == 'page-body') { - if (token.typ == exports.EnumToken.DeclarationNodeType) { - valid = true; - // @ts-ignore - result = { - valid: ValidationLevel.Valid, - matches: [token], - node: null, - syntax, - error: '', - tokens - }; - while (tokens.length > 0 && [exports.EnumToken.DeclarationNodeType].includes(tokens[0].typ)) { - // @ts-ignore - result.matches.push(tokens.shift()); - } - } - else if (token.typ == exports.EnumToken.AtRuleNodeType) { - result = validateSyntax(getParsedSyntax("syntaxes" /* ValidationSyntaxGroupEnum.Syntaxes */, 'page-margin-box-type'), [token], root, options, context); - } - } - else if (syntax.val == 'group-rule-body') { - valid = [exports.EnumToken.AtRuleNodeType, exports.EnumToken.RuleNodeType].includes(token.typ); + else if ('length' == syntax.val) { + valid = isLength(token) || (token.typ == exports.EnumToken.NumberTokenType && token.val == '0'); // @ts-ignore - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'token is not allowed as a child', - tokens - }; - if (!valid) { - return result; - } - } - // - else if ('type-selector' == syntax.val) { - valid = (token.typ == exports.EnumToken.UniversalSelectorTokenType) || - token.typ == exports.EnumToken.IdenTokenType || (token.typ == exports.EnumToken.NameSpaceAttributeTokenType && - (token.l == null || token.l.typ == exports.EnumToken.IdenTokenType || - (token.l.typ == exports.EnumToken.LiteralTokenType && token.l.val == '*')) && - token.r.typ == exports.EnumToken.IdenTokenType); result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], - node: token, + node: valid ? null : token, syntax, error: valid ? '' : 'unexpected token', tokens }; } - else if ('wq-name' == syntax.val) { - valid = token.typ == exports.EnumToken.IdenTokenType || (token.typ == exports.EnumToken.NameSpaceAttributeTokenType && - (token.l == null || token.l.typ == exports.EnumToken.IdenTokenType || (token.l.typ == exports.EnumToken.LiteralTokenType && token.l.val == '*')) && - token.r.typ == exports.EnumToken.IdenTokenType); + else if ('percentage' == syntax.val) { + valid = token.typ == exports.EnumToken.PercentageTokenType || (token.typ == exports.EnumToken.NumberTokenType && token.val == '0'); result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], - node: token, + node: valid ? null : token, syntax, error: valid ? '' : 'unexpected token', tokens }; } - else if (exports.EnumToken.UniversalSelectorTokenType == token.typ && 'subclass-selector' == syntax.val) { - valid = true; - result = { - valid: ValidationLevel.Valid, - matches: [token], - node: null, - syntax, - error: '', - tokens - }; - } - else if ('attribute-selector' == syntax.val) { - valid = token.typ == exports.EnumToken.AttrTokenType && token.chi.length > 0; - if (valid) { - const children = token.chi.filter(t => t.typ != exports.EnumToken.WhitespaceTokenType && t.typ != exports.EnumToken.CommaTokenType); - valid = children.length == 1 && [ - exports.EnumToken.IdenTokenType, - exports.EnumToken.NameSpaceAttributeTokenType, - exports.EnumToken.MatchExpressionTokenType - ].includes(children[0].typ); - if (valid && children[0].typ == exports.EnumToken.MatchExpressionTokenType) { - const t = children[0]; - valid = [ - exports.EnumToken.IdenTokenType, - exports.EnumToken.NameSpaceAttributeTokenType - ].includes(t.l.typ) && - (t.op == null || ([ - exports.EnumToken.DelimTokenType, exports.EnumToken.DashMatchTokenType, - exports.EnumToken.StartMatchTokenType, exports.EnumToken.ContainMatchTokenType, - exports.EnumToken.EndMatchTokenType, exports.EnumToken.IncludeMatchTokenType - ].includes(t.op.typ) && - t.r != null && - [ - exports.EnumToken.StringTokenType, - exports.EnumToken.IdenTokenType - ].includes(t.r.typ))); - if (valid && t.attr != null) { - const s = getParsedSyntax("syntaxes" /* ValidationSyntaxGroupEnum.Syntaxes */, 'attr-modifier')[0]; - valid = s.chi.some((l) => l.some((r) => r.val == t.attr)); - } - } - } + else if ('dashed-ident' == syntax.val) { + valid = token.typ == exports.EnumToken.DashedIdenTokenType; result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], @@ -13056,25 +13463,9 @@ error: valid ? '' : 'unexpected token', tokens }; - if (!valid) { - return result; - } } - else if ('combinator' == syntax.val) { - valid = [ - exports.EnumToken.DescendantCombinatorTokenType, - exports.EnumToken.SubsequentSiblingCombinatorTokenType, - exports.EnumToken.NextSiblingCombinatorTokenType, - exports.EnumToken.ChildCombinatorTokenType, - exports.EnumToken.ColumnCombinatorTokenType - ].includes(token.typ); - if (valid) { - // @ts-ignore - const position = context.tokens.indexOf(token); - if (root == null) { - valid = position > 0 && context.tokens[position - 1]?.typ != exports.EnumToken.CommaTokenType; - } - } + else if ('custom-ident' == syntax.val) { + valid = token.typ == exports.EnumToken.DashedIdenTokenType || token.typ == exports.EnumToken.IdenTokenType; result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], @@ -13083,12 +13474,9 @@ error: valid ? '' : 'unexpected token', tokens }; - if (!valid) { - return result; - } } - else if ('ident-token' == syntax.val) { - valid = token.typ == exports.EnumToken.IdenTokenType; + else if ('custom-property-name' == syntax.val) { + valid = token.typ == exports.EnumToken.DashedIdenTokenType; result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], @@ -13098,8 +13486,8 @@ tokens }; } - else if ('hex-color' == syntax.val) { - valid = token.typ == exports.EnumToken.ColorTokenType && token.kin == 'hex'; + else if ('string' == syntax.val) { + valid = token.typ == exports.EnumToken.StringTokenType; result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], @@ -13109,8 +13497,8 @@ tokens }; } - else if ('resolution' == syntax.val) { - valid = token.typ == exports.EnumToken.ResolutionTokenType; + else if ('declaration-value' == syntax.val) { + valid = token.typ != exports.EnumToken.LiteralTokenType; result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], @@ -13120,147 +13508,8 @@ tokens }; } - else if ('angle' == syntax.val) { - valid = token.typ == exports.EnumToken.AngleTokenType || (token.typ == exports.EnumToken.NumberTokenType && token.val == '0'); - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - } - else if ('time' == syntax.val) { - valid = token.typ == exports.EnumToken.TimingFunctionTokenType; - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - } - else if ('ident' == syntax.val) { - valid = token.typ == exports.EnumToken.IdenTokenType; - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - } - else if (['id-selector', 'hash-token'].includes(syntax.val)) { - valid = token.typ == exports.EnumToken.HashTokenType; - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - } - else if (['integer', 'number'].includes(syntax.val)) { - // valid = token.typ == EnumToken.NumberTokenType; - valid = token.typ == exports.EnumToken.NumberTokenType && ('integer' != syntax.val || Number.isInteger(+token.val)); - if (valid && 'range' in syntax) { - const value = Number(token.val); - const range = syntax.range; - valid = value >= range[0] && (range[1] == null || value <= range[1]); - } - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - } - else if ('length' == syntax.val) { - valid = isLength(token) || (token.typ == exports.EnumToken.NumberTokenType && token.val == '0'); - // @ts-ignore - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - } - else if ('percentage' == syntax.val) { - valid = token.typ == exports.EnumToken.PercentageTokenType || (token.typ == exports.EnumToken.NumberTokenType && token.val == '0'); - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - } - else if ('dashed-ident' == syntax.val) { - valid = token.typ == exports.EnumToken.DashedIdenTokenType; - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - } - else if ('custom-ident' == syntax.val) { - valid = token.typ == exports.EnumToken.DashedIdenTokenType || token.typ == exports.EnumToken.IdenTokenType; - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - } - else if ('custom-property-name' == syntax.val) { - valid = token.typ == exports.EnumToken.DashedIdenTokenType; - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - } - else if ('string' == syntax.val) { - valid = token.typ == exports.EnumToken.StringTokenType; - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - } - else if ('declaration-value' == syntax.val) { - valid = token.typ != exports.EnumToken.LiteralTokenType; - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - } - else if ('url' == syntax.val) { - valid = token.typ == exports.EnumToken.UrlFunctionTokenType || token.typ == exports.EnumToken.StringTokenType; + else if ('url' == syntax.val) { + valid = token.typ == exports.EnumToken.UrlFunctionTokenType || token.typ == exports.EnumToken.StringTokenType; result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], @@ -13631,12 +13880,570 @@ error: valid ? '' : 'invalid token', tokens }; - break; - default: - throw new Error('not implemented: ' + JSON.stringify({ syntax, token, tokens }, null, 1)); + break; + case ValidationTokenEnum.DeclarationDefinitionToken: + if (token.typ != exports.EnumToken.DeclarationNodeType || token.nam != syntax.nam) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: token, + syntax, + error: '', + tokens + }; + } + return validateSyntax([syntax.val], token.val, root, options, context); + default: + throw new Error('not implemented: ' + JSON.stringify({ syntax, token, tokens }, null, 1)); + } + // @ts-ignore + return result; + } + + function validateURL(token) { + if (token.typ == exports.EnumToken.UrlTokenTokenType) { + // @ts-ignore + return { + valid: ValidationLevel.Valid, + matches: [], + node: token, + // @ts-ignore + syntax: 'url()', + error: '', + tokens: [] + }; + } + if (token.typ != exports.EnumToken.UrlFunctionTokenType) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: token, + // @ts-ignore + syntax: 'url()', + error: 'expected url()', + tokens: [] + }; + } + const children = token.chi.slice(); + consumeWhitespace(children); + if (children.length == 0 || ![exports.EnumToken.UrlTokenTokenType, exports.EnumToken.StringTokenType, exports.EnumToken.HashTokenType].includes(children[0].typ)) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: children[0] ?? token, + // @ts-ignore + syntax: 'url()', + error: 'expected url-token', + tokens: children + }; + } + children.shift(); + consumeWhitespace(children); + if (children.length > 0) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: children[0] ?? token, + // @ts-ignore + syntax: 'url()', + error: 'unexpected token', + tokens: children + }; + } + // @ts-ignore + return { + valid: ValidationLevel.Valid, + matches: [], + node: token, + // @ts-ignore + syntax: 'url()', + error: '', + tokens: [] + }; + } + + function validateImage(token) { + if (token.typ == exports.EnumToken.UrlFunctionTokenType) { + return validateURL(token); + } + if (token.typ == exports.EnumToken.ImageFunctionTokenType) { + return validateSyntax(getParsedSyntax("syntaxes" /* ValidationSyntaxGroupEnum.Syntaxes */, token.val + '()'), token.chi); + } + return { + valid: ValidationLevel.Drop, + matches: [], + node: token, + syntax: 'image()', + error: 'expected or ', + tokens: [] + }; + } + + const validateSelectorList = validateComplexSelectorList; + + function validateSelector(selector, options, root) { + if (root == null) { + return validateSelectorList(selector, root, options); + } + // @ts-ignore + if (root.typ == exports.EnumToken.AtRuleNodeType && root.nam.match(/^(-[a-z]+-)?keyframes$/)) { + return validateKeyframeBlockList(selector, root); + } + let isNested = root.typ == exports.EnumToken.RuleNodeType ? 1 : 0; + let currentRoot = root.parent; + while (currentRoot != null && isNested == 0) { + if (currentRoot.typ == exports.EnumToken.RuleNodeType) { + isNested++; + if (isNested > 0) { + // @ts-ignore + return validateRelativeSelectorList(selector, root, { ...(options ?? {}), nestedSelector: true }); + } + } + currentRoot = currentRoot.parent; + } + const nestedSelector = isNested > 0; + // @ts-ignore + return nestedSelector ? validateRelativeSelectorList(selector, root, { ...(options ?? {}), nestedSelector }) : validateSelectorList(selector, root, { ...(options ?? {}), nestedSelector }); + } + + function validateAtRuleMedia(atRule, options, root) { + // media-query-list + if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Valid, + matches: [], + node: null, + syntax: null, + error: '', + tokens: [] + }; + } + let result = null; + const slice = atRule.tokens.slice(); + consumeWhitespace(slice); + if (slice.length == 0) { + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@media', + error: '', + tokens: [] + }; + } + result = validateAtRuleMediaQueryList(atRule.tokens, atRule); + if (result.valid == ValidationLevel.Drop) { + return result; + } + if (!('chi' in atRule)) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@media', + error: 'expected at-rule body', + tokens: [] + }; + } + // @ts-ignore + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@media', + error: '', + tokens: [] + }; + } + function validateAtRuleMediaQueryList(tokenList, atRule) { + const split = splitTokenList(tokenList); + const matched = []; + let result = null; + let previousToken; + let mediaFeatureType; + for (let i = 0; i < split.length; i++) { + const tokens = split[i].slice(); + const match = []; + result = null; + mediaFeatureType = null; + previousToken = null; + if (tokens.length == 0) { + // @ts-ignore + result = { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0] ?? atRule, + syntax: '@media', + error: 'unexpected token', + tokens: [] + }; + continue; + } + while (tokens.length > 0) { + previousToken = tokens[0]; + // media-condition | media-type | custom-media + if (!(validateMediaCondition(tokens[0], atRule) || validateMediaFeature(tokens[0]) || validateCustomMediaCondition(tokens[0], atRule))) { + if (tokens[0].typ == exports.EnumToken.ParensTokenType) { + result = validateAtRuleMediaQueryList(tokens[0].chi, atRule); + } + else { + result = { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0] ?? atRule, + syntax: '@media', + error: 'expecting media feature or media condition', + tokens: [] + }; + } + if (result.valid == ValidationLevel.Drop) { + break; + } + result = null; + } + match.push(tokens.shift()); + if (tokens.length == 0) { + break; + } + if (!consumeWhitespace(tokens)) { + if (previousToken?.typ != exports.EnumToken.ParensTokenType) { + // @ts-ignore + result = { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0] ?? atRule, + syntax: '@media', + error: 'expected media query list', + tokens: [] + }; + break; + } + } + else if (![exports.EnumToken.MediaFeatureOrTokenType, exports.EnumToken.MediaFeatureAndTokenType].includes(tokens[0].typ)) { + // @ts-ignore + result = { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0] ?? atRule, + syntax: '@media', + error: 'expected and/or', + tokens: [] + }; + break; + } + if (mediaFeatureType == null) { + mediaFeatureType = tokens[0]; + } + if (mediaFeatureType.typ != tokens[0].typ) { + // @ts-ignore + result = { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0] ?? atRule, + syntax: '@media', + error: 'mixing and/or not allowed at the same level', + tokens: [] + }; + break; + } + match.push({ typ: exports.EnumToken.WhitespaceTokenType }, tokens.shift()); + consumeWhitespace(tokens); + if (tokens.length == 0) { + // @ts-ignore + result = { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0] ?? atRule, + syntax: '@media', + error: 'expected media-condition', + tokens: [] + }; + break; + } + match.push({ typ: exports.EnumToken.WhitespaceTokenType }); + } + if (result == null && match.length > 0) { + matched.push(match); + } + } + if (result != null) { + return result; + } + if (matched.length == 0) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@media', + error: 'expected media query list', + tokens: [] + }; + } + tokenList.length = 0; + let hasAll = false; + for (let i = 0; i < matched.length; i++) { + if (tokenList.length > 0) { + tokenList.push({ typ: exports.EnumToken.CommaTokenType }); + } + if (matched[i].length == 1 && matched.length > 1 && matched[i][0].typ == exports.EnumToken.MediaFeatureTokenType && matched[i][0].val == 'all') { + hasAll = true; + continue; + } + tokenList.push(...matched[i]); + } + if (hasAll && tokenList.length == 0) { + tokenList.push({ typ: exports.EnumToken.MediaFeatureTokenType, val: 'all' }); + } + // @ts-ignore + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@media', + error: '', + tokens: [] + }; + } + function validateCustomMediaCondition(token, atRule) { + if (token.typ == exports.EnumToken.MediaFeatureNotTokenType) { + return validateMediaCondition(token.val, atRule); + } + if (token.typ != exports.EnumToken.ParensTokenType) { + return false; + } + const chi = token.chi.filter((t) => t.typ != exports.EnumToken.CommentTokenType && t.typ != exports.EnumToken.WhitespaceTokenType); + if (chi.length != 1) { + return false; + } + return chi[0].typ == exports.EnumToken.DashedIdenTokenType; + } + function validateMediaCondition(token, atRule) { + if (token.typ == exports.EnumToken.MediaFeatureNotTokenType) { + return validateMediaCondition(token.val, atRule); + } + if (token.typ != exports.EnumToken.ParensTokenType && !(['when', 'else'].includes(atRule.nam) && token.typ == exports.EnumToken.FunctionTokenType && ['media', 'supports'].includes(token.val))) { + return false; + } + const chi = token.chi.filter((t) => t.typ != exports.EnumToken.CommentTokenType && t.typ != exports.EnumToken.WhitespaceTokenType); + if (chi.length != 1) { + return false; + } + if (chi[0].typ == exports.EnumToken.IdenTokenType) { + return true; + } + if (chi[0].typ == exports.EnumToken.MediaFeatureNotTokenType) { + return validateMediaCondition(chi[0].val, atRule); + } + if (chi[0].typ == exports.EnumToken.MediaQueryConditionTokenType) { + return chi[0].l.typ == exports.EnumToken.IdenTokenType; + } + return false; + } + function validateMediaFeature(token) { + let val = token; + if (token.typ == exports.EnumToken.MediaFeatureOnlyTokenType || token.typ == exports.EnumToken.MediaFeatureNotTokenType) { + val = token.val; + } + return val.typ == exports.EnumToken.MediaFeatureTokenType; + } + + function validateAtRuleCounterStyle(atRule, options, root) { + // media-query-list + if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@counter-style', + error: 'expected counter style name', + tokens: [] + }; + } + const tokens = atRule.tokens.filter((t) => ![exports.EnumToken.WhitespaceTokenType, exports.EnumToken.CommentTokenType].includes(t.typ)); + if (tokens.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@counter-style', + error: 'expected counter style name', + tokens + }; + } + if (tokens.length > 1) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[1] ?? atRule, + syntax: '@counter-style', + error: 'unexpected token', + tokens + }; + } + if (![exports.EnumToken.IdenTokenType, exports.EnumToken.DashedIdenTokenType].includes(tokens[0].typ)) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: '@counter-style', + error: 'expected counter style name', + tokens + }; + } + if (!('chi' in atRule)) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@counter-style', + error: 'expected counter style body', + tokens + }; + } + // @ts-ignore + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@counter-style', + error: '', + tokens + }; + } + + function validateAtRulePage(atRule, options, root) { + // media-query-list + if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Valid, + matches: [], + node: null, + syntax: '@page', + error: '', + tokens: [] + }; + } + // page-selector-list + for (const tokens of splitTokenList(atRule.tokens)) { + if (tokens.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0] ?? atRule, + syntax: '@page', + error: 'unexpected token', + tokens: [] + }; + } + // + | * + // ident pseudo-page* | pseudo-page+ + if (tokens[0].typ == exports.EnumToken.IdenTokenType) { + tokens.shift(); + if (tokens.length == 0) { + continue; + } + // @ts-ignore + if (tokens[0].typ != exports.EnumToken.WhitespaceTokenType) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0] ?? atRule, + syntax: '@page', + error: 'unexpected token', + tokens: [] + }; + } + } + while (tokens.length > 0) { + if (tokens[0].typ == exports.EnumToken.PseudoPageTokenType) { + tokens.shift(); + if (tokens.length == 0) { + continue; + } + // @ts-ignore + if (tokens[0].typ != exports.EnumToken.WhitespaceTokenType) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0] ?? atRule, + syntax: '@page', + error: 'unexpected token', + tokens: [] + }; + } + } + } + } + // @ts-ignore + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@page', + error: '', + tokens: [] + }; + } + + function validateAtRulePageMarginBox(atRule, options, root) { + if (Array.isArray(atRule.tokens) && atRule.tokens.length > 0) { + // @ts-ignore + return { + valid: ValidationLevel.Valid, + matches: [], + node: null, + syntax: '@' + atRule.nam, + error: '', + tokens: [] + }; + } + if (!('chi' in atRule)) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected margin-box body', + tokens: [] + }; + } + for (const token of atRule.chi) { + if (![exports.EnumToken.DeclarationNodeType, exports.EnumToken.CommentNodeType, exports.EnumToken.WhitespaceTokenType].includes(token.typ)) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: token, + syntax: 'declaration-list', + error: 'expected margin-box body', + tokens: [] + }; + } } // @ts-ignore - return result; + return { + valid: ValidationLevel.Valid, + matches: [], + node: null, + syntax: '@' + atRule.nam, + error: '', + tokens: [] + }; } function validateAtRuleSupports(atRule, options, root) { @@ -13647,7 +14454,7 @@ valid: ValidationLevel.Drop, matches: [], node: atRule, - syntax: '@supports', + syntax: '@' + atRule.nam, error: 'expected supports query list', tokens: [] }; @@ -13665,7 +14472,7 @@ valid: ValidationLevel.Drop, matches: [], node: atRule, - syntax: '@supports', + syntax: '@' + atRule.nam, error: 'expected at-rule body', tokens: [] }; @@ -13675,7 +14482,7 @@ valid: ValidationLevel.Valid, matches: [], node: atRule, - syntax: '@supports', + syntax: '@' + atRule.nam, error: '', tokens: [] }; @@ -13688,7 +14495,7 @@ valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? atRule, - syntax: '@supports', + syntax: '@' + atRule.nam, error: 'unexpected token', tokens: [] }; @@ -13722,7 +14529,7 @@ valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? previousToken ?? atRule, - syntax: '@supports', + syntax: '@' + atRule.nam, error: 'expected whitespace', tokens: [] }; @@ -13734,7 +14541,7 @@ valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? atRule, - syntax: '@supports', + syntax: '@' + atRule.nam, error: 'expected and/or', tokens: [] }; @@ -13745,7 +14552,7 @@ valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? atRule, - syntax: '@supports', + syntax: '@' + atRule.nam, error: 'expected supports-condition', tokens: [] }; @@ -13757,7 +14564,7 @@ valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? atRule, - syntax: '@supports', + syntax: '@' + atRule.nam, error: 'expected whitespace', tokens: [] }; @@ -13770,13 +14577,13 @@ if (token.typ == exports.EnumToken.MediaFeatureNotTokenType) { return validateSupportCondition(atRule, token.val); } - if (token.typ != exports.EnumToken.ParensTokenType) { + if (token.typ != exports.EnumToken.ParensTokenType && !(['when', 'else'].includes(atRule.nam) && token.typ == exports.EnumToken.FunctionTokenType && ['supports', 'font-format', 'font-tech'].includes(token.val))) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], node: token, - syntax: '@supports', + syntax: '@' + atRule.nam, error: 'expected supports condition-in-parens', tokens: [] }; @@ -13791,7 +14598,7 @@ valid: ValidationLevel.Valid, matches: [], node: null, - syntax: '@supports', + syntax: '@' + atRule.nam, error: '', tokens: [] }; @@ -14047,69 +14854,306 @@ return { valid: ValidationLevel.Drop, matches: [], - node: tokens[0], - syntax: '@' + atRule.nam, - error: 'expecting whitespace', + node: tokens[0], + syntax: '@' + atRule.nam, + error: 'expecting whitespace', + tokens + }; + } + } + } + if (tokens.length > 0) { + return validateAtRuleMediaQueryList(tokens, atRule); + } + // @ts-ignore + return { + valid: ValidationLevel.Valid, + matches: [], + node: null, + syntax: '@' + atRule.nam, + error: '', + tokens: [] + }; + } + + function validateAtRuleLayer(atRule, options, root) { + // media-query-list + if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@layer', + error: '', + tokens: [] + }; + } + return validateLayerName(atRule.tokens); + } + + function validateAtRuleFontFeatureValues(atRule, options, root) { + if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: null, + syntax: '@' + atRule.nam, + error: 'expected at-rule prelude', + tokens: [] + }; + } + const result = validateFamilyName(atRule.tokens, atRule); + if (result.valid == ValidationLevel.Drop) { + return result; + } + if (!('chi' in atRule)) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected at-rule body', + tokens: [] + }; + } + // @ts-ignore + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: '', + tokens: [] + }; + } + + function validateAtRuleNamespace(atRule, options, root) { + if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@namespace', + error: 'expected at-rule prelude', + tokens: [] + }; + } + if ('chi' in atRule) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@namespace', + error: 'unexpected at-rule body', + tokens: [] + }; + } + const tokens = atRule.tokens.slice(); + consumeWhitespace(tokens); + if (tokens[0].typ == exports.EnumToken.IdenTokenType) { + tokens.shift(); + consumeWhitespace(tokens); + } + if (tokens.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@namespace', + error: 'expected string or url()', + tokens + }; + } + if (tokens[0].typ != exports.EnumToken.StringTokenType) { + const result = validateURL(tokens[0]); + if (result.valid != ValidationLevel.Valid) { + return result; + } + tokens.shift(); + consumeWhitespace(tokens); + } + else { + tokens.shift(); + consumeWhitespace(tokens); + } + if (tokens.length > 0) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: '@namespace', + error: 'unexpected token', + tokens + }; + } + // @ts-ignore + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@namespace', + error: '', + tokens + }; + } + + function validateAtRuleDocument(atRule, options, root) { + if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@document', + error: 'expecting at-rule prelude', + tokens: [] + }; + } + const tokens = atRule.tokens.slice(); + let result = null; + consumeWhitespace(tokens); + if (tokens.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@document', + error: 'expecting at-rule prelude', + tokens + }; + } + if (tokens[0].typ == exports.EnumToken.CommaTokenType) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: '@document', + error: 'unexpected token', + tokens + }; + } + while (tokens.length > 0) { + if (tokens[0].typ == exports.EnumToken.CommentTokenType) { + tokens.shift(); + consumeWhitespace(tokens); + } + result = validateURL(tokens[0]); + if (result.valid == ValidationLevel.Valid) { + tokens.shift(); + consumeWhitespace(tokens); + continue; + } + if (tokens[0].typ == exports.EnumToken.FunctionTokenType) { + if (!['url-prefix', 'domain', 'media-document', 'regexp'].some((t) => t.localeCompare(tokens[0].val, undefined, { sensitivity: 'base' }) == 0)) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: '@document', + error: 'unexpected token', + tokens + }; + } + const children = tokens[0].chi.slice(); + consumeWhitespace(children); + if (children.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: '@document', + error: 'expecting string argument', + tokens + }; + } + if (children[0].typ == exports.EnumToken.StringTokenType) { + children.shift(); + consumeWhitespace(children); + } + if (children.length > 0) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: children[0], + syntax: '@document', + error: 'unexpected token', tokens }; } + tokens.shift(); + consumeWhitespace(tokens); } } - if (tokens.length > 0) { - return validateAtRuleMediaQueryList(tokens, atRule); - } // @ts-ignore return { valid: ValidationLevel.Valid, matches: [], - node: null, - syntax: '@' + atRule.nam, + node: atRule, + syntax: '@document', error: '', - tokens: [] + tokens }; } - function validateAtRuleLayer(atRule, options, root) { - // media-query-list + function validateAtRuleKeyframes(atRule, options, root) { if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { // @ts-ignore return { - valid: ValidationLevel.Valid, + valid: ValidationLevel.Drop, matches: [], node: atRule, - syntax: '@layer', - error: '', + syntax: '@document', + error: 'expecting at-rule prelude', tokens: [] }; } - return validateLayerName(atRule.tokens); - } - - function validateAtRuleFontFeatureValues(atRule, options, root) { - if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { + const tokens = atRule.tokens.slice(); + consumeWhitespace(tokens); + if (tokens.length == 0) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], - node: null, - syntax: '@' + atRule.nam, - error: 'expected at-rule prelude', - tokens: [] + node: atRule, + syntax: '@keyframes', + error: 'expecting at-rule prelude', + tokens }; } - const result = validateFamilyName(atRule.tokens, atRule); - if (result.valid == ValidationLevel.Drop) { - return result; - } - if (!('chi' in atRule)) { + if (![exports.EnumToken.StringTokenType, exports.EnumToken.IdenTokenType].includes(tokens[0].typ)) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], node: atRule, - syntax: '@' + atRule.nam, - error: 'expected at-rule body', - tokens: [] + syntax: '@keyframes', + error: 'expecting ident or string token', + tokens + }; + } + tokens.shift(); + consumeWhitespace(tokens); + if (tokens.length > 0) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: '@keyframes', + error: 'unexpected token', + tokens }; } // @ts-ignore @@ -14117,314 +15161,562 @@ valid: ValidationLevel.Valid, matches: [], node: atRule, - syntax: '@' + atRule.nam, + syntax: '@keyframes', error: '', - tokens: [] + tokens }; } - function validateURL(token) { - if (token.typ == exports.EnumToken.UrlTokenTokenType) { + function validateAtRuleWhen(atRule, options, root) { + const slice = Array.isArray(atRule.tokens) ? atRule.tokens.slice() : []; + consumeWhitespace(slice); + if (slice.length == 0) { // @ts-ignore return { valid: ValidationLevel.Valid, matches: [], - node: token, - // @ts-ignore - syntax: 'url()', + node: atRule, + syntax: '@when', error: '', tokens: [] }; } - if (token.typ != exports.EnumToken.UrlFunctionTokenType) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: token, - // @ts-ignore - syntax: 'url()', - error: 'expected url()', - tokens: [] - }; - } - const children = token.chi.slice(); - consumeWhitespace(children); - if (children.length == 0 || ![exports.EnumToken.UrlTokenTokenType, exports.EnumToken.StringTokenType, exports.EnumToken.HashTokenType].includes(children[0].typ)) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: children[0] ?? token, - // @ts-ignore - syntax: 'url()', - error: 'expected url-token', - tokens: children - }; + const result = validateAtRuleWhenQueryList(atRule.tokens, atRule); + if (result.valid == ValidationLevel.Drop) { + return result; } - children.shift(); - consumeWhitespace(children); - if (children.length > 0) { + if (!('chi' in atRule)) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], - node: children[0] ?? token, - // @ts-ignore - syntax: 'url()', - error: 'unexpected token', - tokens: children + node: atRule, + syntax: '@when', + error: 'expected at-rule body', + tokens: [] }; } - // @ts-ignore return { valid: ValidationLevel.Valid, matches: [], - node: token, - // @ts-ignore - syntax: 'url()', + node: atRule, + syntax: '@when', error: '', - tokens: [] + tokens: result.tokens }; } - - function validateAtRuleNamespace(atRule, options, root) { - if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: atRule, - syntax: '@namespace', - error: 'expected at-rule prelude', - tokens: [] - }; - } - if ('chi' in atRule) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: atRule, - syntax: '@namespace', - error: 'unexpected at-rule body', - tokens: [] - }; + // media() = media( [ | | ] ) + // supports() = supports( ) + function validateAtRuleWhenQueryList(tokenList, atRule) { + const matched = []; + let result = null; + for (const split of splitTokenList(tokenList)) { + const match = []; + result = null; + consumeWhitespace(split); + if (split.length == 0) { + continue; + } + while (split.length > 0) { + if (split[0].typ != exports.EnumToken.FunctionTokenType || !['media', 'supports', 'font-tech', 'font-format'].includes(split[0].val)) { + result = { + valid: ValidationLevel.Drop, + matches: [], + node: split[0] ?? atRule, + syntax: '@when', + error: 'unexpected token', + tokens: [] + }; + break; + } + const chi = split[0].chi.slice(); + consumeWhitespace(chi); + if (split[0].val == 'media') { + // result = valida + if (chi.length != 1 || !(validateMediaFeature(chi[0]) || validateMediaCondition(split[0], atRule))) { + result = { + valid: ValidationLevel.Drop, + matches: [], + node: split[0] ?? atRule, + syntax: 'media( [ | | ] )', + error: 'unexpected token', + tokens: [] + }; + break; + } + } + else if (['supports', 'font-tech', 'font-format'].includes(split[0].val)) { + // result = valida + if (!validateSupportCondition(atRule, split[0])) { + result = { + valid: ValidationLevel.Drop, + matches: [], + node: split[0] ?? atRule, + syntax: 'media( [ | | ] )', + error: 'unexpected token', + tokens: [] + }; + break; + } + } + if (match.length > 0) { + match.push({ typ: exports.EnumToken.WhitespaceTokenType }); + } + match.push(split.shift()); + consumeWhitespace(split); + if (split.length == 0) { + break; + } + if (![exports.EnumToken.MediaFeatureAndTokenType, exports.EnumToken.MediaFeatureOrTokenType].includes(split[0].typ)) { + result = { + valid: ValidationLevel.Drop, + matches: [], + node: split[0] ?? atRule, + syntax: '@when', + error: 'expecting and/or media-condition', + tokens: [] + }; + break; + } + if (match.length > 0) { + match.push({ typ: exports.EnumToken.WhitespaceTokenType }); + } + match.push(split.shift()); + consumeWhitespace(split); + if (split.length == 0) { + result = { + valid: ValidationLevel.Drop, + matches: [], + node: split[0] ?? atRule, + syntax: '@when', + error: 'expecting media-condition', + tokens: [] + }; + break; + } + } + if (result == null && match.length > 0) { + matched.push(match); + } } - const tokens = atRule.tokens.slice(); - consumeWhitespace(tokens); - if (tokens[0].typ == exports.EnumToken.IdenTokenType) { - tokens.shift(); - consumeWhitespace(tokens); + if (result != null) { + return result; } - if (tokens.length == 0) { - // @ts-ignore + if (matched.length == 0) { return { valid: ValidationLevel.Drop, matches: [], - node: atRule, - syntax: '@namespace', - error: 'expected string or url()', - tokens + // @ts-ignore + node: result?.node ?? atRule, + syntax: '@when', + error: 'invalid at-rule body', + tokens: [] }; } - if (tokens[0].typ != exports.EnumToken.StringTokenType) { - const result = validateURL(tokens[0]); - if (result.valid != ValidationLevel.Valid) { - return result; + tokenList.length = 0; + for (const match of matched) { + if (tokenList.length > 0) { + tokenList.push({ + typ: exports.EnumToken.CommaTokenType + }); } - tokens.shift(); - consumeWhitespace(tokens); - } - else { - tokens.shift(); - consumeWhitespace(tokens); - } - if (tokens.length > 0) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0], - syntax: '@namespace', - error: 'unexpected token', - tokens - }; + tokenList.push(...match); } - // @ts-ignore return { valid: ValidationLevel.Valid, matches: [], node: atRule, - syntax: '@namespace', + syntax: '@when', error: '', - tokens + tokens: tokenList }; } - function validateAtRuleDocument(atRule, options, root) { + const validateAtRuleElse = validateAtRuleWhen; + + const validateContainerScrollStateFeature = validateContainerSizeFeature; + function validateAtRuleContainer(atRule, options, root) { + // media-query-list if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], node: atRule, - syntax: '@document', - error: 'expecting at-rule prelude', + syntax: '@' + atRule.nam, + error: 'expected supports query list', tokens: [] }; } - const tokens = atRule.tokens.slice(); - let result = null; - consumeWhitespace(tokens); - if (tokens.length == 0) { + const result = validateAtRuleContainerQueryList(atRule.tokens, atRule); + if (result.valid == ValidationLevel.Drop) { + return result; + } + if (!('chi' in atRule)) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], node: atRule, - syntax: '@document', - error: 'expecting at-rule prelude', - tokens + syntax: '@' + atRule.nam, + error: 'expected at-rule body', + tokens: [] }; } - if (tokens[0].typ == exports.EnumToken.CommaTokenType) { + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: '', + tokens: [] + }; + } + function validateAtRuleContainerQueryList(tokens, atRule) { + if (tokens.length == 0) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], - node: tokens[0], - syntax: '@document', - error: 'unexpected token', + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query list', tokens }; } - while (tokens.length > 0) { - if (tokens[0].typ == exports.EnumToken.CommentTokenType) { - tokens.shift(); - consumeWhitespace(tokens); - } - result = validateURL(tokens[0]); - if (result.valid == ValidationLevel.Valid) { - tokens.shift(); - consumeWhitespace(tokens); - continue; + let result = null; + let tokenType = null; + for (const queries of splitTokenList(tokens)) { + consumeWhitespace(queries); + if (queries.length == 0) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query list', + tokens + }; } - if (tokens[0].typ == exports.EnumToken.FunctionTokenType) { - if (!['url-prefix', 'domain', 'media-document', 'regexp'].some((t) => t.localeCompare(tokens[0].val, undefined, { sensitivity: 'base' }) == 0)) { - // @ts-ignore + result = null; + const match = []; + let token = null; + tokenType = null; + while (queries.length > 0) { + if (queries.length == 0) { return { valid: ValidationLevel.Drop, matches: [], - node: tokens[0], - syntax: '@document', - error: 'unexpected token', + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query list', tokens }; } - const children = tokens[0].chi.slice(); - consumeWhitespace(children); - if (children.length == 0) { - // @ts-ignore + if (queries[0].typ == exports.EnumToken.IdenTokenType) { + match.push(queries.shift()); + consumeWhitespace(queries); + } + if (queries.length == 0) { + break; + } + token = queries[0]; + if (token.typ == exports.EnumToken.MediaFeatureNotTokenType) { + token = token.val; + } + if (token.typ != exports.EnumToken.ParensTokenType && (token.typ != exports.EnumToken.FunctionTokenType || !['scroll-state', 'style'].includes(token.val))) { return { valid: ValidationLevel.Drop, matches: [], - node: tokens[0], - syntax: '@document', - error: 'expecting string argument', + node: queries[0], + syntax: '@' + atRule.nam, + error: 'expected container query-in-parens', tokens }; } - if (children[0].typ == exports.EnumToken.StringTokenType) { - children.shift(); - consumeWhitespace(children); + if (token.typ == exports.EnumToken.ParensTokenType) { + result = validateContainerSizeFeature(token.chi, atRule); } - if (children.length > 0) { - // @ts-ignore + else if (token.val == 'scroll-state') { + result = validateContainerScrollStateFeature(token.chi, atRule); + } + else { + result = validateContainerStyleFeature(token.chi, atRule); + } + if (result.valid == ValidationLevel.Drop) { + return result; + } + queries.shift(); + consumeWhitespace(queries); + if (queries.length == 0) { + break; + } + token = queries[0]; + if (token.typ != exports.EnumToken.MediaFeatureAndTokenType && token.typ != exports.EnumToken.MediaFeatureOrTokenType) { return { valid: ValidationLevel.Drop, matches: [], - node: children[0], - syntax: '@document', - error: 'unexpected token', + node: queries[0], + syntax: '@' + atRule.nam, + error: 'expecting and/or container query token', + tokens + }; + } + if (tokenType == null) { + tokenType = token.typ; + } + if (tokenType != token.typ) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: queries[0], + syntax: '@' + atRule.nam, + error: 'mixing and/or not allowed at the same level', + tokens + }; + } + queries.shift(); + consumeWhitespace(queries); + if (queries.length == 0) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: queries[0], + syntax: '@' + atRule.nam, + error: 'expected container query-in-parens', tokens }; } - tokens.shift(); - consumeWhitespace(tokens); } } - // @ts-ignore return { valid: ValidationLevel.Valid, matches: [], node: atRule, - syntax: '@document', + syntax: '@' + atRule.nam, error: '', tokens }; } - - function validateAtRuleKeyframes(atRule, options, root) { - if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: atRule, - syntax: '@document', - error: 'expecting at-rule prelude', - tokens: [] - }; + function validateContainerStyleFeature(tokens, atRule) { + tokens = tokens.slice(); + consumeWhitespace(tokens); + if (tokens.length == 1) { + if (tokens[0].typ == exports.EnumToken.ParensTokenType) { + return validateContainerStyleFeature(tokens[0].chi, atRule); + } + if ([exports.EnumToken.DashedIdenTokenType, exports.EnumToken.IdenTokenType].includes(tokens[0].typ) || + (tokens[0].typ == exports.EnumToken.MediaQueryConditionTokenType && tokens[0].op.typ == exports.EnumToken.ColonTokenType)) { + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: '', + tokens + }; + } } - const tokens = atRule.tokens.slice(); + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query features', + tokens + }; + } + function validateContainerSizeFeature(tokens, atRule) { + tokens = tokens.slice(); consumeWhitespace(tokens); if (tokens.length == 0) { - // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], node: atRule, - syntax: '@keyframes', - error: 'expecting at-rule prelude', + syntax: '@' + atRule.nam, + error: 'expected container query features', tokens }; } - if (![exports.EnumToken.StringTokenType, exports.EnumToken.IdenTokenType].includes(tokens[0].typ)) { - // @ts-ignore + if (tokens.length == 1) { + const token = tokens[0]; + if (token.typ == exports.EnumToken.MediaFeatureNotTokenType) { + return validateContainerSizeFeature([token.val], atRule); + } + if (token.typ == exports.EnumToken.ParensTokenType) { + return validateAtRuleContainerQueryStyleInParams(token.chi, atRule); + } + if (![exports.EnumToken.DashedIdenTokenType, exports.EnumToken.MediaQueryConditionTokenType].includes(tokens[0].typ)) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query features', + tokens + }; + } return { - valid: ValidationLevel.Drop, + valid: ValidationLevel.Valid, matches: [], node: atRule, - syntax: '@keyframes', - error: 'expecting ident or string token', + syntax: '@' + atRule.nam, + error: '', tokens }; } - tokens.shift(); + return validateAtRuleContainerQueryStyleInParams(tokens, atRule); + } + function validateAtRuleContainerQueryStyleInParams(tokens, atRule) { + tokens = tokens.slice(); consumeWhitespace(tokens); - if (tokens.length > 0) { - // @ts-ignore + if (tokens.length == 0) { return { valid: ValidationLevel.Drop, matches: [], - node: tokens[0], - syntax: '@keyframes', - error: 'unexpected token', + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query features', tokens }; } - // @ts-ignore + let token = tokens[0]; + let tokenType = null; + let result = null; + while (tokens.length > 0) { + token = tokens[0]; + if (token.typ == exports.EnumToken.MediaFeatureNotTokenType) { + token = token.val; + } + if (tokens[0].typ != exports.EnumToken.ParensTokenType) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query-in-parens', + tokens + }; + } + const slices = tokens[0].chi.slice(); + consumeWhitespace(slices); + if (slices.length == 1) { + if ([exports.EnumToken.MediaFeatureNotTokenType, exports.EnumToken.ParensTokenType].includes(slices[0].typ)) { + result = validateAtRuleContainerQueryStyleInParams(slices, atRule); + if (result.valid == ValidationLevel.Drop) { + return result; + } + } + else if (![exports.EnumToken.DashedIdenTokenType, exports.EnumToken.MediaQueryConditionTokenType].includes(slices[0].typ)) { + result = { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query features', + tokens + }; + } + } + else { + result = validateAtRuleContainerQueryStyleInParams(slices, atRule); + if (result.valid == ValidationLevel.Drop) { + return result; + } + } + tokens.shift(); + consumeWhitespace(tokens); + if (tokens.length == 0) { + break; + } + if (![exports.EnumToken.MediaFeatureAndTokenType, exports.EnumToken.MediaFeatureOrTokenType].includes(tokens[0].typ)) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: '@' + atRule.nam, + error: 'expecting and/or container query token', + tokens + }; + } + if (tokenType == null) { + tokenType = tokens[0].typ; + } + if (tokenType != tokens[0].typ) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: '@' + atRule.nam, + error: 'mixing and/or not allowed at the same level', + tokens + }; + } + tokens.shift(); + consumeWhitespace(tokens); + if (tokens.length == 0) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: '@' + atRule.nam, + error: 'expected container query-in-parens', + tokens + }; + } + } return { valid: ValidationLevel.Valid, matches: [], node: atRule, - syntax: '@keyframes', + syntax: '@' + atRule.nam, error: '', tokens }; } + function validateAtRuleCustomMedia(atRule, options, root) { + // media-query-list + if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Valid, + matches: [], + node: null, + syntax: null, + error: '', + tokens: [] + }; + } + const queries = atRule.tokens.slice(); + consumeWhitespace(queries); + if (queries.length == 0 || queries[0].typ != exports.EnumToken.DashedIdenTokenType) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@custom-media', + error: 'expecting dashed identifier', + tokens: [] + }; + } + queries.shift(); + const result = validateAtRuleMediaQueryList(queries, atRule); + if (result.valid == ValidationLevel.Drop) { + atRule.tokens = []; + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@custom-media', + error: '', + tokens: [] + }; + } + return result; + } + function validateAtRule(atRule, options, root) { if (atRule.nam == 'charset') { const valid = atRule.val.match(/^"[a-zA-Z][a-zA-Z0-9_-]+"$/i) != null; @@ -14467,9 +15759,21 @@ if (atRule.nam == 'namespace') { return validateAtRuleNamespace(atRule); } + if (atRule.nam == 'when') { + return validateAtRuleWhen(atRule); + } + if (atRule.nam == 'else') { + return validateAtRuleElse(atRule); + } + if (atRule.nam == 'container') { + return validateAtRuleContainer(atRule); + } if (atRule.nam == 'document') { return validateAtRuleDocument(atRule); } + if (atRule.nam == 'custom-media') { + return validateAtRuleCustomMedia(atRule); + } if (['position-try', 'property', 'font-palette-values'].includes(atRule.nam)) { if (!('tokens' in atRule)) { return { @@ -14539,15 +15843,14 @@ } } if (!(name in config.atRules)) { - // if (root?.typ == EnumToken.AtRuleNodeType) { - // - // const syntaxes: ValidationToken = (getParsedSyntax(ValidationSyntaxGroupEnum.AtRules, '@' + (root as AstAtRule).nam) as ValidationToken[])?.[0]; - // - // if ('chi' in syntaxes) { - // - // return validateSyntax(syntaxes.chi as ValidationToken[], [atRule], root, options); - // } - // } + if (options.lenient) { + return { + valid: ValidationLevel.Lenient, + node: atRule, + syntax: null, + error: '' + }; + } return { valid: ValidationLevel.Drop, node: atRule, @@ -14591,51 +15894,14 @@ exports.EnumToken.StartMatchTokenType, exports.EnumToken.EndMatchTokenType, exports.EnumToken.IncludeMatchTokenType, exports.EnumToken.DashMatchTokenType, exports.EnumToken.ContainMatchTokenType, exports.EnumToken.EOFTokenType ]); - const webkitPseudoAliasMap = { - '-webkit-autofill': 'autofill', - '-webkit-any': 'is', - '-moz-any': 'is', - '-webkit-border-after': 'border-block-end', - '-webkit-border-after-color': 'border-block-end-color', - '-webkit-border-after-style': 'border-block-end-style', - '-webkit-border-after-width': 'border-block-end-width', - '-webkit-border-before': 'border-block-start', - '-webkit-border-before-color': 'border-block-start-color', - '-webkit-border-before-style': 'border-block-start-style', - '-webkit-border-before-width': 'border-block-start-width', - '-webkit-border-end': 'border-inline-end', - '-webkit-border-end-color': 'border-inline-end-color', - '-webkit-border-end-style': 'border-inline-end-style', - '-webkit-border-end-width': 'border-inline-end-width', - '-webkit-border-start': 'border-inline-start', - '-webkit-border-start-color': 'border-inline-start-color', - '-webkit-border-start-style': 'border-inline-start-style', - '-webkit-border-start-width': 'border-inline-start-width', - '-webkit-box-align': 'align-items', - '-webkit-box-direction': 'flex-direction', - '-webkit-box-flex': 'flex-grow', - '-webkit-box-lines': 'flex-flow', - '-webkit-box-ordinal-group': 'order', - '-webkit-box-orient': 'flex-direction', - '-webkit-box-pack': 'justify-content', - '-webkit-column-break-after': 'break-after', - '-webkit-column-break-before': 'break-before', - '-webkit-column-break-inside': 'break-inside', - '-webkit-font-feature-settings': 'font-feature-settings', - '-webkit-hyphenate-character': 'hyphenate-character', - '-webkit-initial-letter': 'initial-letter', - '-webkit-margin-end': 'margin-block-end', - '-webkit-margin-start': 'margin-block-start', - '-webkit-padding-after': 'padding-block-end', - '-webkit-padding-before': 'padding-block-start', - '-webkit-padding-end': 'padding-inline-end', - '-webkit-padding-start': 'padding-inline-start', - '-webkit-min-device-pixel-ratio': 'min-resolution', - '-webkit-max-device-pixel-ratio': 'max-resolution' - }; function reject(reason) { throw new Error(reason ?? 'Parsing aborted'); } + /** + * parse css string + * @param iterator + * @param options + */ async function doParse(iterator, options = {}) { if (options.signal != null) { options.signal.addEventListener('abort', reject); @@ -14657,6 +15923,7 @@ setParent: true, removePrefix: false, validation: true, + lenient: true, ...options }; if (options.expandNestingRules) { @@ -14695,9 +15962,10 @@ } const iter = tokenize$1(iterator); let item; + const rawTokens = []; while (item = iter.next().value) { stats.bytesIn = item.bytesIn; - // + rawTokens.push(item); // doParse error if (item.hint != null && BadTokensTypes.includes(item.hint)) { // bad token @@ -14707,7 +15975,8 @@ tokens.push(item); } if (item.token == ';' || item.token == '{') { - let node = await parseNode(tokens, context, stats, options, errors, src, map); + let node = await parseNode(tokens, context, stats, options, errors, src, map, rawTokens); + rawTokens.length = 0; if (node != null) { // @ts-ignore stack.push(node); @@ -14733,7 +16002,8 @@ map = new Map; } else if (item.token == '}') { - await parseNode(tokens, context, stats, options, errors, src, map); + await parseNode(tokens, context, stats, options, errors, src, map, rawTokens); + rawTokens.length = 0; const previousNode = stack.pop(); // @ts-ignore context = stack[stack.length - 1] ?? ast; @@ -14756,7 +16026,8 @@ } } if (tokens.length > 0) { - await parseNode(tokens, context, stats, options, errors, src, map); + await parseNode(tokens, context, stats, options, errors, src, map, rawTokens); + rawTokens.length = 0; if (context != null && context.typ == exports.EnumToken.InvalidRuleTokenType) { const index = context.chi.findIndex(node => node == context); if (index > -1) { @@ -14768,6 +16039,7 @@ const previousNode = stack.pop(); // @ts-ignore context = stack[stack.length - 1] ?? ast; + // remove empty nodes // @ts-ignore if (options.removeEmpty && previousNode != null && previousNode.chi.length == 0 && context.chi[context.chi.length - 1] == previousNode) { // @ts-ignore @@ -14820,33 +16092,6 @@ minify(ast, options, true, errors, false); } } - // if (options.setParent) { - // - // const nodes: Array = [ast]; - // let node: AstNode; - // - // while ((node = nodes.shift()!)) { - // - // // @ts-ignore - // if (node.chi.length > 0) { - // - // // @ts-ignore - // for (const child of node.chi) { - // - // if (child.parent != node) { - // - // Object.defineProperty(child, 'parent', {...definedPropertySettings, value: node}); - // } - // - // if ('chi' in child && child.chi.length > 0) { - // - // // @ts-ignore - // nodes.push(child); - // } - // } - // } - // } - // } const endTime = performance.now(); if (options.signal != null) { options.signal.removeEventListener('abort', reject); @@ -14863,7 +16108,17 @@ } }; } - async function parseNode(results, context, stats, options, errors, src, map) { + function getLastNode(context) { + let i = context.chi.length; + while (i--) { + if ([exports.EnumToken.CommentTokenType, exports.EnumToken.CDOCOMMTokenType, exports.EnumToken.WhitespaceTokenType].includes(context.chi[i].typ)) { + continue; + } + return context.chi[i]; + } + return null; + } + async function parseNode(results, context, stats, options, errors, src, map, rawTokens) { let tokens = []; for (const t of results) { const node = getTokenType(t.token, t.hint); @@ -14918,24 +16173,6 @@ if (tokens[0]?.typ == exports.EnumToken.AtRuleTokenType) { const atRule = tokens.shift(); const position = map.get(atRule); - // if (atRule.val == 'charset') { - // - // if (context.typ != EnumToken.StyleSheetNodeType || context.chi.some(t => t.typ != EnumToken.CDOCOMMTokenType && t.typ != EnumToken.CommentNodeType)) { - // - // errors.push({ - // action: 'drop', - // message: 'doParse: invalid @charset', - // location: {src, ...position} - // }); - // - // return null; - // } - // - // if (options.removeCharset) { - // - // return null; - // } - // } // @ts-ignore while ([exports.EnumToken.WhitespaceTokenType].includes(tokens[0]?.typ)) { tokens.shift(); @@ -15032,8 +16269,43 @@ // https://www.w3.org/TR/css-nesting-1/#conditionals // allowed nesting at-rules // there must be a top level rule in the stack - if (atRule.val == 'charset' && options.removeCharset) { - return null; + if (atRule.val == 'charset') { + let spaces = 0; + // https://developer.mozilla.org/en-US/docs/Web/CSS/@charset + for (let k = 1; k < rawTokens.length; k++) { + if (rawTokens[k].hint == exports.EnumToken.WhitespaceTokenType) { + spaces += rawTokens[k].len; + continue; + } + if (rawTokens[k].hint == exports.EnumToken.CommentTokenType) { + continue; + } + if (rawTokens[k].hint == exports.EnumToken.CDOCOMMTokenType) { + continue; + } + if (spaces > 1) { + errors.push({ + action: 'drop', + message: '@charset must have only one space', + // @ts-ignore + location: { src, ...(map.get(atRule) ?? position) } + }); + return null; + } + if (rawTokens[k].hint != exports.EnumToken.StringTokenType || rawTokens[k].token[0] != '"') { + errors.push({ + action: 'drop', + message: '@charset expects a ""', + // @ts-ignore + location: { src, ...(map.get(atRule) ?? position) } + }); + return null; + } + break; + } + if (options.removeCharset) { + return null; + } } const t = parseAtRulePrelude(parseTokens(tokens, { minify: options.minify }), atRule); const raw = t.reduce((acc, curr) => { @@ -15061,11 +16333,36 @@ node.loc = loc; } if (options.validation) { - const valid = validateAtRule(node, options, context); + let isValid = true; + if (node.nam == 'else') { + const prev = getLastNode(context); + if (prev != null && prev.typ == exports.EnumToken.AtRuleNodeType && ['when', 'else'].includes(prev.nam)) { + if (prev.nam == 'else') { + isValid = Array.isArray(prev.tokens) && prev.tokens.length > 0; + } + } + else { + isValid = false; + } + } + const valid = isValid ? validateAtRule(node, options, context) : { + valid: ValidationLevel.Drop, + node, + syntax: '@' + node.nam, + error: '@' + node.nam + ' not allowed here'}; if (valid.valid == ValidationLevel.Drop) { + errors.push({ + action: 'drop', + message: valid.error + ' - "' + tokens.reduce((acc, curr) => acc + renderToken(curr, { minify: false }), '') + '"', + // @ts-ignore + location: { src, ...(map.get(valid.node) ?? position) } + }); // @ts-ignore node.typ = exports.EnumToken.InvalidAtRuleTokenType; } + else { + node.val = node.tokens.reduce((acc, curr) => acc + renderToken(curr, { minify: false, removeComments: true }), ''); + } } // @ts-ignore context.chi.push(node); @@ -15244,7 +16541,24 @@ }; const result = parseDeclarationNode(node, errors, src, position); if (result != null) { - if (options.validation) ; + // if (options.validation) { + // + // const valid: ValidationResult = validateDeclaration(result, options, context); + // + // console.error({valid}); + // + // if (valid.valid == ValidationLevel.Drop) { + // + // errors.push({ + // action: 'drop', + // message: valid.error + ' - "' + tokens.reduce((acc, curr) => acc + renderToken(curr, {minify: false}), '') + '"', + // // @ts-ignore + // location: {src, ...(map.get(valid.node) ?? position)} + // }); + // + // return null; + // } + // } // @ts-ignore context.chi.push(result); Object.defineProperty(result, 'parent', { ...definedPropertySettings, value: context }); @@ -15253,6 +16567,11 @@ } } } + /** + * parse at-rule prelude + * @param tokens + * @param atRule + */ function parseAtRulePrelude(tokens, atRule) { // @ts-ignore for (const { value, parent } of walkValues(tokens, null, null, true)) { @@ -15321,17 +16640,18 @@ continue; } } - if (value.typ == exports.EnumToken.ParensTokenType) { + if (value.typ == exports.EnumToken.ParensTokenType || (value.typ == exports.EnumToken.FunctionTokenType && ['media', 'supports', 'style', 'scroll-state'].includes(value.val))) { // @todo parse range and declarations // parseDeclaration(parent.chi); let i; let nameIndex = -1; let valueIndex = -1; + const dashedIdent = value.typ == exports.EnumToken.FunctionTokenType && value.val == 'style'; for (let i = 0; i < value.chi.length; i++) { if (value.chi[i].typ == exports.EnumToken.CommentTokenType || value.chi[i].typ == exports.EnumToken.WhitespaceTokenType) { continue; } - if (value.chi[i].typ == exports.EnumToken.IdenTokenType) { + if ((dashedIdent && value.chi[i].typ == exports.EnumToken.DashedIdenTokenType) || value.chi[i].typ == exports.EnumToken.IdenTokenType || value.chi[i].typ == exports.EnumToken.FunctionTokenType || value.chi[i].typ == exports.EnumToken.ColorTokenType) { nameIndex = i; } break; @@ -15360,6 +16680,13 @@ ].includes(value.chi[valueIndex].typ)) { const val = value.chi.splice(valueIndex, 1)[0]; const node = value.chi.splice(nameIndex, 1)[0]; + // 'background' + // @ts-ignore + if (node.typ == exports.EnumToken.ColorTokenType && node.kin == 'dpsys') { + // @ts-ignore + delete node.kin; + node.typ = exports.EnumToken.IdenTokenType; + } while (value.chi[0]?.typ == exports.EnumToken.WhitespaceTokenType) { value.chi.shift(); } @@ -15377,6 +16704,10 @@ } return tokens; } + /** + * parse selector + * @param tokens + */ function parseSelector(tokens) { for (const { value, previousValue, nextValue, parent } of walkValues(tokens)) { if (value.typ == exports.EnumToken.CommentTokenType || @@ -15520,6 +16851,11 @@ // // return doParse(`.x{${src}`, options).then((result: ParseResult) => (result.ast.chi[0]).chi.filter(t => t.typ == EnumToken.DeclarationNodeType)); // } + /** + * parse string + * @param src + * @param options + */ function parseString(src, options = { location: false }) { return parseTokens([...tokenize$1(src)].map(t => { const token = getTokenType(t.token, t.hint); @@ -15601,7 +16937,7 @@ chi: [] }; } - if (['linear-gradient', 'radial-gradient', 'repeating-linear-gradient', 'repeating-radial-gradient', 'conic-gradient', 'image', 'image-set', 'element', 'cross-fade'].includes(val)) { + if (['linear-gradient', 'radial-gradient', 'repeating-linear-gradient', 'repeating-radial-gradient', 'conic-gradient', 'image', 'image-set', 'element', 'cross-fade', 'paint'].includes(val)) { return { typ: exports.EnumToken.ImageFunctionTokenType, val, @@ -15701,6 +17037,11 @@ val }; } + /** + * parse token list + * @param tokens + * @param options + */ function parseTokens(tokens, options = {}) { for (let i = 0; i < tokens.length; i++) { const t = tokens[i]; @@ -16077,6 +17418,11 @@ WalkerValueEvent[WalkerValueEvent["Enter"] = 0] = "Enter"; WalkerValueEvent[WalkerValueEvent["Leave"] = 1] = "Leave"; })(WalkerValueEvent || (WalkerValueEvent = {})); + /** + * walk ast nodes + * @param node + * @param filter + */ function* walk(node, filter) { const parents = [node]; const root = node; @@ -16105,6 +17451,13 @@ } } } + /** + * walk ast values + * @param values + * @param root + * @param filter + * @param reverse + */ function* walkValues(values, root = null, filter, reverse) { // const set = new Set(); const stack = values.slice(); @@ -16198,6 +17551,10 @@ } } + /** + * expand nested css ast + * @param ast + */ function expand(ast) { // if (![exports.EnumToken.RuleNodeType, exports.EnumToken.StyleSheetNodeType, exports.EnumToken.AtRuleNodeType].includes(ast.typ)) { @@ -16242,7 +17599,7 @@ } return result; } - function expandRule(node, parent) { + function expandRule(node) { const ast = { ...node, chi: node.chi.slice() }; const result = []; if (ast.typ == exports.EnumToken.RuleNodeType) { @@ -16347,7 +17704,10 @@ if (astAtRule.val.includes('&')) { astAtRule.val = replaceCompound(astAtRule.val, ast.sel); } - astAtRule = expand(astAtRule); + const slice = astAtRule.chi.slice().filter(t => t.typ == exports.EnumToken.RuleNodeType && t.sel.includes('&')); + if (slice.length > 0) { + expandRule({ ...node, chi: astAtRule.chi.slice() }); + } } else { // @ts-ignore @@ -16386,6 +17746,11 @@ // @ts-ignore return ast.chi.length > 0 ? [ast].concat(result) : result; } + /** + * replace compound selector + * @param input + * @param replace + */ function replaceCompound(input, replace) { const tokens = parseString(input); let replacement = null; @@ -16857,20 +18222,8 @@ return acc; }, []) }][Symbol.iterator](); - // return { - // next() { - // - // return iterator.next(); - // } - // } } return iterator; - // return { - // next() { - // - // return iterator.next(); - // } - // } } } @@ -17027,55 +18380,6 @@ } return this; } - matchTypes(declaration) { - const patterns = this.pattern.slice(); - const values = [...declaration.val]; - let i; - let j; - const map = new Map; - for (i = 0; i < patterns.length; i++) { - for (j = 0; j < values.length; j++) { - if (!map.has(patterns[i])) { - // @ts-ignore - map.set(patterns[i], this.config.properties?.[patterns[i]]?.constraints?.mapping?.max ?? 1); - } - let count = map.get(patterns[i]); - if (count > 0 && matchType(values[j], this.config.properties[patterns[i]])) { - Object.defineProperty(values[j], 'propertyName', { - enumerable: false, - writable: true, - value: patterns[i] - }); - map.set(patterns[i], --count); - values.splice(j--, 1); - } - } - } - if (this.config.set != null) { - for (const [key, val] of Object.entries(this.config.set)) { - if (map.has(key)) { - for (const v of val) { - // missing - if (map.get(v) == 1) { - let i = declaration.val.length; - while (i--) { - // @ts-ignore - if (declaration.val[i].propertyName == key) { - const val = { ...declaration.val[i] }; - Object.defineProperty(val, 'propertyName', { - enumerable: false, - writable: true, - value: v - }); - declaration.val.splice(i, 0, val, { typ: exports.EnumToken.WhitespaceTokenType }); - } - } - } - } - } - } - } - } [Symbol.iterator]() { let iterable; let requiredCount = 0; @@ -17114,9 +18418,15 @@ // @ts-ignore let typ = (exports.EnumToken[this.config.separator?.typ] ?? exports.EnumToken.CommaTokenType); // @ts-ignore - const sep = this.config.separator == null ? null : { ...this.config.separator, typ: exports.EnumToken[this.config.separator.typ] }; + const sep = this.config.separator == null ? null : { + ...this.config.separator, + typ: exports.EnumToken[this.config.separator.typ] + }; // @ts-ignore - const separator = this.config.separator ? renderToken({ ...this.config.separator, typ: exports.EnumToken[this.config.separator.typ] }) : ','; + const separator = this.config.separator ? renderToken({ + ...this.config.separator, + typ: exports.EnumToken[this.config.separator.typ] + }) : ','; this.matchTypes(declaration); values.push(value); for (i = 0; i < declaration.val.length; i++) { @@ -17311,7 +18621,6 @@ acc.push(curr); return acc; }, []); - // @todo remove renderToken call if (props.default.includes(curr[1][i].reduce((acc, curr) => acc + renderToken(curr) + ' ', '').trimEnd())) { if (!this.config.properties[curr[0]].required) { continue; @@ -17409,6 +18718,7 @@ } // @ts-ignore if (values.length == 1 && + // @ts-ignore typeof values[0].val == 'string' && this.config.default.includes(values[0].val.toLowerCase()) && this.config.default[0] != values[0].val.toLowerCase()) { @@ -17427,6 +18737,7 @@ // @ts-ignore next() { let v = iterable.next(); + // @ts-ignore while (v.done || v.value instanceof PropertySet) { if (v.value instanceof PropertySet) { // @ts-ignore @@ -17449,6 +18760,55 @@ } }; } + matchTypes(declaration) { + const patterns = this.pattern.slice(); + const values = [...declaration.val]; + let i; + let j; + const map = new Map; + for (i = 0; i < patterns.length; i++) { + for (j = 0; j < values.length; j++) { + if (!map.has(patterns[i])) { + // @ts-ignore + map.set(patterns[i], this.config.properties?.[patterns[i]]?.constraints?.mapping?.max ?? 1); + } + let count = map.get(patterns[i]); + if (count > 0 && matchType(values[j], this.config.properties[patterns[i]])) { + Object.defineProperty(values[j], 'propertyName', { + enumerable: false, + writable: true, + value: patterns[i] + }); + map.set(patterns[i], --count); + values.splice(j--, 1); + } + } + } + if (this.config.set != null) { + for (const [key, val] of Object.entries(this.config.set)) { + if (map.has(key)) { + for (const v of val) { + // missing + if (map.get(v) == 1) { + let i = declaration.val.length; + while (i--) { + // @ts-ignore + if (declaration.val[i].propertyName == key) { + const val = { ...declaration.val[i] }; + Object.defineProperty(val, 'propertyName', { + enumerable: false, + writable: true, + value: v + }); + declaration.val.splice(i, 0, val, { typ: exports.EnumToken.WhitespaceTokenType }); + } + } + } + } + } + } + } + } removeDefaults(map, value) { for (const [key, val] of map) { const config = this.config.properties[key]; @@ -17749,6 +19109,15 @@ const notEndingWith = ['(', '['].concat(combinators); // @ts-ignore const features = Object.values(allFeatures).sort((a, b) => a.ordering - b.ordering); + /** + * minify ast + * @param ast + * @param options + * @param recursive + * @param errors + * @param nestingContent + * @param context + */ function minify(ast, options = {}, recursive = false, errors, nestingContent, context = {}) { if (!('nodes' in context)) { context.nodes = new Set; @@ -17818,7 +19187,8 @@ continue; } if (node.typ == exports.EnumToken.AtRuleNodeType) { - if (node.nam == 'media' && node.val == 'all') { + // @ts-ignore + if (node.nam == 'media' && ['all', '', null].includes(node.val)) { // @ts-ignore ast.chi?.splice(i, 1, ...node.chi); i--; @@ -18133,6 +19503,18 @@ } return ast; } + function hasDeclaration(node) { + // @ts-ignore + for (let i = 0; i < node.chi?.length; i++) { + // @ts-ignore + if (node.chi[i].typ == exports.EnumToken.CommentNodeType) { + continue; + } + // @ts-ignore + return node.chi[i].typ == exports.EnumToken.DeclarationNodeType; + } + return true; + } function reduceSelector(selector) { if (selector.length == 0) { return null; @@ -18233,18 +19615,10 @@ reducible: selector.every((selector) => !['>', '+', '~', '&'].includes(selector[0])) }; } - function hasDeclaration(node) { - // @ts-ignore - for (let i = 0; i < node.chi?.length; i++) { - // @ts-ignore - if (node.chi[i].typ == exports.EnumToken.CommentNodeType) { - continue; - } - // @ts-ignore - return node.chi[i].typ == exports.EnumToken.DeclarationNodeType; - } - return true; - } + /** + * split selector string + * @param buffer + */ function splitRule(buffer) { const result = [[]]; let str = ''; diff --git a/dist/index.cjs b/dist/index.cjs index 4fb031ef..02cb81df 100644 --- a/dist/index.cjs +++ b/dist/index.cjs @@ -7,6 +7,7 @@ var ValidationLevel; (function (ValidationLevel) { ValidationLevel[ValidationLevel["Valid"] = 0] = "Valid"; ValidationLevel[ValidationLevel["Drop"] = 1] = "Drop"; + ValidationLevel[ValidationLevel["Lenient"] = 2] = "Lenient"; /* preserve unknown at-rules, declarations and pseudo-classes */ })(ValidationLevel || (ValidationLevel = {})); exports.EnumToken = void 0; (function (EnumToken) { @@ -203,7 +204,7 @@ const colorRange = { } }; const colorFuncColorSpace = ['srgb', 'srgb-linear', 'display-p3', 'prophoto-rgb', 'a98-rgb', 'rec2020', 'xyz', 'xyz-d65', 'xyz-d50']; -({ typ: exports.EnumToken.IdenTokenType, val: 'none' }); +({ typ: exports.EnumToken.IdenTokenType}); const D50 = [0.3457 / 0.3585, 1.00000, (1.0 - 0.3457 - 0.3585) / 0.3585]; const k = Math.pow(29, 3) / Math.pow(3, 3); const e = Math.pow(6, 3) / Math.pow(29, 3); @@ -734,7 +735,7 @@ function OKLab_to_sRGB(l, a, b) { 1.2914855378640917399 * b, 3); return lsrgb2srgbvalues( /* r: */ - +4.076741661347994 * L - + 4.076741661347994 * L - 3.307711590408193 * M + 0.230969928729428 * S, /* g: */ @@ -3331,17 +3332,27 @@ function update(position, str) { } } } +/** + * render ast + * @param data + * @param options + */ function doRender(data, options = {}) { + const minify = options.minify ?? true; + const beautify = options.beautify ?? !minify; options = { - ...(options.minify ?? true ? { + ...(beautify ? { + indent: ' ', + newLine: '\n', + } : { indent: '', newLine: '', + }), + ...(minify ? { removeEmpty: true, removeComments: true } : { - indent: ' ', - newLine: '\n', - compress: false, + removeEmpty: false, removeComments: false, }), sourcemap: false, convertColor: true, expandNestingRules: false, preserveLicense: false, ...options }; @@ -3409,7 +3420,18 @@ function updateSourceMap(node, options, cache, sourcemap, position, str) { } update(position, str); } -// @ts-ignore +/** + * render ast node + * @param data + * @param options + * @param sourcemap + * @param position + * @param errors + * @param reducer + * @param cache + * @param level + * @param indents + */ function renderAstNode(data, options, sourcemap, position, errors, reducer, cache, level = 0, indents = []) { if (indents.length < level + 1) { indents.push(options.indent.repeat(level)); @@ -3502,8 +3524,15 @@ function renderAstNode(data, options, sourcemap, position, errors, reducer, cach // return renderToken(data as Token, options, cache, reducer, errors); throw new Error(`render: unexpected token ${JSON.stringify(data, null, 1)}`); } - return ''; } +/** + * render ast token + * @param token + * @param options + * @param cache + * @param reducer + * @param errors + */ function renderToken(token, options = {}, cache = Object.create(null), reducer, errors) { if (reducer == null) { reducer = function (acc, curr) { @@ -3933,6 +3962,341 @@ const mediaTypes = ['all', 'print', 'screen', 'aural', 'braille', 'embossed', 'handheld', 'projection', 'tty', 'tv', 'speech']; // https://www.w3.org/TR/css-values-4/#math-function const mathFuncs = ['calc', 'clamp', 'min', 'max', 'round', 'mod', 'rem', 'sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'atan2', 'pow', 'sqrt', 'hypot', 'log', 'exp', 'abs', 'sign']; +const webkitPseudoAliasMap = { + '-webkit-autofill': 'autofill', + '-webkit-any': 'is', + '-moz-any': 'is', + '-webkit-border-after': 'border-block-end', + '-webkit-border-after-color': 'border-block-end-color', + '-webkit-border-after-style': 'border-block-end-style', + '-webkit-border-after-width': 'border-block-end-width', + '-webkit-border-before': 'border-block-start', + '-webkit-border-before-color': 'border-block-start-color', + '-webkit-border-before-style': 'border-block-start-style', + '-webkit-border-before-width': 'border-block-start-width', + '-webkit-border-end': 'border-inline-end', + '-webkit-border-end-color': 'border-inline-end-color', + '-webkit-border-end-style': 'border-inline-end-style', + '-webkit-border-end-width': 'border-inline-end-width', + '-webkit-border-start': 'border-inline-start', + '-webkit-border-start-color': 'border-inline-start-color', + '-webkit-border-start-style': 'border-inline-start-style', + '-webkit-border-start-width': 'border-inline-start-width', + '-webkit-box-align': 'align-items', + '-webkit-box-direction': 'flex-direction', + '-webkit-box-flex': 'flex-grow', + '-webkit-box-lines': 'flex-flow', + '-webkit-box-ordinal-group': 'order', + '-webkit-box-orient': 'flex-direction', + '-webkit-box-pack': 'justify-content', + '-webkit-column-break-after': 'break-after', + '-webkit-column-break-before': 'break-before', + '-webkit-column-break-inside': 'break-inside', + '-webkit-font-feature-settings': 'font-feature-settings', + '-webkit-hyphenate-character': 'hyphenate-character', + '-webkit-initial-letter': 'initial-letter', + '-webkit-margin-end': 'margin-block-end', + '-webkit-margin-start': 'margin-block-start', + '-webkit-padding-after': 'padding-block-end', + '-webkit-padding-before': 'padding-block-start', + '-webkit-padding-end': 'padding-inline-end', + '-webkit-padding-start': 'padding-inline-start', + '-webkit-min-device-pixel-ratio': 'min-resolution', + '-webkit-max-device-pixel-ratio': 'max-resolution' +}; +// https://developer.mozilla.org/en-US/docs/Web/CSS/WebKit_Extensions +// https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-scrollbar +const webkitExtensions = new Set([ + '-webkit-app-region', + '-webkit-border-horizontal-spacing', + '-webkit-border-vertical-spacing', + '-webkit-box-reflect', + '-webkit-column-axis', + '-webkit-column-progression', + '-webkit-cursor-visibility', + '-webkit-font-smoothing', + '-webkit-hyphenate-limit-after', + '-webkit-hyphenate-limit-before', + '-webkit-hyphenate-limit-lines', + '-webkit-line-align', + '-webkit-line-box-contain', + '-webkit-line-clamp', + '-webkit-line-grid', + '-webkit-line-snap', + '-webkit-locale', + '-webkit-logical-height', + '-webkit-logical-width', + '-webkit-margin-after', + '-webkit-margin-before', + '-webkit-mask-box-image-outset', + '-webkit-mask-box-image-repeat', + '-webkit-mask-box-image-slice', + '-webkit-mask-box-image-source', + '-webkit-mask-box-image-width', + '-webkit-mask-box-image', + '-webkit-mask-composite', + '-webkit-mask-position-x', + '-webkit-mask-position-y', + '-webkit-mask-repeat-x', + '-webkit-mask-repeat-y', + '-webkit-mask-source-type', + '-webkit-max-logical-height', + '-webkit-max-logical-width', + '-webkit-min-logical-height', + '-webkit-min-logical-width', + '-webkit-nbsp-mode', + '-webkit-perspective-origin-x', + '-webkit-perspective-origin-y', + '-webkit-rtl-ordering', + '-webkit-tap-highlight-color', + '-webkit-text-decoration-skip', + '-webkit-text-decorations-in-effect', + '-webkit-text-fill-color', + '-webkit-text-security', + '-webkit-text-stroke-color', + '-webkit-text-stroke-width', + '-webkit-text-stroke', + '-webkit-text-zoom', + '-webkit-touch-callout', + '-webkit-transform-origin-x', + '-webkit-transform-origin-y', + '-webkit-transform-origin-z', + '-webkit-user-drag', + '-webkit-user-modify', + '-webkit-border-after', + '-webkit-border-after-color', + '-webkit-border-after-style', + '-webkit-border-after-width', + '-webkit-border-before', + '-webkit-border-before-color', + '-webkit-border-before-style', + '-webkit-border-before-width', + '-webkit-border-end', + '-webkit-border-end-color', + '-webkit-border-end-style', + '-webkit-border-end-width', + '-webkit-border-start', + '-webkit-border-start-color', + '-webkit-border-start-style', + '-webkit-border-start-width', + '-webkit-box-align', + '-webkit-box-direction', + '-webkit-box-flex-group', + '-webkit-box-flex', + '-webkit-box-lines', + '-webkit-box-ordinal-group', + '-webkit-box-orient', + '-webkit-box-pack', + '-webkit-column-break-after', + '-webkit-column-break-before', + '-webkit-column-break-inside', + '-webkit-font-feature-settings', + '-webkit-hyphenate-character', + '-webkit-initial-letter', + '-webkit-margin-end', + '-webkit-margin-start', + '-webkit-padding-after', + '-webkit-padding-before', + '-webkit-padding-end', + '-webkit-padding-start', + '-webkit-fill-available', + ':-webkit-animating-full-screen-transition', + ':-webkit-any', + ':-webkit-any-link', + ':-webkit-autofill', + ':-webkit-autofill-strong-password', + ':-webkit-drag', + ':-webkit-full-page-media', + ':-webkit-full-screen*', + ':-webkit-full-screen-ancestor', + ':-webkit-full-screen-document', + ':-webkit-full-screen-controls-hidden', + '::-webkit-file-upload-button*', + '::-webkit-inner-spin-button', + '::-webkit-input-placeholder', + '::-webkit-meter-bar', + '::-webkit-meter-even-less-good-value', + '::-webkit-meter-inner-element', + '::-webkit-meter-optimum-value', + '::-webkit-meter-suboptimum-value', + '::-webkit-progress-bar', + '::-webkit-progress-inner-element', + '::-webkit-progress-value', + '::-webkit-search-cancel-button', + '::-webkit-search-results-button', + '::-webkit-slider-runnable-track', + '::-webkit-slider-thumb', + '-webkit-animation', + '-webkit-device-pixel-ratio', + '-webkit-transform-2d', + '-webkit-transform-3d', + '-webkit-transition', + '::-webkit-scrollbar', + '::-webkit-scrollbar-button', + '::-webkit-scrollbar', + '::-webkit-scrollbar-thumb', + '::-webkit-scrollbar-track', + '::-webkit-scrollbar-track-piece', + '::-webkit-scrollbar:vertical', + '::-webkit-scrollbar-corner ', + '::-webkit-resizer', + ':vertical', + ':horizontal', +]); +// https://developer.mozilla.org/en-US/docs/Web/CSS/Mozilla_Extensions +const mozExtensions = new Set([ + '-moz-box-align', + '-moz-box-direction', + '-moz-box-flex', + '-moz-box-ordinal-group', + '-moz-box-orient', + '-moz-box-pack', + '-moz-float-edge', + '-moz-force-broken-image-icon', + '-moz-image-region', + '-moz-orient', + '-moz-osx-font-smoothing', + '-moz-user-focus', + '-moz-user-input', + '-moz-user-modify', + '-moz-animation', + '-moz-animation-delay', + '-moz-animation-direction', + '-moz-animation-duration', + '-moz-animation-fill-mode', + '-moz-animation-iteration-count', + '-moz-animation-name', + '-moz-animation-play-state', + '-moz-animation-timing-function', + '-moz-appearance', + '-moz-backface-visibility', + '-moz-background-clip', + '-moz-background-origin', + '-moz-background-inline-policy', + '-moz-background-size', + '-moz-border-end', + '-moz-border-end-color', + '-moz-border-end-style', + '-moz-border-end-width', + '-moz-border-image', + '-moz-border-start', + '-moz-border-start-color', + '-moz-border-start-style', + '-moz-border-start-width', + '-moz-box-sizing', + 'clip-path', + '-moz-column-count', + '-moz-column-fill', + '-moz-column-gap', + '-moz-column-width', + '-moz-column-rule', + '-moz-column-rule-width', + '-moz-column-rule-style', + '-moz-column-rule-color', + 'filter', + '-moz-font-feature-settings', + '-moz-font-language-override', + '-moz-hyphens', + '-moz-margin-end', + '-moz-margin-start', + 'mask', + '-moz-opacity', + '-moz-outline', + '-moz-outline-color', + '-moz-outline-offset', + '-moz-outline-style', + '-moz-outline-width', + '-moz-padding-end', + '-moz-padding-start', + '-moz-perspective', + '-moz-perspective-origin', + 'pointer-events', + '-moz-tab-size', + '-moz-text-align-last', + '-moz-text-decoration-color', + '-moz-text-decoration-line', + '-moz-text-decoration-style', + '-moz-text-size-adjust', + '-moz-transform', + '-moz-transform-origin', + '-moz-transform-style', + '-moz-transition', + '-moz-transition-delay', + '-moz-transition-duration', + '-moz-transition-property', + '-moz-transition-timing-function', + '-moz-user-select', + '-moz-initial', + '-moz-appearance', + '-moz-linear-gradient', + '-moz-radial-gradient', + '-moz-element', + '-moz-image-rect', + '::-moz-anonymous-block', + '::-moz-anonymous-positioned-block', + ':-moz-any', + ':-moz-any-link', + ':-moz-broken', + '::-moz-canvas', + '::-moz-color-swatch', + '::-moz-cell-content', + ':-moz-drag-over', + ':-moz-first-node', + '::-moz-focus-inner', + '::-moz-focus-outer', + ':-moz-full-screen', + ':-moz-full-screen-ancestor', + ':-moz-handler-blocked', + ':-moz-handler-crashed', + ':-moz-handler-disabled', + '::-moz-inline-table', + ':-moz-last-node', + '::-moz-list-bullet', + '::-moz-list-number', + ':-moz-loading', + ':-moz-locale-dir', + ':-moz-locale-dir', + ':-moz-lwtheme', + ':-moz-lwtheme-brighttext', + ':-moz-lwtheme-darktext', + '::-moz-meter-bar', + ':-moz-native-anonymous', + ':-moz-only-whitespace', + '::-moz-pagebreak', + '::-moz-pagecontent', + ':-moz-placeholder', + '::-moz-placeholder', + '::-moz-progress-bar', + '::-moz-range-progress', + '::-moz-range-thumb', + '::-moz-range-track', + ':-moz-read-only', + ':-moz-read-write', + '::-moz-scrolled-canvas', + '::-moz-scrolled-content', + '::-moz-selection', + ':-moz-submit-invalid', + ':-moz-suppressed', + '::-moz-svg-foreign-content', + '::-moz-table', + '::-moz-table-cell', + '::-moz-table-column', + '::-moz-table-column-group', + '::-moz-table-outer', + '::-moz-table-row', + '::-moz-table-row-group', + ':-moz-ui-invalid', + ':-moz-ui-valid', + ':-moz-user-disabled', + '::-moz-viewport', + '::-moz-viewport-scroll', + ':-moz-window-inactive', + '-moz-device-pixel-ratio', + '-moz-os-version', + '-moz-touch-enabled', + '-moz-windows-glass', + '-moz-alt-content' +]); function isLength(dimension) { return 'unit' in dimension && dimensionUnits.has(dimension.unit.toLowerCase()); } @@ -5953,7 +6317,13 @@ function consumeWhiteSpace(parseInfo) { return count; } function pushToken(token, parseInfo, hint) { - const result = { token, hint, position: { ...parseInfo.position }, bytesIn: parseInfo.currentPosition.ind + 1 }; + const result = { + token, + len: parseInfo.currentPosition.ind - parseInfo.position.ind, + hint, + position: { ...parseInfo.position }, + bytesIn: parseInfo.currentPosition.ind + 1 + }; parseInfo.position.ind = parseInfo.currentPosition.ind; parseInfo.position.lin = parseInfo.currentPosition.lin; parseInfo.position.col = Math.max(parseInfo.currentPosition.col, 1); @@ -6073,6 +6443,10 @@ function next(parseInfo, count = 1) { } return char; } +/** + * tokenize css string + * @param stream + */ function* tokenize$1(stream) { const parseInfo = { stream, @@ -6121,8 +6495,10 @@ function* tokenize$1(stream) { buffer += value; } } - yield pushToken(buffer, parseInfo, exports.EnumToken.BadCommentTokenType); - buffer = ''; + if (buffer.length > 0) { + yield pushToken(buffer, parseInfo, exports.EnumToken.BadCommentTokenType); + buffer = ''; + } } break; case '&': @@ -7498,9 +7874,6 @@ var declarations = { "inline-size": { syntax: "<'width'>" }, - "input-security": { - syntax: "auto | none" - }, inset: { syntax: "<'top'>{1,4}" }, @@ -8059,6 +8432,9 @@ var declarations = { "shape-rendering": { syntax: "auto | optimizeSpeed | crispEdges | geometricPrecision" }, + "speak-as": { + syntax: "normal | spell-out || digits || [ literal-punctuation | no-punctuation ]" + }, "stop-color": { syntax: "<'color'>" }, @@ -8171,7 +8547,7 @@ var declarations = { syntax: "none | auto | " }, "text-spacing-trim": { - syntax: "space-all | normal | space-first | trim-start | trim-both | trim-all | auto" + syntax: "space-all | normal | space-first | trim-start" }, "text-transform": { syntax: "none | capitalize | uppercase | lowercase | full-width | full-size-kana" @@ -8267,7 +8643,7 @@ var declarations = { syntax: "normal | pre | nowrap | pre-wrap | pre-line | break-spaces | [ <'white-space-collapse'> || <'text-wrap'> ]" }, "white-space-collapse": { - syntax: "collapse | discard | preserve | preserve-breaks | preserve-spaces | break-spaces" + syntax: "collapse | preserve | preserve-breaks | preserve-spaces | break-spaces" }, widows: { syntax: "" @@ -8329,10 +8705,10 @@ var functions = { syntax: "attr( ? [, ]? )" }, blur: { - syntax: "blur( )" + syntax: "blur( ? )" }, brightness: { - syntax: "brightness( )" + syntax: "brightness( [ | ]? )" }, calc: { syntax: "calc( )" @@ -8356,7 +8732,7 @@ var functions = { syntax: "conic-gradient( [ from ]? [ at ]?, )" }, contrast: { - syntax: "contrast( [ ] )" + syntax: "contrast( [ | ]? )" }, cos: { syntax: "cos( )" @@ -8371,7 +8747,7 @@ var functions = { syntax: "cross-fade( , ? )" }, "drop-shadow": { - syntax: "drop-shadow( {2,3} ? )" + syntax: "drop-shadow( [ ? && {2,3} ] )" }, element: { syntax: "element( )" @@ -8389,7 +8765,7 @@ var functions = { syntax: "fit-content( )" }, grayscale: { - syntax: "grayscale( )" + syntax: "grayscale( [ | ]? )" }, hsl: { syntax: "hsl( [ / ]? ) | hsl( , , , ? )" @@ -8398,7 +8774,7 @@ var functions = { syntax: "hsla( [ / ]? ) | hsla( , , , ? )" }, "hue-rotate": { - syntax: "hue-rotate( )" + syntax: "hue-rotate( [ | ]? )" }, hwb: { syntax: "hwb( [ | none] [ | none] [ | none] [ / [ | none] ]? )" @@ -8416,7 +8792,7 @@ var functions = { syntax: "inset( {1,4} [ round <'border-radius'> ]? )" }, invert: { - syntax: "invert( )" + syntax: "invert( [ | ]? )" }, lab: { syntax: "lab( [ | | none] [ | | none] [ | | none] [ / [ | none] ]? )" @@ -8464,7 +8840,7 @@ var functions = { syntax: "oklch( [ | | none] [ | | none] [ | none] [ / [ | none] ]? )" }, opacity: { - syntax: "opacity( [ ] )" + syntax: "opacity( [ | ]? )" }, paint: { syntax: "paint( , ? )" @@ -8473,13 +8849,13 @@ var functions = { syntax: "palette-mix( , [ [normal | light | dark | | ] && ? ]#{2})" }, path: { - syntax: "path( [ , ]? )" + syntax: "path( <'fill-rule'>? , )" }, perspective: { syntax: "perspective( [ | none ] )" }, polygon: { - syntax: "polygon( ? , [ ]# )" + syntax: "polygon( <'fill-rule'>? , [ ]# )" }, pow: { syntax: "pow( , )" @@ -8527,7 +8903,7 @@ var functions = { syntax: "round( ?, , )" }, saturate: { - syntax: "saturate( )" + syntax: "saturate( [ | ]? )" }, scale: { syntax: "scale( [ | ]#{1,2} )" @@ -8548,7 +8924,7 @@ var functions = { syntax: "scroll( [ || ]? )" }, sepia: { - syntax: "sepia( )" + syntax: "sepia( [ | ]? )" }, sign: { syntax: "sign( )" @@ -8703,10 +9079,10 @@ var syntaxes = { syntax: "normal | multiply | screen | overlay | darken | lighten | color-dodge | color-burn | hard-light | soft-light | difference | exclusion | hue | saturation | color | luminosity" }, "blur()": { - syntax: "blur( )" + syntax: "blur( ? )" }, "brightness()": { - syntax: "brightness( )" + syntax: "brightness( [ | ]? )" }, "calc()": { syntax: "calc( )" @@ -8748,7 +9124,13 @@ var syntaxes = { syntax: "" }, color: { - syntax: " | | | | | | | | | | | | | | currentcolor | transparent" + syntax: " | currentColor | | | " + }, + "color-base": { + syntax: " | | | | transparent" + }, + "color-function": { + syntax: " | | | | | | | | | " }, "color()": { syntax: "color( [from ]? [ / [ | none ] ]? )" @@ -8820,7 +9202,7 @@ var syntaxes = { syntax: "[ contextual | no-contextual ]" }, "contrast()": { - syntax: "contrast( [ ] )" + syntax: "contrast( [ | ]? )" }, "coord-box": { syntax: " | view-box" @@ -8862,7 +9244,7 @@ var syntaxes = { syntax: "[ [ | ]+ ]#" }, "deprecated-system-color": { - syntax: "ActiveBorder | ActiveCaption | AppWorkspace | Background | ButtonFace | ButtonHighlight | ButtonShadow | ButtonText | CaptionText | GrayText | Highlight | HighlightText | InactiveBorder | InactiveCaption | InactiveCaptionText | InfoBackground | InfoText | Menu | MenuText | Scrollbar | ThreeDDarkShadow | ThreeDFace | ThreeDHighlight | ThreeDLightShadow | ThreeDShadow | Window | WindowFrame | WindowText" + syntax: "ActiveBorder | ActiveCaption | AppWorkspace | Background | ButtonHighlight | ButtonShadow | CaptionText | InactiveBorder | InactiveCaption | InactiveCaptionText | InfoBackground | InfoText | Menu | MenuText | Scrollbar | ThreeDDarkShadow | ThreeDFace | ThreeDHighlight | ThreeDLightShadow | ThreeDShadow | Window | WindowFrame | WindowText" }, "discretionary-lig-values": { syntax: "[ discretionary-ligatures | no-discretionary-ligatures ]" @@ -8886,7 +9268,7 @@ var syntaxes = { syntax: "block | inline | run-in" }, "drop-shadow()": { - syntax: "drop-shadow( {2,3} ? )" + syntax: "drop-shadow( [ ? && {2,3} ] )" }, "easing-function": { syntax: "linear | | " @@ -8939,9 +9321,6 @@ var syntaxes = { "feature-value-name": { syntax: "" }, - "fill-rule": { - syntax: "nonzero | evenodd" - }, "filter-function": { syntax: " | | | | | | | | | " }, @@ -8991,7 +9370,7 @@ var syntaxes = { syntax: " | | | | | " }, "grayscale()": { - syntax: "grayscale( )" + syntax: "grayscale( [ | ]? )" }, "grid-line": { syntax: "auto | | [ && ? ] | [ span && [ || ] ]" @@ -9012,7 +9391,7 @@ var syntaxes = { syntax: "[ shorter | longer | increasing | decreasing ] hue" }, "hue-rotate()": { - syntax: "hue-rotate( )" + syntax: "hue-rotate( [ | ]? )" }, "hwb()": { syntax: "hwb( [ | none] [ | none] [ | none] [ / [ | none] ]? )" @@ -9033,7 +9412,7 @@ var syntaxes = { syntax: "image-set( # )" }, "image-set-option": { - syntax: "[ | ]x [ || type() ]" + syntax: "[ | ] [ || type() ]" }, "image-src": { syntax: " | " @@ -9048,7 +9427,7 @@ var syntaxes = { syntax: "inset( {1,4} [ round <'border-radius'> ]? )" }, "invert()": { - syntax: "invert( )" + syntax: "invert( [ | ]? )" }, "keyframe-block": { syntax: "# {\n \n}" @@ -9192,7 +9571,7 @@ var syntaxes = { syntax: "repeat( [ | auto-fill ], + )" }, "named-color": { - syntax: "transparent | aliceblue | antiquewhite | aqua | aquamarine | azure | beige | bisque | black | blanchedalmond | blue | blueviolet | brown | burlywood | cadetblue | chartreuse | chocolate | coral | cornflowerblue | cornsilk | crimson | cyan | darkblue | darkcyan | darkgoldenrod | darkgray | darkgreen | darkgrey | darkkhaki | darkmagenta | darkolivegreen | darkorange | darkorchid | darkred | darksalmon | darkseagreen | darkslateblue | darkslategray | darkslategrey | darkturquoise | darkviolet | deeppink | deepskyblue | dimgray | dimgrey | dodgerblue | firebrick | floralwhite | forestgreen | fuchsia | gainsboro | ghostwhite | gold | goldenrod | gray | green | greenyellow | grey | honeydew | hotpink | indianred | indigo | ivory | khaki | lavender | lavenderblush | lawngreen | lemonchiffon | lightblue | lightcoral | lightcyan | lightgoldenrodyellow | lightgray | lightgreen | lightgrey | lightpink | lightsalmon | lightseagreen | lightskyblue | lightslategray | lightslategrey | lightsteelblue | lightyellow | lime | limegreen | linen | magenta | maroon | mediumaquamarine | mediumblue | mediumorchid | mediumpurple | mediumseagreen | mediumslateblue | mediumspringgreen | mediumturquoise | mediumvioletred | midnightblue | mintcream | mistyrose | moccasin | navajowhite | navy | oldlace | olive | olivedrab | orange | orangered | orchid | palegoldenrod | palegreen | paleturquoise | palevioletred | papayawhip | peachpuff | peru | pink | plum | powderblue | purple | rebeccapurple | red | rosybrown | royalblue | saddlebrown | salmon | sandybrown | seagreen | seashell | sienna | silver | skyblue | slateblue | slategray | slategrey | snow | springgreen | steelblue | tan | teal | thistle | tomato | turquoise | violet | wheat | white | whitesmoke | yellow | yellowgreen" + syntax: "aliceblue | antiquewhite | aqua | aquamarine | azure | beige | bisque | black | blanchedalmond | blue | blueviolet | brown | burlywood | cadetblue | chartreuse | chocolate | coral | cornflowerblue | cornsilk | crimson | cyan | darkblue | darkcyan | darkgoldenrod | darkgray | darkgreen | darkgrey | darkkhaki | darkmagenta | darkolivegreen | darkorange | darkorchid | darkred | darksalmon | darkseagreen | darkslateblue | darkslategray | darkslategrey | darkturquoise | darkviolet | deeppink | deepskyblue | dimgray | dimgrey | dodgerblue | firebrick | floralwhite | forestgreen | fuchsia | gainsboro | ghostwhite | gold | goldenrod | gray | green | greenyellow | grey | honeydew | hotpink | indianred | indigo | ivory | khaki | lavender | lavenderblush | lawngreen | lemonchiffon | lightblue | lightcoral | lightcyan | lightgoldenrodyellow | lightgray | lightgreen | lightgrey | lightpink | lightsalmon | lightseagreen | lightskyblue | lightslategray | lightslategrey | lightsteelblue | lightyellow | lime | limegreen | linen | magenta | maroon | mediumaquamarine | mediumblue | mediumorchid | mediumpurple | mediumseagreen | mediumslateblue | mediumspringgreen | mediumturquoise | mediumvioletred | midnightblue | mintcream | mistyrose | moccasin | navajowhite | navy | oldlace | olive | olivedrab | orange | orangered | orchid | palegoldenrod | palegreen | paleturquoise | palevioletred | papayawhip | peachpuff | peru | pink | plum | powderblue | purple | rebeccapurple | red | rosybrown | royalblue | saddlebrown | salmon | sandybrown | seagreen | seashell | sienna | silver | skyblue | slateblue | slategray | slategrey | snow | springgreen | steelblue | tan | teal | thistle | tomato | turquoise | violet | wheat | white | whitesmoke | yellow | yellowgreen" }, "namespace-prefix": { syntax: "" @@ -9225,7 +9604,7 @@ var syntaxes = { syntax: "oklch( [ | | none] [ | | none] [ | none] [ / [ | none] ]? )" }, "opacity()": { - syntax: "opacity( [ ] )" + syntax: "opacity( [ | ]? )" }, "opacity-value": { syntax: " | " @@ -9273,7 +9652,7 @@ var syntaxes = { syntax: "palette-mix( , [ [normal | light | dark | | ] && ? ]#{2})" }, "path()": { - syntax: "path( [ , ]? )" + syntax: "path( <'fill-rule'>? , )" }, "perspective()": { syntax: "perspective( [ | none ] )" @@ -9282,7 +9661,7 @@ var syntaxes = { syntax: "hsl | hwb | lch | oklch" }, "polygon()": { - syntax: "polygon( ? , [ ]# )" + syntax: "polygon( <'fill-rule'>? , [ ]# )" }, position: { syntax: "[ [ left | center | right ] || [ top | center | bottom ] | [ left | center | right | ] [ top | center | bottom | ]? | [ [ left | right ] ] && [ [ top | bottom ] ] ]" @@ -9381,7 +9760,7 @@ var syntaxes = { syntax: "nearest | up | down | to-zero" }, "saturate()": { - syntax: "saturate( )" + syntax: "saturate( [ | ]? )" }, "scale()": { syntax: "scale( [ | ]#{1,2} )" @@ -9417,7 +9796,7 @@ var syntaxes = { syntax: "center | start | end | self-start | self-end | flex-start | flex-end" }, "sepia()": { - syntax: "sepia( )" + syntax: "sepia( [ | ]? )" }, shadow: { syntax: "inset? && {2,4} && ?" @@ -9982,19 +10361,103 @@ var atRules = { syntax: "@charset \"\";" }, "@counter-style": { - syntax: "@counter-style {\n [ system: ; ] ||\n [ symbols: ; ] ||\n [ additive-symbols: ; ] ||\n [ negative: ; ] ||\n [ prefix: ; ] ||\n [ suffix: ; ] ||\n [ range: ; ] ||\n [ pad: ; ] ||\n [ speak-as: ; ] ||\n [ fallback: ; ]\n}" + syntax: "@counter-style {\n [ system: ; ] ||\n [ symbols: ; ] ||\n [ additive-symbols: ; ] ||\n [ negative: ; ] ||\n [ prefix: ; ] ||\n [ suffix: ; ] ||\n [ range: ; ] ||\n [ pad: ; ] ||\n [ speak-as: ; ] ||\n [ fallback: ; ]\n}", + descriptors: { + "additive-symbols": { + syntax: "[ && ]#" + }, + fallback: { + syntax: "" + }, + negative: { + syntax: " ?" + }, + pad: { + syntax: " && " + }, + prefix: { + syntax: "" + }, + range: { + syntax: "[ [ | infinite ]{2} ]# | auto" + }, + "speak-as": { + syntax: "auto | bullets | numbers | words | spell-out | " + }, + suffix: { + syntax: "" + }, + symbols: { + syntax: "+" + }, + system: { + syntax: "cyclic | numeric | alphabetic | symbolic | additive | [ fixed ? ] | [ extends ]" + } + } }, "@document": { syntax: "@document [ | url-prefix() | domain() | media-document() | regexp() ]# {\n \n}" }, "@font-face": { - syntax: "@font-face {\n [ font-family: ; ] ||\n [ src: ; ] ||\n [ unicode-range: ; ] ||\n [ font-variant: ; ] ||\n [ font-feature-settings: ; ] ||\n [ font-variation-settings: ; ] ||\n [ font-stretch: ; ] ||\n [ font-weight: ; ] ||\n [ font-style: ; ] ||\n [ size-adjust: ; ] ||\n [ ascent-override: ; ] ||\n [ descent-override: ; ] ||\n [ line-gap-override: ; ]\n}" + syntax: "@font-face {\n [ font-family: ; ] ||\n [ src: ; ] ||\n [ unicode-range: ; ] ||\n [ font-variant: ; ] ||\n [ font-feature-settings: ; ] ||\n [ font-variation-settings: ; ] ||\n [ font-stretch: ; ] ||\n [ font-weight: ; ] ||\n [ font-style: ; ] ||\n [ size-adjust: ; ] ||\n [ ascent-override: ; ] ||\n [ descent-override: ; ] ||\n [ line-gap-override: ; ]\n}", + descriptors: { + "ascent-override": { + syntax: "normal | " + }, + "descent-override": { + syntax: "normal | " + }, + "font-display": { + syntax: "[ auto | block | swap | fallback | optional ]" + }, + "font-family": { + syntax: "" + }, + "font-feature-settings": { + syntax: "normal | #" + }, + "font-stretch": { + syntax: "{1,2}" + }, + "font-style": { + syntax: "normal | italic | oblique {0,2}" + }, + "font-variation-settings": { + syntax: "normal | [ ]#" + }, + "font-weight": { + syntax: "{1,2}" + }, + "line-gap-override": { + syntax: "normal | " + }, + "size-adjust": { + syntax: "" + }, + src: { + syntax: "[ [ format( # ) ]? | local( ) ]#" + }, + "unicode-range": { + syntax: "#" + } + } }, "@font-feature-values": { syntax: "@font-feature-values # {\n \n}" }, "@font-palette-values": { - syntax: "@font-palette-values {\n \n}" + syntax: "@font-palette-values {\n \n}", + descriptors: { + "base-palette": { + syntax: "light | dark | " + }, + "font-family": { + syntax: "#" + }, + "override-colors": { + syntax: "[ ]#" + } + } }, "@import": { syntax: "@import [ | ]\n [ layer | layer() ]?\n [ supports( [ | ] ) ]?\n ? ;" @@ -10012,13 +10475,38 @@ var atRules = { syntax: "@namespace ? [ | ];" }, "@page": { - syntax: "@page {\n \n}" + syntax: "@page {\n \n}", + descriptors: { + bleed: { + syntax: "auto | " + }, + marks: { + syntax: "none | [ crop || cross ]" + }, + "page-orientation": { + syntax: "upright | rotate-left | rotate-right " + }, + size: { + syntax: "{1,2} | auto | [ || [ portrait | landscape ] ]" + } + } }, "@position-try": { syntax: "@position-try {\n \n}" }, "@property": { - syntax: "@property {\n \n}" + syntax: "@property {\n \n}", + descriptors: { + inherits: { + syntax: "true | false" + }, + "initial-value": { + syntax: "?" + }, + syntax: { + syntax: "" + } + } }, "@scope": { syntax: "@scope [()]? [to ()]? {\n \n}" @@ -10030,7 +10518,15 @@ var atRules = { syntax: "@supports {\n \n}" }, "@view-transition": { - syntax: "@view-transition {\n \n}" + syntax: "@view-transition {\n \n}", + descriptors: { + navigation: { + syntax: "auto | none" + }, + types: { + syntax: "none | +" + } + } } }; var config$3 = { @@ -10084,6 +10580,7 @@ var ValidationTokenEnum; ValidationTokenEnum[ValidationTokenEnum["DeclarationDefinitionToken"] = 37] = "DeclarationDefinitionToken"; ValidationTokenEnum[ValidationTokenEnum["SemiColon"] = 38] = "SemiColon"; ValidationTokenEnum[ValidationTokenEnum["Character"] = 39] = "Character"; + ValidationTokenEnum[ValidationTokenEnum["ColumnArrayToken"] = 40] = "ColumnArrayToken"; })(ValidationTokenEnum || (ValidationTokenEnum = {})); var ValidationSyntaxGroupEnum; (function (ValidationSyntaxGroupEnum) { @@ -10094,6 +10591,20 @@ var ValidationSyntaxGroupEnum; ValidationSyntaxGroupEnum["AtRules"] = "atRules"; })(ValidationSyntaxGroupEnum || (ValidationSyntaxGroupEnum = {})); +var WalkValidationTokenEnum; +(function (WalkValidationTokenEnum) { + WalkValidationTokenEnum[WalkValidationTokenEnum["IgnoreChildren"] = 0] = "IgnoreChildren"; + WalkValidationTokenEnum[WalkValidationTokenEnum["IgnoreNode"] = 1] = "IgnoreNode"; + WalkValidationTokenEnum[WalkValidationTokenEnum["IgnoreAll"] = 2] = "IgnoreAll"; +})(WalkValidationTokenEnum || (WalkValidationTokenEnum = {})); +var WalkValidationTokenKeyTypeEnum; +(function (WalkValidationTokenKeyTypeEnum) { + WalkValidationTokenKeyTypeEnum["Array"] = "array"; + WalkValidationTokenKeyTypeEnum["Children"] = "chi"; + WalkValidationTokenKeyTypeEnum["Left"] = "l"; + WalkValidationTokenKeyTypeEnum["Right"] = "r"; + WalkValidationTokenKeyTypeEnum["Prelude"] = "prelude"; +})(WalkValidationTokenKeyTypeEnum || (WalkValidationTokenKeyTypeEnum = {})); const skipped = [ ValidationTokenEnum.Star, ValidationTokenEnum.HashMark, @@ -10260,13 +10771,66 @@ function* tokenize(syntax, position = { ind: 0, lin: 1, col: 0 }, currentPositio yield getTokenType$1(buffer, position, currentPosition); } } +function columnCallback(token, parent, key) { + if (key == WalkValidationTokenKeyTypeEnum.Prelude) { + return WalkValidationTokenEnum.IgnoreAll; + } + if (token.typ == ValidationTokenEnum.ColumnToken || token.typ == ValidationTokenEnum.Whitespace) { + return WalkValidationTokenEnum.IgnoreNode; + } + return WalkValidationTokenEnum.IgnoreChildren; +} +function toColumnArray(ast, parent) { + const result = new Map; + // @ts-ignore + for (const { token } of walkValidationToken(ast, null, columnCallback)) { + result.set(JSON.stringify(token), token); + } + const node = { + typ: ValidationTokenEnum.ColumnArrayToken, + chi: [...result.values()] + }; + if (parent != null) { + replaceNode(parent, ast, node); + } + return node; +} +function replaceNode(parent, target, node) { + if ('l' in parent && parent.l == target) { + parent.l = node; + } + if ('r' in parent && parent.r == target) { + parent.r = node; + } + if ('chi' in parent) { + // @ts-ignore + for (let i = 0; i < parent.chi.length; i++) { + // @ts-ignore + if (parent.chi[i] == target) { + // @ts-ignore + parent.chi[i] = node; + break; + } + } + } +} +function transform$1(context) { + for (const { token, parent } of walkValidationToken(context)) { + switch (token.typ) { + case ValidationTokenEnum.ColumnToken: + toColumnArray(token, parent); + break; + } + } + return context; +} function parseSyntax(syntax) { const root = Object.defineProperty({ typ: ValidationTokenEnum.Root, chi: [] }, 'pos', { ...objectProperties, value: { ind: 0, lin: 1, col: 0 } }); // return minify(doParseSyntax(syntaxes, tokenize(syntaxes), root)) as ValidationRootToken; - return minify$1(doParseSyntax(syntax, tokenize(syntax), root)); + return minify$1(transform$1(doParseSyntax(syntax, tokenize(syntax), root))); } function matchParens(syntax, iterator) { let item; @@ -11034,6 +11598,10 @@ function renderSyntax(token, parent) { (token.chi == null ? '' : ' {\n' + token.chi.reduce((acc, curr) => acc + renderSyntax(curr), '')).slice(1, -1) + '\n}'; case ValidationTokenEnum.Block: return '{' + token.chi.reduce((acc, t) => acc + renderSyntax(t), '') + '}'; + case ValidationTokenEnum.DeclarationDefinitionToken: + return token.nam + ': ' + renderSyntax(token.val); + case ValidationTokenEnum.ColumnArrayToken: + return token.chi.reduce((acc, curr) => acc + (acc.trim().length > 0 ? '||' : '') + renderSyntax(curr), ''); default: throw new Error('Unhandled token: ' + JSON.stringify({ token })); } @@ -11125,35 +11693,42 @@ function minify$1(ast) { } return ast; } -function* walkValidationToken(token, parent, callback) { +function* walkValidationToken(token, parent, callback, key) { if (Array.isArray(token)) { for (const child of token) { - yield* walkValidationToken(child, parent); + yield* walkValidationToken(child, parent, callback, WalkValidationTokenKeyTypeEnum.Array); } return; } - yield { token, parent }; - if ('prelude' in token) { + let action = null; + if (callback != null) { + // @ts-ignore + action = callback(token, parent, key); + } + if (action != WalkValidationTokenEnum.IgnoreNode && action != WalkValidationTokenEnum.IgnoreAll) { + yield { token, parent }; + } + if (action != WalkValidationTokenEnum.IgnoreChildren && action != WalkValidationTokenEnum.IgnoreAll && 'prelude' in token) { for (const child of token.prelude) { - yield* walkValidationToken(child, token); + yield* walkValidationToken(child, token, callback, WalkValidationTokenKeyTypeEnum.Prelude); } } - if ('chi' in token) { + if (action != WalkValidationTokenEnum.IgnoreChildren && 'chi' in token) { // @ts-ignore for (const child of token.chi) { - yield* walkValidationToken(child, token); + yield* walkValidationToken(child, token, callback, WalkValidationTokenKeyTypeEnum.Children); } } - if ('l' in token) { + if (action != WalkValidationTokenEnum.IgnoreChildren && action != WalkValidationTokenEnum.IgnoreAll && 'l' in token) { // @ts-ignore for (const child of token.l) { - yield* walkValidationToken(child, token); + yield* walkValidationToken(child, token, callback, WalkValidationTokenKeyTypeEnum.Left); } } - if ('r' in token) { + if (action != WalkValidationTokenEnum.IgnoreChildren && action != WalkValidationTokenEnum.IgnoreAll && 'r' in token) { // @ts-ignore for (const child of token.r) { - yield* walkValidationToken(child, token); + yield* walkValidationToken(child, token, callback, WalkValidationTokenKeyTypeEnum.Right); } } } @@ -11165,25 +11740,30 @@ function getSyntaxConfig() { return config$3; } function getParsedSyntax(group, key) { - if (!(key in config$3[group])) { - const matches = key.match(/(@?)(-[a-zA-Z]+)-(.*?)$/); - if (matches != null) { - key = matches[1] + matches[3]; - } - if (!(key in config$3[group])) { - return null; + // @ts-ignore + let obj = config$3[group]; + const keys = Array.isArray(key) ? key : [key]; + for (let i = 0; i < keys.length; i++) { + key = keys[i]; + if (!(key in obj)) { + if ((i == 0 && key.charAt(0) == '@') || key.charAt(0) == '-') { + const matches = key.match(/^(@?)(-[a-zA-Z]+)-(.*?)$/); + if (matches != null) { + key = matches[1] + matches[3]; + } + } + if (!(key in obj)) { + return null; + } } + // @ts-ignore + obj = obj[key]; } - const index = group + '.' + key; + const index = group + '.' + keys.join('.'); // @ts-ignore if (!parsedSyntaxes.has(index)) { // @ts-ignore - const syntax = parseSyntax(config$3[group][key].syntax); - for (const node of syntax.chi) { - for (const { token, parent } of walkValidationToken(node)) { - token.text = renderSyntax(token); - } - } + const syntax = parseSyntax(obj.syntax); // @ts-ignore parsedSyntaxes.set(index, syntax.chi); } @@ -11261,12 +11841,12 @@ function consumeWhitespace(tokens) { return true; } -function splitTokenList(tokenList) { +function splitTokenList(tokenList, split = [exports.EnumToken.CommaTokenType]) { return tokenList.reduce((acc, curr) => { if (curr.typ == exports.EnumToken.CommentTokenType) { return acc; } - if (curr.typ == exports.EnumToken.CommaTokenType) { + if (split.includes(curr.typ)) { acc.push([]); } else { @@ -11357,38 +11937,27 @@ function validateFamilyName(tokens, atRule) { }; } -const combinatorsTokens = [exports.EnumToken.ChildCombinatorTokenType, exports.EnumToken.ColumnCombinatorTokenType, - exports.EnumToken.DescendantCombinatorTokenType, exports.EnumToken.NextSiblingCombinatorTokenType, exports.EnumToken.SubsequentSiblingCombinatorTokenType]; -// [ ? ]* -function validateComplexSelector(tokens, root, options) { - // [ ? * [ * ]* ]! - tokens = tokens.slice(); - consumeWhitespace(tokens); +function validateCompoundSelector(tokens, root, options) { if (tokens.length == 0) { + // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], // @ts-ignore node: root, + // @ts-ignore syntax: null, error: 'expected selector', tokens }; } + tokens = tokens.slice(); + consumeWhitespace(tokens); + const config = getSyntaxConfig(); + let match = 0; + let length = tokens.length; while (tokens.length > 0) { - if (combinatorsTokens.includes(tokens[0].typ)) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - // @ts-ignore - node: tokens[0], - syntax: null, - error: 'unexpected combinator', - tokens - }; - } - if (tokens[0].typ == exports.EnumToken.NestingSelectorTokenType) { + while (tokens.length > 0 && tokens[0].typ == exports.EnumToken.NestingSelectorTokenType) { if (!options?.nestedSelector) { // @ts-ignore return { @@ -11401,28 +11970,28 @@ function validateComplexSelector(tokens, root, options) { tokens }; } - while (tokens.length > 0 && tokens[0].typ == exports.EnumToken.NestingSelectorTokenType) { - tokens.shift(); - consumeWhitespace(tokens); - } - if (tokens.length == 0) { - break; - } - } - if (exports.EnumToken.IdenTokenType == tokens[0].typ) { + match++; tokens.shift(); consumeWhitespace(tokens); - if (tokens.length == 0) { - break; - } } - if (exports.EnumToken.UniversalSelectorTokenType == tokens[0].typ) { + // + while (tokens.length > 0 && + [ + exports.EnumToken.IdenTokenType, + exports.EnumToken.NameSpaceAttributeTokenType, + exports.EnumToken.ClassSelectorTokenType, + exports.EnumToken.HashTokenType, + exports.EnumToken.UniversalSelectorTokenType + ].includes(tokens[0].typ)) { + match++; tokens.shift(); consumeWhitespace(tokens); } - while (tokens.length > 0) { - if (tokens[0].typ == exports.EnumToken.PseudoClassFuncTokenType) { - if (tokens[0].val.startsWith(':-webkit-')) { + while (tokens.length > 0 && tokens[0].typ == exports.EnumToken.PseudoClassFuncTokenType) { + if (!mozExtensions.has(tokens[0].val + '()') && + !webkitExtensions.has(tokens[0].val + '()') && + !((tokens[0].val + '()') in config.selectors)) { + if (!options?.lenient || /^(:?)-webkit-/.test(tokens[0].val)) { // @ts-ignore return { valid: ValidationLevel.Drop, @@ -11430,23 +11999,26 @@ function validateComplexSelector(tokens, root, options) { // @ts-ignore node: tokens[0], syntax: null, - error: 'invalid pseudo-class', + error: 'unknown pseudo-class: ' + tokens[0].val + '()', tokens }; } } - if ([ - exports.EnumToken.ClassSelectorTokenType, - exports.EnumToken.HashTokenType, - exports.EnumToken.PseudoClassTokenType, - exports.EnumToken.PseudoClassFuncTokenType - ].includes(tokens[0].typ)) { - tokens.shift(); - consumeWhitespace(tokens); - continue; - } - if (tokens[0].typ == exports.EnumToken.NestingSelectorTokenType) { - if (!options?.nestedSelector) { + match++; + tokens.shift(); + consumeWhitespace(tokens); + } + while (tokens.length > 0 && tokens[0].typ == exports.EnumToken.PseudoClassTokenType) { + const isPseudoElement = tokens[0].val.startsWith('::'); + if ( + // https://developer.mozilla.org/en-US/docs/Web/CSS/WebKit_Extensions#pseudo-elements + !(isPseudoElement && tokens[0].val.startsWith('::-webkit-')) && + !mozExtensions.has(tokens[0].val) && + !webkitExtensions.has(tokens[0].val) && + !(tokens[0].val in config.selectors) && + !(!isPseudoElement && + (':' + tokens[0].val) in config.selectors)) { + if (!options?.lenient || /^(:?)-webkit-/.test(tokens[0].val)) { // @ts-ignore return { valid: ValidationLevel.Drop, @@ -11454,37 +12026,68 @@ function validateComplexSelector(tokens, root, options) { // @ts-ignore node: tokens[0], syntax: null, - error: 'nested selector not allowed', + error: 'unknown pseudo-class: ' + tokens[0].val, tokens }; } - tokens.shift(); - consumeWhitespace(tokens); - continue; } - // validate namespace - if (tokens[0].typ == exports.EnumToken.NameSpaceAttributeTokenType) { - if (!((tokens[0].l == null || tokens[0].l.typ == exports.EnumToken.IdenTokenType || (tokens[0].l.typ == exports.EnumToken.LiteralTokenType && tokens[0].l.val == '*')) && - tokens[0].r.typ == exports.EnumToken.IdenTokenType)) { - // @ts-ignore + match++; + tokens.shift(); + consumeWhitespace(tokens); + } + while (tokens.length > 0 && tokens[0].typ == exports.EnumToken.AttrTokenType) { + const children = tokens[0].chi.slice(); + consumeWhitespace(children); + if (children.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: null, + error: 'invalid attribute selector', + tokens + }; + } + if (![ + exports.EnumToken.IdenTokenType, + exports.EnumToken.NameSpaceAttributeTokenType, + exports.EnumToken.MatchExpressionTokenType + ].includes(children[0].typ)) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: null, + error: 'invalid attribute selector', + tokens + }; + } + if (children[0].typ == exports.EnumToken.MatchExpressionTokenType) { + if (children.length != 1) { return { valid: ValidationLevel.Drop, matches: [], node: tokens[0], syntax: null, - error: 'expecting wq-name', + error: 'invalid ', tokens }; } - tokens.shift(); - consumeWhitespace(tokens); - continue; - } - // validate attribute - else if (tokens[0].typ == exports.EnumToken.AttrTokenType) { - const children = tokens[0].chi.slice(); - consumeWhitespace(children); - if (children.length == 0) { + if (![ + exports.EnumToken.IdenTokenType, + exports.EnumToken.NameSpaceAttributeTokenType + ].includes(children[0].l.typ) || + ![ + exports.EnumToken.EqualMatchTokenType, exports.EnumToken.DashMatchTokenType, + exports.EnumToken.StartMatchTokenType, exports.EnumToken.ContainMatchTokenType, + exports.EnumToken.EndMatchTokenType, exports.EnumToken.IncludeMatchTokenType + ].includes(children[0].op.typ) || + ![ + exports.EnumToken.StringTokenType, + exports.EnumToken.IdenTokenType + ].includes(children[0].r.typ)) { // @ts-ignore return { valid: ValidationLevel.Drop, @@ -11495,11 +12098,7 @@ function validateComplexSelector(tokens, root, options) { tokens }; } - if (![ - exports.EnumToken.IdenTokenType, - exports.EnumToken.NameSpaceAttributeTokenType, - exports.EnumToken.MatchExpressionTokenType - ].includes(children[0].typ)) { + if (children[0].attr != null && !['i', 's'].includes(children[0].attr)) { // @ts-ignore return { valid: ValidationLevel.Drop, @@ -11510,122 +12109,85 @@ function validateComplexSelector(tokens, root, options) { tokens }; } - if (children[0].typ == exports.EnumToken.MatchExpressionTokenType) { - if (![exports.EnumToken.IdenTokenType, - exports.EnumToken.NameSpaceAttributeTokenType].includes(children[0].l.typ) || - ![ - exports.EnumToken.EqualMatchTokenType, exports.EnumToken.DashMatchTokenType, - exports.EnumToken.StartMatchTokenType, exports.EnumToken.ContainMatchTokenType, - exports.EnumToken.EndMatchTokenType, exports.EnumToken.IncludeMatchTokenType - ].includes(children[0].op.typ) || - ![exports.EnumToken.StringTokenType, - exports.EnumToken.IdenTokenType].includes(children[0].r.typ)) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0], - syntax: null, - error: 'invalid attribute selector', - tokens - }; - } - if (children[0].attr != null && !['i', 's'].includes(children[0].attr)) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0], - syntax: null, - error: 'invalid attribute selector', - tokens - }; - } - } - children.shift(); - consumeWhitespace(children); - if (children.length > 0) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: children[0], - syntax: null, - error: 'unexpected token', - tokens - }; - } - tokens.shift(); - consumeWhitespace(tokens); - continue; } - break; - } - if (tokens.length == 0) { - break; + match++; + tokens.shift(); + consumeWhitespace(tokens); } - // combinator - if (!combinatorsTokens.includes(tokens[0].typ)) { - if (tokens[0].typ == exports.EnumToken.NestingSelectorTokenType) { - if (!options?.nestedSelector) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - // @ts-ignore - node: tokens[0], - syntax: null, - error: 'nested selector not allowed', - tokens - }; - } - tokens.shift(); - consumeWhitespace(tokens); - continue; - } - if (tokens.length > 0 && - [ - exports.EnumToken.IdenTokenType, - exports.EnumToken.AttrTokenType, - exports.EnumToken.NameSpaceAttributeTokenType, - exports.EnumToken.ClassSelectorTokenType, - exports.EnumToken.HashTokenType, - exports.EnumToken.PseudoClassTokenType, - exports.EnumToken.PseudoClassFuncTokenType - ].includes(tokens[0].typ)) { - continue; - } - // @ts-ignore + if (length == tokens.length) { return { valid: ValidationLevel.Drop, matches: [], + // @ts-ignore node: tokens[0], + // @ts-ignore syntax: null, - error: 'expecting combinator or subclass-selector', + error: 'expected compound selector', tokens }; } - const token = tokens.shift(); - consumeWhitespace(tokens); - if (tokens.length == 0) { + length = tokens.length; + } + return match == 0 ? { + valid: ValidationLevel.Drop, + matches: [], + // @ts-ignore + node: root, + // @ts-ignore + syntax: null, + error: 'expected compound selector', + tokens + } : + // @ts-ignore + { + valid: ValidationLevel.Valid, + matches: [], // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: token, - syntax: null, - error: 'expected compound-selector', - tokens - }; + node: root, + // @ts-ignore + syntax: null, + error: null, + tokens + }; +} + +const combinatorsTokens = [exports.EnumToken.ChildCombinatorTokenType, exports.EnumToken.ColumnCombinatorTokenType, + // EnumToken.DescendantCombinatorTokenType, + exports.EnumToken.NextSiblingCombinatorTokenType, exports.EnumToken.SubsequentSiblingCombinatorTokenType]; +// [ ? ]* +function validateComplexSelector(tokens, root, options) { + // [ ? * [ * ]* ]! + tokens = tokens.slice(); + consumeWhitespace(tokens); + if (tokens.length == 0) { + return { + valid: ValidationLevel.Drop, + matches: [], + // @ts-ignore + node: root, + syntax: null, + error: 'expected selector', + tokens + }; + } + // const config = getSyntaxConfig(); + // + // let match: number = 0; + let result = null; + // const combinators: EnumToken[] = combinatorsTokens.filter((t: EnumToken) => t != EnumToken.DescendantCombinatorTokenType); + for (const t of splitTokenList(tokens, combinatorsTokens)) { + result = validateCompoundSelector(t, root, options); + if (result.valid == ValidationLevel.Drop) { + return result; } } // @ts-ignore - return { - valid: ValidationLevel.Valid, + return result ?? { + valid: ValidationLevel.Drop, matches: [], - node: null, + node: root, syntax: null, - error: '', + error: 'expecting compound-selector', tokens }; } @@ -11657,20 +12219,48 @@ function validateRelativeSelector(tokens, root, options) { } function validateRelativeSelectorList(tokens, root, options) { - let i = 0; - let j = 0; - let result = null; - while (i + 1 < tokens.length) { - if (tokens[++i].typ == exports.EnumToken.CommaTokenType) { - result = validateRelativeSelector(tokens.slice(j, i), root, options); - if (result.valid == ValidationLevel.Drop) { - return result; - } - j = i + 1; - i = j; + tokens = tokens.slice(); + consumeWhitespace(tokens); + if (tokens.length == 0) { + return { + valid: ValidationLevel.Drop, + matches: [], + // @ts-ignore + node: root, + // @ts-ignore + syntax: null, + error: 'expecting relative selector list', + tokens + }; + } + for (const t of splitTokenList(tokens)) { + if (t.length == 0) { + return { + valid: ValidationLevel.Drop, + matches: [], + // @ts-ignore + node: root, + // @ts-ignore + syntax: null, + error: 'unexpected comma', + tokens + }; + } + const result = validateRelativeSelector(t, root, options); + if (result.valid == ValidationLevel.Drop) { + return result; } } - return validateRelativeSelector(i == j ? tokens.slice(i) : tokens.slice(j, i + 1), root, options); + return { + valid: ValidationLevel.Valid, + matches: [], + // @ts-ignore + node: root, + // @ts-ignore + syntax: null, + error: '', + tokens + }; } function validateComplexSelectorList(tokens, root, options) { @@ -11687,23 +12277,26 @@ function validateComplexSelectorList(tokens, root, options) { tokens }; } - let i = -1; - let j = 0; let result = null; - while (i + 1 < tokens.length) { - if (tokens[++i].typ == exports.EnumToken.CommaTokenType) { - result = validateSelector$1(tokens.slice(j, i), root, options); - if (result.valid == ValidationLevel.Drop) { - return result; - } - j = i + 1; - i = j; + for (const t of splitTokenList(tokens)) { + result = validateSelector$1(t, root, options); + if (result.valid == ValidationLevel.Drop) { + return result; } } - return validateSelector$1(i == j ? tokens.slice(i) : tokens.slice(j, i + 1), root, options); + // @ts-ignore + return result ?? { + valid: ValidationLevel.Drop, + matches: [], + // @ts-ignore + node: root, + syntax: null, + error: 'expecting complex selector list', + tokens + }; } -function validateKeyframeSelector(tokens, atRule) { +function validateKeyframeSelector(tokens, atRule, options) { consumeWhitespace(tokens); if (tokens.length == 0) { // @ts-ignore @@ -11830,7 +12423,7 @@ function validateKeyframeSelector(tokens, atRule) { }; } -function validateKeyframeBlockList(tokens, atRule) { +function validateKeyframeBlockList(tokens, atRule, options) { let i = 0; let j = 0; let result = null; @@ -11847,1079 +12440,969 @@ function validateKeyframeBlockList(tokens, atRule) { return validateKeyframeSelector(i == j ? tokens.slice(i) : tokens.slice(j, i + 1), atRule); } -const validateSelectorList = validateComplexSelectorList; - -function validateSelector(selector, options, root) { - if (root == null) { - return validateRelativeSelectorList(selector, root); +const config$2 = getSyntaxConfig(); +function consumeToken(tokens) { + tokens.shift(); +} +function consumeSyntax(syntaxes) { + syntaxes.shift(); +} +function splice(tokens, matches) { + if (matches.length == 0) { + return tokens; } // @ts-ignore - if (root.typ == exports.EnumToken.AtRuleNodeType && root.nam.match(/^(-[a-z]+-)?keyframes$/)) { - return validateKeyframeBlockList(selector, root); - } - let isNested = root.typ == exports.EnumToken.RuleNodeType ? 1 : 0; - let currentRoot = root.parent; - while (currentRoot != null && isNested == 0) { - if (currentRoot.typ == exports.EnumToken.RuleNodeType) { - isNested++; - if (isNested > 0) { - // @ts-ignore - return validateRelativeSelectorList(selector, root, { nestedSelector: true }); - } - } - currentRoot = currentRoot.parent; + const index = tokens.indexOf(matches.at(-1)); + if (index > -1) { + tokens.splice(0, index + 1); } - const nestedSelector = isNested > 0; - // @ts-ignore - return nestedSelector ? validateRelativeSelectorList(selector, root, { nestedSelector }) : validateSelectorList(selector, root, { nestedSelector }); + return tokens; } - -function validateAtRuleMedia(atRule, options, root) { - // media-query-list - if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { +function validateSyntax(syntaxes, tokens, root, options, context = { level: 0 }) { + console.error(JSON.stringify({ + syntax: syntaxes.reduce((acc, curr) => acc + renderSyntax(curr), ''), + syntaxes, + tokens, + s: new Error('bar').stack + }, null, 1)); + if (syntaxes == null) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], - node: atRule, - syntax: '@media', - error: 'expected media query list', - tokens: [] + node: tokens[0] ?? null, + syntax: null, + error: 'no matching syntaxes found', + tokens }; } - const result = validateAtRuleMediaQueryList(atRule.tokens, atRule); - if (result.valid == ValidationLevel.Drop) { - return result; + let token = null; + let syntax; + let result = null; + let validSyntax = false; + let matched = false; + const matches = []; + tokens = tokens.slice(); + syntaxes = syntaxes.slice(); + tokens = tokens.slice(); + if (context.cache == null) { + context.cache = new WeakMap; } - if (!('chi' in atRule)) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: atRule, - syntax: '@media', - error: 'expected at-rule body', - tokens: [] - }; + if (context.tokens == null) { + context.tokens = tokens.slice(); } - // @ts-ignore - return { - valid: ValidationLevel.Valid, - matches: [], - node: atRule, - syntax: '@media', - error: '', - tokens: [] - }; -} -function validateAtRuleMediaQueryList(tokenList, atRule) { - for (const tokens of splitTokenList(tokenList)) { - if (tokens.length == 0) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0] ?? atRule, - syntax: '@media', - error: 'unexpected token', - tokens: [] - }; + context = { ...context }; + main: while (tokens.length > 0) { + if (syntaxes.length == 0) { + break; } - let previousToken = null; - while (tokens.length > 0) { - // media-condition - if (validateMediaCondition(tokens[0])) { - previousToken = tokens[0]; - tokens.shift(); - } - // media-type - else if (validateMediaFeature(tokens[0])) { - previousToken = tokens[0]; + token = tokens[0]; + syntax = syntaxes[0]; + // @ts-ignore + context.position = context.tokens.indexOf(token); + const cached = context.cache.get(token)?.get(syntax.text) ?? null; + if (cached != null) { + if (cached.error.length > 0) { + return { ...cached, tokens, node: cached.valid == ValidationLevel.Valid ? null : token }; + } + syntaxes.shift(); + tokens.shift(); + continue; + } + if (token.typ == exports.EnumToken.DescendantCombinatorTokenType) { + tokens.shift(); + if (syntax.typ == ValidationTokenEnum.Whitespace) { + syntaxes.shift(); + } + continue; + } + else if (syntax.typ == ValidationTokenEnum.Whitespace) { + syntaxes.shift(); + if (token.typ == exports.EnumToken.WhitespaceTokenType) { tokens.shift(); } - if (tokens.length == 0) { - break; + continue; + } + else if (syntax.typ == ValidationTokenEnum.Block && exports.EnumToken.AtRuleTokenType == token.typ && ('chi' in token)) { + syntaxes.shift(); + tokens.shift(); + // @ts-ignore + matches.push(token); + continue; + } + if (syntax.isOptional) { + if (!context.cache.has(token)) { + context.cache.set(token, new Map); } - if (!consumeWhitespace(tokens)) { - if (previousToken?.typ != exports.EnumToken.ParensTokenType) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0] ?? atRule, - syntax: '@media', - error: 'expected media query list', - tokens: [] - }; - } + if (context.cache.get(token).has(syntax.text)) { + result = context.cache.get(token).get(syntax.text); + return { ...result, tokens, node: result.valid == ValidationLevel.Valid ? null : token }; } - if (![exports.EnumToken.MediaFeatureOrTokenType, exports.EnumToken.MediaFeatureAndTokenType].includes(tokens[0].typ)) { + // @ts-ignore + const { isOptional, ...c } = syntax; + // @ts-ignore + let result2; + // @ts-ignore + result2 = validateSyntax([c], tokens, root, options, context); + if (result2.valid == ValidationLevel.Valid && result2.matches.length > 0) { + tokens = result2.tokens; + // splice(tokens, result2.matches); + // tokens = result2.tokens; // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0] ?? atRule, - syntax: '@media', - error: 'expected and/or', - tokens: [] - }; + matches.push(...result2.matches); + matched = true; + result = result2; } - if (tokens.length == 1) { + else { + syntaxes.shift(); + continue; + } + syntaxes.shift(); + if (syntaxes.length == 0) { // @ts-ignore return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0] ?? atRule, - syntax: '@media', - error: 'expected media-condition', - tokens: [] + valid: ValidationLevel.Valid, + matches: result2.matches, + node: result2.node, + syntax: result2.syntax, + error: result2.error, + tokens }; } - tokens.shift(); - if (!consumeWhitespace(tokens)) { + continue; + } + if (syntax.isList) { + let index = -1; + // @ts-ignore + let { isList, ...c } = syntax; + // const c: ValidationToken = {...syntaxes, isList: false} as ValidationToken; + let result2 = null; + validSyntax = false; + do { + for (let i = index + 1; i < tokens.length; i++) { + if (tokens[i].typ == exports.EnumToken.CommaTokenType) { + index = i; + break; + } + } + if (tokens[index + 1]?.typ == exports.EnumToken.CommaTokenType) { + return { + valid: ValidationLevel.Drop, + matches, + node: tokens[0], + syntax, + error: 'unexpected token', + tokens + }; + } + if (index == -1) { + index = tokens.length; + } + if (index == 0) { + break; + } // @ts-ignore + result2 = validateSyntax([c], tokens.slice(0, index), root, options, context); + matched = result2.valid == ValidationLevel.Valid && result2.matches.length > 0; + if (matched) { + const l = tokens.length; + validSyntax = true; + // @ts-ignore + // matches.push(...result2.matches); + // splice(tokens, result2.matches); + if (tokens.length == 1 && tokens[0].typ == exports.EnumToken.CommaTokenType) { + return { + valid: ValidationLevel.Drop, + matches, + node: tokens[0], + syntax, + error: 'unexpected token', + tokens + }; + } + tokens = tokens.slice(index); + result = result2; + // @ts-ignore + matches.push(...result2.matches); + if (result.tokens.length > 0) { + if (index == -1) { + tokens = result.tokens; + } + else { + tokens = tokens.slice(index - result.tokens.length); + } + } + else if (index > 0) { + tokens = tokens.slice(index); + } + index = -1; + if (l == tokens.length) { + break; + } + } + else { + break; + } + } while (tokens.length > 0); + // if (level == 0) { + // } + if (!matched) { return { valid: ValidationLevel.Drop, - matches: [], - node: tokens[0] ?? atRule, - syntax: '@media', - error: 'expected whitespace', - tokens: [] + // @ts-ignore + matches: [...new Set(matches)], + node: token, + syntax, + error: 'unexpected token', + tokens }; } + syntaxes.shift(); + continue; } - } - // @ts-ignore - return { - valid: ValidationLevel.Valid, - matches: [], - node: atRule, - syntax: '@media', - error: '', - tokens: [] - }; -} -function validateMediaCondition(token) { - if (token.typ == exports.EnumToken.MediaFeatureNotTokenType) { - return validateMediaCondition(token.val); - } - if (token.typ != exports.EnumToken.ParensTokenType) { - return false; - } - const chi = token.chi.filter((t) => t.typ != exports.EnumToken.CommentTokenType && t.typ != exports.EnumToken.WhitespaceTokenType); - if (chi.length != 1) { - return false; - } - if (chi[0].typ == exports.EnumToken.IdenTokenType) { - return true; - } - if (chi[0].typ == exports.EnumToken.MediaFeatureNotTokenType) { - return validateMediaCondition(chi[0].val); - } - if (chi[0].typ == exports.EnumToken.MediaQueryConditionTokenType) { - return chi[0].l.typ == exports.EnumToken.IdenTokenType; - } - return false; -} -function validateMediaFeature(token) { - let val = token; - if (token.typ == exports.EnumToken.MediaFeatureOnlyTokenType || token.typ == exports.EnumToken.MediaFeatureNotTokenType) { - val = token.val; - } - return val.typ == exports.EnumToken.MediaFeatureTokenType; -} - -function validateAtRuleCounterStyle(atRule, options, root) { - // media-query-list - if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: atRule, - syntax: '@counter-style', - error: 'expected media query list', - tokens: [] - }; - } - const tokens = atRule.tokens.filter((t) => ![exports.EnumToken.WhitespaceTokenType, exports.EnumToken.CommentTokenType].includes(t.typ)); - if (tokens.length == 0) { - // @ts-ignore - return { - valid: ValidationLevel.Valid, - matches: [], - node: atRule, - syntax: '@counter-style', - error: 'expected counter style name', - tokens - }; - } - if (tokens.length > 1) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[1] ?? atRule, - syntax: '@counter-style', - error: 'unexpected token', - tokens - }; - } - if (![exports.EnumToken.IdenTokenType, exports.EnumToken.DashedIdenTokenType].includes(tokens[0].typ)) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0], - syntax: '@counter-style', - error: 'expected counter style name', - tokens - }; - } - if (!('chi' in atRule)) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: atRule, - syntax: '@counter-style', - error: 'expected counter style body', - tokens - }; - } - // @ts-ignore - return { - valid: ValidationLevel.Valid, - matches: [], - node: atRule, - syntax: '@counter-style', - error: '', - tokens - }; -} - -function validateAtRulePage(atRule, options, root) { - // media-query-list - if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { - // @ts-ignore - return { - valid: ValidationLevel.Valid, - matches: [], - node: null, - syntax: '@page', - error: '', - tokens: [] - }; - } - // page-selector-list - for (const tokens of splitTokenList(atRule.tokens)) { - if (tokens.length == 0) { + if (syntax.isRepeatable) { // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0] ?? atRule, - syntax: '@page', - error: 'unexpected token', - tokens: [] - }; + let { isRepeatable, ...c } = syntax; + let result2 = null; + validSyntax = false; + let l = tokens.length; + let tok = null; + do { + // @ts-ignore + result2 = validateSyntax([c], tokens, root, options, context); + if (result2.matches.length == 0 && result2.error.length > 0) { + syntaxes.shift(); + break main; + } + if (result2.valid == ValidationLevel.Valid) { + tokens = result2.tokens; + // @ts-ignore + matches.push(...result2.matches); + result = result2; + if (l == tokens.length) { + if (tok == tokens[0]) { + break; + } + if (result2.matches.length == 0 && tokens.length > 0) { + tokens = result2.tokens; + tok = tokens[0]; + continue; + } + break; + } + if (matches.length == 0) { + tokens = result2.tokens; + } + l = tokens.length; + continue; + } + break; + } while (result2.valid == ValidationLevel.Valid && tokens.length > 0); + // if (lastResult != null) { + // + // splice(tokens, lastResult.matches); + // // tokens = lastResult.tokens; + // } + syntaxes.shift(); + continue; } - // + | * - // ident pseudo-page* | pseudo-page+ - if (tokens[0].typ == exports.EnumToken.IdenTokenType) { - tokens.shift(); - if (tokens.length == 0) { - continue; - } - // @ts-ignore - if (tokens[0].typ != exports.EnumToken.WhitespaceTokenType) { + // at least one match + if (syntax.isRepeatableGroup) { + validSyntax = false; + let count = 0; + let l = tokens.length; + let result2 = null; + do { + // @ts-ignore + const { isRepeatableGroup, ...c } = syntax; + // @ts-ignore + result2 = validateSyntax([c], tokens, root, options, context); + if (result2.valid == ValidationLevel.Drop || result2.matches.length == 0) { + if (count > 0) { + syntaxes.shift(); + // if (result2.matches.length == 0) { + tokens = result2.tokens; + // break main; + if (syntaxes.length == 0) { + return result2; + } + break main; + } + return result2; + } + if (result2.valid == ValidationLevel.Valid && result2.matches.length > 0) { + count++; + // lastResult = result; + validSyntax = true; + tokens = result2.tokens; + // splice(tokens, result2.matches); + // tokens = result2.tokens; + // @ts-ignore + matches.push(...result2.matches); + result = result2; + if (l == tokens.length) { + break; + } + l = tokens.length; + } + else { + break; + } + } while (tokens.length > 0 && result.valid == ValidationLevel.Valid); + // if (lastResult != null) { + // + // splice(tokens, lastResult.matches); + // // tokens = lastResult.tokens; + // } + // at least one match is expected + if (!validSyntax /* || result.matches.length == 0 */) { // @ts-ignore return { valid: ValidationLevel.Drop, - matches: [], - node: tokens[0] ?? atRule, - syntax: '@page', + node: token, + tokens, + syntax, error: 'unexpected token', - tokens: [] + matches: [] }; } + syntaxes.shift(); + continue; } - while (tokens.length > 0) { - if (tokens[0].typ == exports.EnumToken.PseudoPageTokenType) { - tokens.shift(); - if (tokens.length == 0) { - continue; + if (syntax.atLeastOnce) { + const { atLeastOnce, ...c } = syntax; + result = validateSyntax([c], tokens, root, options, context); + if (result.valid == ValidationLevel.Drop) { + return result; + } + splice(tokens, result.matches); + // tokens = result.tokens; + // @ts-ignore + matches.push(...result.matches); + let l = tokens.length; + let r = validateSyntax([c], tokens, root, options, context); + while (r.valid == ValidationLevel.Valid) { + splice(tokens, r.matches); + // tokens = r.tokens; + r = validateSyntax([c], tokens, root, options, context); + if (l == tokens.length) { + break; } - // @ts-ignore - if (tokens[0].typ != exports.EnumToken.WhitespaceTokenType) { + if (r.valid == ValidationLevel.Valid && r.matches.length > 0) { // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0] ?? atRule, - syntax: '@page', - error: 'unexpected token', - tokens: [] - }; + matches.push(...result.matches); } + l = tokens.length; } + syntaxes.shift(); + continue; } - } - // @ts-ignore - return { - valid: ValidationLevel.Valid, - matches: [], - node: atRule, - syntax: '@page', - error: '', - tokens: [] - }; -} - -function validateAtRulePageMarginBox(atRule, options, root) { - if (Array.isArray(atRule.tokens) && atRule.tokens.length > 0) { - // @ts-ignore - return { - valid: ValidationLevel.Valid, - matches: [], - node: null, - syntax: '@' + atRule.nam, - error: '', - tokens: [] - }; - } - if (!('chi' in atRule)) { // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: atRule, - syntax: '@' + atRule.nam, - error: 'expected margin-box body', - tokens: [] - }; - } - for (const token of atRule.chi) { - if (![exports.EnumToken.DeclarationNodeType, exports.EnumToken.CommentNodeType, exports.EnumToken.WhitespaceTokenType].includes(token.typ)) { + if (syntax.occurence != null) { // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: token, - syntax: 'declaration-list', - error: 'expected margin-box body', - tokens: [] - }; - } - } - // @ts-ignore - return { - valid: ValidationLevel.Valid, - matches: [], - node: null, - syntax: '@' + atRule.nam, - error: '', - tokens: [] - }; -} - -const config$2 = getSyntaxConfig(); -function consumeToken(tokens) { - tokens.shift(); -} -function consumeSyntax(syntaxes) { - syntaxes.shift(); -} -function splice(tokens, matches) { - if (matches.length == 0) { - return tokens; - } - // @ts-ignore - const index = tokens.indexOf(matches.at(-1)); - if (index > -1) { - tokens.splice(0, index + 1); - } - return tokens; -} -function validateSyntax(syntaxes, tokens, root, options, context = { level: 0 }) { - if (syntaxes == null) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0] ?? null, - syntax: null, - error: 'no matching syntaxes found', - tokens - }; - } - let token = null; - let syntax; - let result = null; - let validSyntax = false; - let matched = false; - const matches = []; - tokens = tokens.slice(); - syntaxes = syntaxes.slice(); - tokens = tokens.slice(); - if (context.cache == null) { - context.cache = new WeakMap; - } - if (context.tokens == null) { - context.tokens = tokens.slice(); - } - context = { ...context }; - main: while (tokens.length > 0) { - if (syntaxes.length == 0) { - break; - } - token = tokens[0]; - syntax = syntaxes[0]; - // @ts-ignore - context.position = context.tokens.indexOf(token); - const cached = context.cache.get(token)?.get(syntax.text) ?? null; - if (cached != null) { - if (cached.error.length > 0) { - return { ...cached, tokens, node: cached.valid == ValidationLevel.Valid ? null : token }; + const { occurence, ...c } = syntax; + // && syntaxes.occurence.max != null + // consume all tokens + let match = 1; + // @ts-ignore + result = validateSyntax([c], tokens, root, options, context); + if (result.valid == ValidationLevel.Drop) { + return result; } - syntaxes.shift(); - tokens.shift(); - continue; - } - if (token.typ == exports.EnumToken.DescendantCombinatorTokenType) { - tokens.shift(); - if (syntax.typ == ValidationTokenEnum.Whitespace) { + if (result.matches.length == 0) { syntaxes.shift(); + continue; + } + // splice(tokens, result.matches); + // tokens = result.tokens; + // @ts-ignore + matches.push(...result.matches); + matched = true; + tokens = result.tokens; + while (occurence.max == null || match < occurence.max) { + // trim whitespace + if (tokens[0]?.typ == exports.EnumToken.WhitespaceTokenType) { + tokens.shift(); + } + // @ts-ignore + let r = validateSyntax([c], tokens, root, options, context); + if (r.valid != ValidationLevel.Valid || r.matches.length == 0) { + break; + } + result = r; + // splice(tokens, r.matches); + // tokens = r.tokens; + // @ts-ignore + matches.push(...result.matches); + match++; + tokens = r.tokens; + result = r; + if (tokens.length == 0 || (occurence.max != null && match >= occurence.max)) { + break; + } + // @ts-ignore + // r = validateSyntax([c], tokens, root, options, context); } + syntaxes.shift(); continue; } - else if (syntax.typ == ValidationTokenEnum.Whitespace) { - syntaxes.shift(); + // @ts-ignore + if (syntax.typ == ValidationTokenEnum.Whitespace) { if (token.typ == exports.EnumToken.WhitespaceTokenType) { tokens.shift(); } + syntaxes.shift(); continue; } - else if (syntax.typ == ValidationTokenEnum.Block && exports.EnumToken.AtRuleTokenType == token.typ && ('chi' in token)) { - syntaxes.shift(); - tokens.shift(); + // @ts-ignore + if (token.val != null && specialValues.includes(token.val)) { + matched = true; + result = { + valid: ValidationLevel.Valid, + matches: [token], + node: null, + syntax, + error: '', + tokens + }; // @ts-ignore - matches.push(token); - continue; + matches.push(...result.matches); } - if (syntax.isOptional) { - if (!context.cache.has(token)) { - context.cache.set(token, new Map); - } - if (context.cache.get(token).has(syntax.text)) { - result = context.cache.get(token).get(syntax.text); - return { ...result, tokens, node: result.valid == ValidationLevel.Valid ? null : token }; + else { + result = doValidateSyntax(syntax, token, tokens, root, options, context); + matched = result.valid == ValidationLevel.Valid && result.matches.length > 0; + if (matched) { + // splice(tokens, result.matches); + tokens = result.tokens; + // @ts-ignore + matches.push(...result.matches); } + } + if (result.valid == ValidationLevel.Drop) { // @ts-ignore - const { isOptional, ...c } = syntax; + return { ...result, matches, tokens, node: result.valid == ValidationLevel.Valid ? null : token }; + } + consumeSyntax(syntaxes); + if (tokens.length == 0) { + return result; + } + } + if (result?.valid == ValidationLevel.Valid) { + // splice(tokens, result.matches); + tokens = result.tokens; + // @ts-ignore + matches.push(...result.matches); + } + if ( /* result == null && */tokens.length == 0 && syntaxes.length > 0) { + validSyntax = isOptionalSyntax(syntaxes); + } + if (result == null) { + result = { + valid: validSyntax ? ValidationLevel.Valid : ValidationLevel.Drop, + matches, + node: validSyntax ? null : tokens[0] ?? null, // @ts-ignore - let result2; + syntax, + error: validSyntax ? '' : 'unexpected token', + tokens + }; + } + if (token != null) { + if (!context.cache.has(token)) { + context.cache.set(token, new Map); + } + context.cache.get(token).set(syntax.text, result); + } + if (result != null) { + // @ts-ignore + return { ...result, matches: [...(new Set(matches))] }; + } + return result; +} +function isOptionalSyntax(syntaxes) { + return syntaxes.length > 0 && syntaxes.every(t => t.typ == ValidationTokenEnum.Whitespace || t.isOptional || t.isRepeatable || (t.typ == ValidationTokenEnum.PropertyType && isOptionalSyntax(getParsedSyntax("syntaxes" /* ValidationSyntaxGroupEnum.Syntaxes */, t.val) ?? getParsedSyntax("declarations" /* ValidationSyntaxGroupEnum.Declarations */, t.val) ?? []))); +} +function doValidateSyntax(syntax, token, tokens, root, options, context) { + let valid = false; + let result; + let children; + let queue; + let matches; + let child; + let astNodes = new Set; + if (token.typ == exports.EnumToken.NestingSelectorTokenType && syntax.typ == 2) { + valid = root != null && 'relative-selector' == syntax.val; + return { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], + node: valid ? null : token, + syntax, + error: valid ? '' : 'unexpected token', + tokens + }; + } + switch (syntax.typ) { + case ValidationTokenEnum.Comma: + valid = token.typ === exports.EnumToken.CommaTokenType; // @ts-ignore - result2 = validateSyntax([c], tokens, root, options, context); - if (result2.valid == ValidationLevel.Valid && result2.matches.length > 0) { - tokens = result2.tokens; - // splice(tokens, result2.matches); - // tokens = result2.tokens; + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], + node: valid ? null : token, + syntax, + error: valid ? '' : 'unexpected token', + tokens + }; + break; + case ValidationTokenEnum.AtRule: + if (token.typ != exports.EnumToken.AtRuleNodeType) { // @ts-ignore - matches.push(...result2.matches); - matched = true; - result = result2; - } - else { - syntaxes.shift(); - continue; + return { + valid: ValidationLevel.Drop, + matches: [], + node: token, + syntax, + error: 'expecting at-rule', + tokens + }; } - syntaxes.shift(); - if (syntaxes.length == 0) { + if (token.nam != syntax.val) { // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: token, + syntax, + error: `expecting '@${syntax.val}' but found '@${token.nam}'`, + tokens + }; + } + if (root == null) { return { valid: ValidationLevel.Valid, - matches: result2.matches, - node: result2.node, - syntax: result2.syntax, - error: result2.error, + matches: [token], + node: null, + syntax, + error: '', + tokens + }; + } + if (root.typ != exports.EnumToken.AtRuleNodeType) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: token, + syntax, + error: 'not allowed here', + tokens + }; + } + if (!('chi' in token)) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: token, + syntax, + error: '@at-rule must have children', tokens }; } - continue; - } - if (syntax.isList) { - let index = -1; // @ts-ignore - let { isList, ...c } = syntax; - // const c: ValidationToken = {...syntaxes, isList: false} as ValidationToken; - let result2 = null; - validSyntax = false; - do { - for (let i = index + 1; i < tokens.length; i++) { - if (tokens[i].typ == exports.EnumToken.CommaTokenType) { - index = i; - break; - } - } - if (tokens[index + 1]?.typ == exports.EnumToken.CommaTokenType) { + result = { + valid: ValidationLevel.Valid, + matches: [token], + node: null, + syntax, + error: '', + tokens + }; + break; + case ValidationTokenEnum.AtRuleDefinition: + if (token.typ != exports.EnumToken.AtRuleNodeType) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: token, + syntax, + error: 'expecting at-rule', + tokens + }; + } + if ('chi' in syntax && !('chi' in token)) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: token, + syntax, + error: '@at-rule must have children', + tokens + }; + } + if ('chi' in token && !('chi' in token)) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: token, + syntax, + error: 'children not allowed here', + tokens + }; + } + const s = getParsedSyntax("atRules" /* ValidationSyntaxGroupEnum.AtRules */, '@' + token.nam); + if ('prelude' in syntax) { + if (!('tokens' in token)) { + // @ts-ignore return { valid: ValidationLevel.Drop, - matches, - node: tokens[0], + matches: [], + node: token, syntax, - error: 'unexpected token', + error: 'expected at-rule prelude', tokens }; } - if (index == -1) { - index = tokens.length; - } - if (index == 0) { - break; + result = validateSyntax(s[0].prelude, token.tokens, root, options, { + ...context, + tokens: null, + level: context.level + 1 + }); + if (result.valid == ValidationLevel.Drop) { + return result; } - // @ts-ignore - result2 = validateSyntax([c], tokens.slice(0, index), root, options, context); - matched = result2.valid == ValidationLevel.Valid && result2.matches.length > 0; - if (matched) { - const l = tokens.length; - validSyntax = true; - // @ts-ignore - // matches.push(...result2.matches); - // splice(tokens, result2.matches); - if (tokens.length == 1 && tokens[0].typ == exports.EnumToken.CommaTokenType) { - return { - valid: ValidationLevel.Drop, - matches, - node: tokens[0], - syntax, - error: 'unexpected token', - tokens - }; - } - tokens = tokens.slice(index); - result = result2; + } + const hasBody = 'chi' in s[0]; + if ('chi' in token) { + if (!hasBody) { // @ts-ignore - matches.push(...result2.matches); - if (result.tokens.length > 0) { - if (index == -1) { - tokens = result.tokens; - } - else { - tokens = tokens.slice(index - result.tokens.length); - } - } - else if (index > 0) { - tokens = tokens.slice(index); - } - index = -1; - if (l == tokens.length) { - break; - } - } - else { - break; + return { + valid: ValidationLevel.Drop, + matches: [], + node: token, + syntax, + error: 'unexpected at-rule body', + tokens + }; } - } while (tokens.length > 0); - // if (level == 0) { - // } - if (!matched) { + } + else if (hasBody) { + // @ts-ignore return { valid: ValidationLevel.Drop, - // @ts-ignore - matches: [...new Set(matches)], + matches: [], node: token, syntax, - error: 'unexpected token', + error: 'expecting at-rule body', tokens }; } - syntaxes.shift(); - continue; - } - if (syntax.isRepeatable) { + break; + case ValidationTokenEnum.DeclarationType: // @ts-ignore - let { isRepeatable, ...c } = syntax; - let result2 = null; - validSyntax = false; - let l = tokens.length; - let tok = null; - do { - // @ts-ignore - result2 = validateSyntax([c], tokens, root, options, context); - if (result2.matches.length == 0 && result2.error.length > 0) { - syntaxes.shift(); - break main; + result = validateSyntax(getParsedSyntax("declarations" /* ValidationSyntaxGroupEnum.Declarations */, syntax.val), [token], root, options, context); + break; + case ValidationTokenEnum.Keyword: + valid = (token.typ == exports.EnumToken.IdenTokenType && token.val.localeCompare(syntax.val, 'en', { sensitivity: 'base' }) == 0) || + (token.typ == exports.EnumToken.ColorTokenType && token.kin == 'lit' && syntax.val.localeCompare(token.val, 'en', { sensitivity: 'base' }) == 0); + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], + node: valid ? null : token, + syntax, + error: valid ? '' : 'unexpected token', + tokens + }; + break; + case ValidationTokenEnum.SemiColon: + valid = root == null || [exports.EnumToken.RuleNodeType, exports.EnumToken.AtRuleNodeType, exports.EnumToken.StyleSheetNodeType].includes(root.typ); + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], + node: valid ? null : token, + syntax, + error: valid ? '' : 'unexpected token', + tokens + }; + break; + case ValidationTokenEnum.Separator: + valid = token.typ == exports.EnumToken.LiteralTokenType && token.val != '/'; + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], + node: valid ? null : token, + syntax, + error: valid ? '' : 'unexpected token', + tokens + }; + break; + case ValidationTokenEnum.PropertyType: + // + if ('image' == syntax.val) { + valid = token.typ == exports.EnumToken.UrlFunctionTokenType || token.typ == exports.EnumToken.ImageFunctionTokenType; + if (!valid) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: token, + syntax, + error: 'unexpected ', + tokens + }; } - if (result2.valid == ValidationLevel.Valid) { - tokens = result2.tokens; - // @ts-ignore - matches.push(...result2.matches); - result = result2; - if (l == tokens.length) { - if (tok == tokens[0]) { - break; - } - if (result2.matches.length == 0 && tokens.length > 0) { - tokens = result2.tokens; - tok = tokens[0]; - continue; - } - break; - } - if (matches.length == 0) { - tokens = result2.tokens; + result = validateImage(token); + } + else if (['media-feature', 'mf-plain'].includes(syntax.val)) { + valid = token.typ == exports.EnumToken.DeclarationNodeType; + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], + node: valid ? null : token, + syntax, + error: valid ? '' : 'unexpected token', + tokens + }; + } + else if (syntax.val == 'pseudo-page') { + valid = token.typ == exports.EnumToken.PseudoClassTokenType && [':left', ':right', ':first', ':blank'].includes(token.val); + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], + node: valid ? null : token, + syntax, + error: valid ? '' : 'unexpected token', + tokens + }; + } + else if (syntax.val == 'page-body') { + if (token.typ == exports.EnumToken.DeclarationNodeType) { + valid = true; + // @ts-ignore + result = { + valid: ValidationLevel.Valid, + matches: [token], + node: null, + syntax, + error: '', + tokens + }; + while (tokens.length > 0 && [exports.EnumToken.DeclarationNodeType].includes(tokens[0].typ)) { + // @ts-ignore + result.matches.push(tokens.shift()); } - l = tokens.length; - continue; } - break; - } while (result2.valid == ValidationLevel.Valid && tokens.length > 0); - // if (lastResult != null) { - // - // splice(tokens, lastResult.matches); - // // tokens = lastResult.tokens; - // } - syntaxes.shift(); - continue; - } - // at least one match - if (syntax.isRepeatableGroup) { - validSyntax = false; - let count = 0; - let l = tokens.length; - let result2 = null; - do { - // @ts-ignore - const { isRepeatableGroup, ...c } = syntax; - // @ts-ignore - result2 = validateSyntax([c], tokens, root, options, context); - if (result2.valid == ValidationLevel.Drop || result2.matches.length == 0) { - if (count > 0) { - syntaxes.shift(); - // if (result2.matches.length == 0) { - tokens = result2.tokens; - // break main; - if (syntaxes.length == 0) { - return result2; - } - break main; - } - return result2; + else if (token.typ == exports.EnumToken.AtRuleNodeType) { + result = validateSyntax(getParsedSyntax("syntaxes" /* ValidationSyntaxGroupEnum.Syntaxes */, 'page-margin-box-type'), [token], root, options, context); } - if (result2.valid == ValidationLevel.Valid && result2.matches.length > 0) { - count++; - // lastResult = result; - validSyntax = true; - tokens = result2.tokens; - // splice(tokens, result2.matches); - // tokens = result2.tokens; - // @ts-ignore - matches.push(...result2.matches); - result = result2; - if (l == tokens.length) { - break; + } + else if (syntax.val == 'group-rule-body') { + valid = [exports.EnumToken.AtRuleNodeType, exports.EnumToken.RuleNodeType].includes(token.typ); + if (!valid && token.typ == exports.EnumToken.DeclarationNodeType && root?.typ == exports.EnumToken.AtRuleNodeType && root.nam == 'media') { + // allowed only if nested rule + let parent = root; + while (parent != null) { + if (parent.typ == exports.EnumToken.RuleNodeType) { + valid = true; + break; + } + // @ts-ignore + parent = parent.parent; } - l = tokens.length; } - else { - break; + // @ts-ignore + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: [], + node: valid ? null : token, + syntax, + error: valid ? '' : 'token is not allowed as a child', + tokens + }; + if (!valid) { + return result; } - } while (tokens.length > 0 && result.valid == ValidationLevel.Valid); - // if (lastResult != null) { + } // - // splice(tokens, lastResult.matches); - // // tokens = lastResult.tokens; - // } - // at least one match is expected - if (!validSyntax /* || result.matches.length == 0 */) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, + else if ('type-selector' == syntax.val) { + valid = (token.typ == exports.EnumToken.UniversalSelectorTokenType) || + token.typ == exports.EnumToken.IdenTokenType || (token.typ == exports.EnumToken.NameSpaceAttributeTokenType && + (token.l == null || token.l.typ == exports.EnumToken.IdenTokenType || + (token.l.typ == exports.EnumToken.LiteralTokenType && token.l.val == '*')) && + token.r.typ == exports.EnumToken.IdenTokenType); + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], node: token, - tokens, syntax, - error: 'unexpected token', - matches: [] + error: valid ? '' : 'unexpected token', + tokens }; } - syntaxes.shift(); - continue; - } - if (syntax.atLeastOnce) { - const { atLeastOnce, ...c } = syntax; - result = validateSyntax([c], tokens, root, options, context); - if (result.valid == ValidationLevel.Drop) { - return result; + else if ('wq-name' == syntax.val) { + valid = token.typ == exports.EnumToken.IdenTokenType || (token.typ == exports.EnumToken.NameSpaceAttributeTokenType && + (token.l == null || token.l.typ == exports.EnumToken.IdenTokenType || (token.l.typ == exports.EnumToken.LiteralTokenType && token.l.val == '*')) && + token.r.typ == exports.EnumToken.IdenTokenType); + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], + node: token, + syntax, + error: valid ? '' : 'unexpected token', + tokens + }; } - splice(tokens, result.matches); - // tokens = result.tokens; - // @ts-ignore - matches.push(...result.matches); - let l = tokens.length; - let r = validateSyntax([c], tokens, root, options, context); - while (r.valid == ValidationLevel.Valid) { - splice(tokens, r.matches); - // tokens = r.tokens; - r = validateSyntax([c], tokens, root, options, context); - if (l == tokens.length) { - break; + else if (exports.EnumToken.UniversalSelectorTokenType == token.typ && 'subclass-selector' == syntax.val) { + valid = true; + result = { + valid: ValidationLevel.Valid, + matches: [token], + node: null, + syntax, + error: '', + tokens + }; + } + else if ('attribute-selector' == syntax.val) { + valid = token.typ == exports.EnumToken.AttrTokenType && token.chi.length > 0; + if (valid) { + const children = token.chi.filter(t => t.typ != exports.EnumToken.WhitespaceTokenType && t.typ != exports.EnumToken.CommaTokenType); + valid = children.length == 1 && [ + exports.EnumToken.IdenTokenType, + exports.EnumToken.NameSpaceAttributeTokenType, + exports.EnumToken.MatchExpressionTokenType + ].includes(children[0].typ); + if (valid && children[0].typ == exports.EnumToken.MatchExpressionTokenType) { + const t = children[0]; + valid = [ + exports.EnumToken.IdenTokenType, + exports.EnumToken.NameSpaceAttributeTokenType + ].includes(t.l.typ) && + (t.op == null || ([ + exports.EnumToken.DelimTokenType, exports.EnumToken.DashMatchTokenType, + exports.EnumToken.StartMatchTokenType, exports.EnumToken.ContainMatchTokenType, + exports.EnumToken.EndMatchTokenType, exports.EnumToken.IncludeMatchTokenType + ].includes(t.op.typ) && + t.r != null && + [ + exports.EnumToken.StringTokenType, + exports.EnumToken.IdenTokenType + ].includes(t.r.typ))); + if (valid && t.attr != null) { + const s = getParsedSyntax("syntaxes" /* ValidationSyntaxGroupEnum.Syntaxes */, 'attr-modifier')[0]; + valid = s.chi.some((l) => l.some((r) => r.val == t.attr)); + } + } } - if (r.valid == ValidationLevel.Valid && r.matches.length > 0) { + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], + node: valid ? null : token, + syntax, + error: valid ? '' : 'unexpected token', + tokens + }; + if (!valid) { + return result; + } + } + else if ('combinator' == syntax.val) { + valid = [ + exports.EnumToken.DescendantCombinatorTokenType, + exports.EnumToken.SubsequentSiblingCombinatorTokenType, + exports.EnumToken.NextSiblingCombinatorTokenType, + exports.EnumToken.ChildCombinatorTokenType, + exports.EnumToken.ColumnCombinatorTokenType + ].includes(token.typ); + if (valid) { // @ts-ignore - matches.push(...result.matches); + const position = context.tokens.indexOf(token); + if (root == null) { + valid = position > 0 && context.tokens[position - 1]?.typ != exports.EnumToken.CommaTokenType; + } + } + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], + node: valid ? null : token, + syntax, + error: valid ? '' : 'unexpected token', + tokens + }; + if (!valid) { + return result; } - l = tokens.length; } - syntaxes.shift(); - continue; - } - // @ts-ignore - if (syntax.occurence != null) { - // @ts-ignore - const { occurence, ...c } = syntax; - // && syntaxes.occurence.max != null - // consume all tokens - let match = 1; - // @ts-ignore - result = validateSyntax([c], tokens, root, options, context); - if (result.valid == ValidationLevel.Drop) { - return result; - } - if (result.matches.length == 0) { - syntaxes.shift(); - continue; - } - // splice(tokens, result.matches); - // tokens = result.tokens; - // @ts-ignore - matches.push(...result.matches); - matched = true; - tokens = result.tokens; - while (occurence.max == null || match < occurence.max) { - // trim whitespace - if (tokens[0]?.typ == exports.EnumToken.WhitespaceTokenType) { - tokens.shift(); - } - // @ts-ignore - let r = validateSyntax([c], tokens, root, options, context); - if (r.valid != ValidationLevel.Valid || r.matches.length == 0) { - break; - } - result = r; - // splice(tokens, r.matches); - // tokens = r.tokens; - // @ts-ignore - matches.push(...result.matches); - match++; - tokens = r.tokens; - result = r; - if (tokens.length == 0 || (occurence.max != null && match >= occurence.max)) { - break; - } - // @ts-ignore - // r = validateSyntax([c], tokens, root, options, context); - } - syntaxes.shift(); - continue; - } - // @ts-ignore - if (syntax.typ == ValidationTokenEnum.Whitespace) { - if (token.typ == exports.EnumToken.WhitespaceTokenType) { - tokens.shift(); - } - syntaxes.shift(); - continue; - } - // @ts-ignore - if (token.val != null && specialValues.includes(token.val)) { - matched = true; - result = { - valid: ValidationLevel.Valid, - matches: [token], - node: null, - syntax, - error: '', - tokens - }; - // @ts-ignore - matches.push(...result.matches); - } - else { - result = doValidateSyntax(syntax, token, tokens, root, options, context); - matched = result.valid == ValidationLevel.Valid && result.matches.length > 0; - if (matched) { - // splice(tokens, result.matches); - tokens = result.tokens; - // @ts-ignore - matches.push(...result.matches); - } - } - if (result.valid == ValidationLevel.Drop) { - // @ts-ignore - return { ...result, matches, tokens, node: result.valid == ValidationLevel.Valid ? null : token }; - } - consumeSyntax(syntaxes); - if (tokens.length == 0) { - return result; - } - } - if (result?.valid == ValidationLevel.Valid) { - // splice(tokens, result.matches); - tokens = result.tokens; - // @ts-ignore - matches.push(...result.matches); - } - if ( /* result == null && */tokens.length == 0 && syntaxes.length > 0) { - validSyntax = isOptionalSyntax(syntaxes); - } - if (result == null) { - result = { - valid: validSyntax ? ValidationLevel.Valid : ValidationLevel.Drop, - matches, - node: validSyntax ? null : tokens[0] ?? null, - // @ts-ignore - syntax, - error: validSyntax ? '' : 'unexpected token', - tokens - }; - } - if (token != null) { - if (!context.cache.has(token)) { - context.cache.set(token, new Map); - } - context.cache.get(token).set(syntax.text, result); - } - if (result != null) { - // @ts-ignore - return { ...result, matches: [...(new Set(matches))] }; - } - return result; -} -function isOptionalSyntax(syntaxes) { - return syntaxes.every(t => t.typ == ValidationTokenEnum.Whitespace || t.isOptional || t.isRepeatable || (t.typ == ValidationTokenEnum.PropertyType && isOptionalSyntax(getParsedSyntax("syntaxes" /* ValidationSyntaxGroupEnum.Syntaxes */, t.val) ?? getParsedSyntax("declarations" /* ValidationSyntaxGroupEnum.Declarations */, t.val)))); -} -function doValidateSyntax(syntax, token, tokens, root, options, context) { - let valid = false; - let result; - let children; - let queue; - let matches; - let child; - let astNodes = new Set; - if (token.typ == exports.EnumToken.NestingSelectorTokenType && syntax.typ == 2) { - valid = root != null && 'relative-selector' == syntax.val; - return { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - } - switch (syntax.typ) { - case ValidationTokenEnum.Comma: - valid = token.typ === exports.EnumToken.CommaTokenType; - // @ts-ignore - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - break; - case ValidationTokenEnum.AtRule: - if (token.typ != exports.EnumToken.AtRuleNodeType) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: token, + else if ('ident-token' == syntax.val) { + valid = token.typ == exports.EnumToken.IdenTokenType; + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], + node: valid ? null : token, syntax, - error: 'expecting at-rule', + error: valid ? '' : 'unexpected token', tokens }; } - if (token.nam != syntax.val) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: token, + else if ('hex-color' == syntax.val) { + valid = token.typ == exports.EnumToken.ColorTokenType && token.kin == 'hex'; + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], + node: valid ? null : token, syntax, - error: `expecting '@${syntax.val}' but found '@${token.nam}'`, + error: valid ? '' : 'unexpected token', tokens }; } - if (root == null) { - return { - valid: ValidationLevel.Valid, - matches: [token], - node: null, + else if ('resolution' == syntax.val) { + valid = token.typ == exports.EnumToken.ResolutionTokenType; + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], + node: valid ? null : token, syntax, - error: '', + error: valid ? '' : 'unexpected token', tokens }; } - if (root.typ != exports.EnumToken.AtRuleNodeType) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: token, + else if ('angle' == syntax.val) { + valid = token.typ == exports.EnumToken.AngleTokenType || (token.typ == exports.EnumToken.NumberTokenType && token.val == '0'); + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], + node: valid ? null : token, syntax, - error: 'not allowed here', + error: valid ? '' : 'unexpected token', tokens }; } - if (!('chi' in token)) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: token, + else if ('time' == syntax.val) { + valid = token.typ == exports.EnumToken.TimingFunctionTokenType; + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], + node: valid ? null : token, syntax, - error: '@at-rule must have children', + error: valid ? '' : 'unexpected token', tokens }; } - // @ts-ignore - result = { - valid: ValidationLevel.Valid, - matches: [token], - node: null, - syntax, - error: '', - tokens - }; - break; - case ValidationTokenEnum.AtRuleDefinition: - if (token.typ != exports.EnumToken.AtRuleNodeType) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: token, + else if ('ident' == syntax.val) { + valid = token.typ == exports.EnumToken.IdenTokenType; + result = { + valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, + matches: valid ? [token] : [], + node: valid ? null : token, syntax, - error: 'expecting at-rule', + error: valid ? '' : 'unexpected token', tokens }; } - if ('chi' in syntax && !('chi' in token)) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: token, - syntax, - error: '@at-rule must have children', - tokens - }; - } - if ('chi' in token && !('chi' in token)) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: token, - syntax, - error: 'children not allowed here', - tokens - }; - } - const s = getParsedSyntax("atRules" /* ValidationSyntaxGroupEnum.AtRules */, '@' + token.nam); - if ('prelude' in syntax) { - if (!('tokens' in token)) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: token, - syntax, - error: 'expected at-rule prelude', - tokens - }; - } - result = validateSyntax(s[0].prelude, token.tokens, root, options, { - ...context, - tokens: null, - level: context.level + 1 - }); - if (result.valid == ValidationLevel.Drop) { - return result; - } - } - const hasBody = 'chi' in s[0]; - if ('chi' in token) { - if (!hasBody) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: token, - syntax, - error: 'unexpected at-rule body', - tokens - }; - } - } - else if (hasBody) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: token, - syntax, - error: 'expecting at-rule body', - tokens - }; - } - break; - case ValidationTokenEnum.DeclarationType: - // @ts-ignore - result = validateSyntax(getParsedSyntax("declarations" /* ValidationSyntaxGroupEnum.Declarations */, syntax.val), [token], root, options, context); - break; - case ValidationTokenEnum.Keyword: - valid = (token.typ == exports.EnumToken.IdenTokenType && token.val.localeCompare(syntax.val, 'en', { sensitivity: 'base' }) == 0) || - (token.typ == exports.EnumToken.ColorTokenType && token.kin == 'lit' && syntax.val.localeCompare(token.val, 'en', { sensitivity: 'base' }) == 0); - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - break; - case ValidationTokenEnum.SemiColon: - valid = root == null || [exports.EnumToken.RuleNodeType, exports.EnumToken.AtRuleNodeType, exports.EnumToken.StyleSheetNodeType].includes(root.typ); - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - break; - case ValidationTokenEnum.Separator: - valid = token.typ == exports.EnumToken.LiteralTokenType && token.val != '/'; - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - break; - case ValidationTokenEnum.PropertyType: - // - if (['media-feature', 'mf-plain'].includes(syntax.val)) { - valid = token.typ == exports.EnumToken.DeclarationNodeType; + else if (['id-selector', 'hash-token'].includes(syntax.val)) { + valid = token.typ == exports.EnumToken.HashTokenType; result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], @@ -12929,8 +13412,14 @@ function doValidateSyntax(syntax, token, tokens, root, options, context) { tokens }; } - else if (syntax.val == 'pseudo-page') { - valid = token.typ == exports.EnumToken.PseudoClassTokenType && [':left', ':right', ':first', ':blank'].includes(token.val); + else if (['integer', 'number'].includes(syntax.val)) { + // valid = token.typ == EnumToken.NumberTokenType; + valid = token.typ == exports.EnumToken.NumberTokenType && ('integer' != syntax.val || Number.isInteger(+token.val)); + if (valid && 'range' in syntax) { + const value = Number(token.val); + const range = syntax.range; + valid = value >= range[0] && (range[1] == null || value <= range[1]); + } result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], @@ -12940,113 +13429,31 @@ function doValidateSyntax(syntax, token, tokens, root, options, context) { tokens }; } - else if (syntax.val == 'page-body') { - if (token.typ == exports.EnumToken.DeclarationNodeType) { - valid = true; - // @ts-ignore - result = { - valid: ValidationLevel.Valid, - matches: [token], - node: null, - syntax, - error: '', - tokens - }; - while (tokens.length > 0 && [exports.EnumToken.DeclarationNodeType].includes(tokens[0].typ)) { - // @ts-ignore - result.matches.push(tokens.shift()); - } - } - else if (token.typ == exports.EnumToken.AtRuleNodeType) { - result = validateSyntax(getParsedSyntax("syntaxes" /* ValidationSyntaxGroupEnum.Syntaxes */, 'page-margin-box-type'), [token], root, options, context); - } - } - else if (syntax.val == 'group-rule-body') { - valid = [exports.EnumToken.AtRuleNodeType, exports.EnumToken.RuleNodeType].includes(token.typ); + else if ('length' == syntax.val) { + valid = isLength(token) || (token.typ == exports.EnumToken.NumberTokenType && token.val == '0'); // @ts-ignore - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'token is not allowed as a child', - tokens - }; - if (!valid) { - return result; - } - } - // - else if ('type-selector' == syntax.val) { - valid = (token.typ == exports.EnumToken.UniversalSelectorTokenType) || - token.typ == exports.EnumToken.IdenTokenType || (token.typ == exports.EnumToken.NameSpaceAttributeTokenType && - (token.l == null || token.l.typ == exports.EnumToken.IdenTokenType || - (token.l.typ == exports.EnumToken.LiteralTokenType && token.l.val == '*')) && - token.r.typ == exports.EnumToken.IdenTokenType); result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], - node: token, + node: valid ? null : token, syntax, error: valid ? '' : 'unexpected token', tokens }; } - else if ('wq-name' == syntax.val) { - valid = token.typ == exports.EnumToken.IdenTokenType || (token.typ == exports.EnumToken.NameSpaceAttributeTokenType && - (token.l == null || token.l.typ == exports.EnumToken.IdenTokenType || (token.l.typ == exports.EnumToken.LiteralTokenType && token.l.val == '*')) && - token.r.typ == exports.EnumToken.IdenTokenType); + else if ('percentage' == syntax.val) { + valid = token.typ == exports.EnumToken.PercentageTokenType || (token.typ == exports.EnumToken.NumberTokenType && token.val == '0'); result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], - node: token, + node: valid ? null : token, syntax, error: valid ? '' : 'unexpected token', tokens }; } - else if (exports.EnumToken.UniversalSelectorTokenType == token.typ && 'subclass-selector' == syntax.val) { - valid = true; - result = { - valid: ValidationLevel.Valid, - matches: [token], - node: null, - syntax, - error: '', - tokens - }; - } - else if ('attribute-selector' == syntax.val) { - valid = token.typ == exports.EnumToken.AttrTokenType && token.chi.length > 0; - if (valid) { - const children = token.chi.filter(t => t.typ != exports.EnumToken.WhitespaceTokenType && t.typ != exports.EnumToken.CommaTokenType); - valid = children.length == 1 && [ - exports.EnumToken.IdenTokenType, - exports.EnumToken.NameSpaceAttributeTokenType, - exports.EnumToken.MatchExpressionTokenType - ].includes(children[0].typ); - if (valid && children[0].typ == exports.EnumToken.MatchExpressionTokenType) { - const t = children[0]; - valid = [ - exports.EnumToken.IdenTokenType, - exports.EnumToken.NameSpaceAttributeTokenType - ].includes(t.l.typ) && - (t.op == null || ([ - exports.EnumToken.DelimTokenType, exports.EnumToken.DashMatchTokenType, - exports.EnumToken.StartMatchTokenType, exports.EnumToken.ContainMatchTokenType, - exports.EnumToken.EndMatchTokenType, exports.EnumToken.IncludeMatchTokenType - ].includes(t.op.typ) && - t.r != null && - [ - exports.EnumToken.StringTokenType, - exports.EnumToken.IdenTokenType - ].includes(t.r.typ))); - if (valid && t.attr != null) { - const s = getParsedSyntax("syntaxes" /* ValidationSyntaxGroupEnum.Syntaxes */, 'attr-modifier')[0]; - valid = s.chi.some((l) => l.some((r) => r.val == t.attr)); - } - } - } + else if ('dashed-ident' == syntax.val) { + valid = token.typ == exports.EnumToken.DashedIdenTokenType; result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], @@ -13055,25 +13462,9 @@ function doValidateSyntax(syntax, token, tokens, root, options, context) { error: valid ? '' : 'unexpected token', tokens }; - if (!valid) { - return result; - } } - else if ('combinator' == syntax.val) { - valid = [ - exports.EnumToken.DescendantCombinatorTokenType, - exports.EnumToken.SubsequentSiblingCombinatorTokenType, - exports.EnumToken.NextSiblingCombinatorTokenType, - exports.EnumToken.ChildCombinatorTokenType, - exports.EnumToken.ColumnCombinatorTokenType - ].includes(token.typ); - if (valid) { - // @ts-ignore - const position = context.tokens.indexOf(token); - if (root == null) { - valid = position > 0 && context.tokens[position - 1]?.typ != exports.EnumToken.CommaTokenType; - } - } + else if ('custom-ident' == syntax.val) { + valid = token.typ == exports.EnumToken.DashedIdenTokenType || token.typ == exports.EnumToken.IdenTokenType; result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], @@ -13082,12 +13473,9 @@ function doValidateSyntax(syntax, token, tokens, root, options, context) { error: valid ? '' : 'unexpected token', tokens }; - if (!valid) { - return result; - } } - else if ('ident-token' == syntax.val) { - valid = token.typ == exports.EnumToken.IdenTokenType; + else if ('custom-property-name' == syntax.val) { + valid = token.typ == exports.EnumToken.DashedIdenTokenType; result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], @@ -13097,8 +13485,8 @@ function doValidateSyntax(syntax, token, tokens, root, options, context) { tokens }; } - else if ('hex-color' == syntax.val) { - valid = token.typ == exports.EnumToken.ColorTokenType && token.kin == 'hex'; + else if ('string' == syntax.val) { + valid = token.typ == exports.EnumToken.StringTokenType; result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], @@ -13108,8 +13496,8 @@ function doValidateSyntax(syntax, token, tokens, root, options, context) { tokens }; } - else if ('resolution' == syntax.val) { - valid = token.typ == exports.EnumToken.ResolutionTokenType; + else if ('declaration-value' == syntax.val) { + valid = token.typ != exports.EnumToken.LiteralTokenType; result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], @@ -13119,147 +13507,8 @@ function doValidateSyntax(syntax, token, tokens, root, options, context) { tokens }; } - else if ('angle' == syntax.val) { - valid = token.typ == exports.EnumToken.AngleTokenType || (token.typ == exports.EnumToken.NumberTokenType && token.val == '0'); - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - } - else if ('time' == syntax.val) { - valid = token.typ == exports.EnumToken.TimingFunctionTokenType; - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - } - else if ('ident' == syntax.val) { - valid = token.typ == exports.EnumToken.IdenTokenType; - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - } - else if (['id-selector', 'hash-token'].includes(syntax.val)) { - valid = token.typ == exports.EnumToken.HashTokenType; - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - } - else if (['integer', 'number'].includes(syntax.val)) { - // valid = token.typ == EnumToken.NumberTokenType; - valid = token.typ == exports.EnumToken.NumberTokenType && ('integer' != syntax.val || Number.isInteger(+token.val)); - if (valid && 'range' in syntax) { - const value = Number(token.val); - const range = syntax.range; - valid = value >= range[0] && (range[1] == null || value <= range[1]); - } - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - } - else if ('length' == syntax.val) { - valid = isLength(token) || (token.typ == exports.EnumToken.NumberTokenType && token.val == '0'); - // @ts-ignore - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - } - else if ('percentage' == syntax.val) { - valid = token.typ == exports.EnumToken.PercentageTokenType || (token.typ == exports.EnumToken.NumberTokenType && token.val == '0'); - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - } - else if ('dashed-ident' == syntax.val) { - valid = token.typ == exports.EnumToken.DashedIdenTokenType; - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - } - else if ('custom-ident' == syntax.val) { - valid = token.typ == exports.EnumToken.DashedIdenTokenType || token.typ == exports.EnumToken.IdenTokenType; - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - } - else if ('custom-property-name' == syntax.val) { - valid = token.typ == exports.EnumToken.DashedIdenTokenType; - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - } - else if ('string' == syntax.val) { - valid = token.typ == exports.EnumToken.StringTokenType; - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - } - else if ('declaration-value' == syntax.val) { - valid = token.typ != exports.EnumToken.LiteralTokenType; - result = { - valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, - matches: valid ? [token] : [], - node: valid ? null : token, - syntax, - error: valid ? '' : 'unexpected token', - tokens - }; - } - else if ('url' == syntax.val) { - valid = token.typ == exports.EnumToken.UrlFunctionTokenType || token.typ == exports.EnumToken.StringTokenType; + else if ('url' == syntax.val) { + valid = token.typ == exports.EnumToken.UrlFunctionTokenType || token.typ == exports.EnumToken.StringTokenType; result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, matches: valid ? [token] : [], @@ -13630,12 +13879,570 @@ function doValidateSyntax(syntax, token, tokens, root, options, context) { error: valid ? '' : 'invalid token', tokens }; - break; - default: - throw new Error('not implemented: ' + JSON.stringify({ syntax, token, tokens }, null, 1)); + break; + case ValidationTokenEnum.DeclarationDefinitionToken: + if (token.typ != exports.EnumToken.DeclarationNodeType || token.nam != syntax.nam) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: token, + syntax, + error: '', + tokens + }; + } + return validateSyntax([syntax.val], token.val, root, options, context); + default: + throw new Error('not implemented: ' + JSON.stringify({ syntax, token, tokens }, null, 1)); + } + // @ts-ignore + return result; +} + +function validateURL(token) { + if (token.typ == exports.EnumToken.UrlTokenTokenType) { + // @ts-ignore + return { + valid: ValidationLevel.Valid, + matches: [], + node: token, + // @ts-ignore + syntax: 'url()', + error: '', + tokens: [] + }; + } + if (token.typ != exports.EnumToken.UrlFunctionTokenType) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: token, + // @ts-ignore + syntax: 'url()', + error: 'expected url()', + tokens: [] + }; + } + const children = token.chi.slice(); + consumeWhitespace(children); + if (children.length == 0 || ![exports.EnumToken.UrlTokenTokenType, exports.EnumToken.StringTokenType, exports.EnumToken.HashTokenType].includes(children[0].typ)) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: children[0] ?? token, + // @ts-ignore + syntax: 'url()', + error: 'expected url-token', + tokens: children + }; + } + children.shift(); + consumeWhitespace(children); + if (children.length > 0) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: children[0] ?? token, + // @ts-ignore + syntax: 'url()', + error: 'unexpected token', + tokens: children + }; + } + // @ts-ignore + return { + valid: ValidationLevel.Valid, + matches: [], + node: token, + // @ts-ignore + syntax: 'url()', + error: '', + tokens: [] + }; +} + +function validateImage(token) { + if (token.typ == exports.EnumToken.UrlFunctionTokenType) { + return validateURL(token); + } + if (token.typ == exports.EnumToken.ImageFunctionTokenType) { + return validateSyntax(getParsedSyntax("syntaxes" /* ValidationSyntaxGroupEnum.Syntaxes */, token.val + '()'), token.chi); + } + return { + valid: ValidationLevel.Drop, + matches: [], + node: token, + syntax: 'image()', + error: 'expected or ', + tokens: [] + }; +} + +const validateSelectorList = validateComplexSelectorList; + +function validateSelector(selector, options, root) { + if (root == null) { + return validateSelectorList(selector, root, options); + } + // @ts-ignore + if (root.typ == exports.EnumToken.AtRuleNodeType && root.nam.match(/^(-[a-z]+-)?keyframes$/)) { + return validateKeyframeBlockList(selector, root); + } + let isNested = root.typ == exports.EnumToken.RuleNodeType ? 1 : 0; + let currentRoot = root.parent; + while (currentRoot != null && isNested == 0) { + if (currentRoot.typ == exports.EnumToken.RuleNodeType) { + isNested++; + if (isNested > 0) { + // @ts-ignore + return validateRelativeSelectorList(selector, root, { ...(options ?? {}), nestedSelector: true }); + } + } + currentRoot = currentRoot.parent; + } + const nestedSelector = isNested > 0; + // @ts-ignore + return nestedSelector ? validateRelativeSelectorList(selector, root, { ...(options ?? {}), nestedSelector }) : validateSelectorList(selector, root, { ...(options ?? {}), nestedSelector }); +} + +function validateAtRuleMedia(atRule, options, root) { + // media-query-list + if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Valid, + matches: [], + node: null, + syntax: null, + error: '', + tokens: [] + }; + } + let result = null; + const slice = atRule.tokens.slice(); + consumeWhitespace(slice); + if (slice.length == 0) { + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@media', + error: '', + tokens: [] + }; + } + result = validateAtRuleMediaQueryList(atRule.tokens, atRule); + if (result.valid == ValidationLevel.Drop) { + return result; + } + if (!('chi' in atRule)) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@media', + error: 'expected at-rule body', + tokens: [] + }; + } + // @ts-ignore + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@media', + error: '', + tokens: [] + }; +} +function validateAtRuleMediaQueryList(tokenList, atRule) { + const split = splitTokenList(tokenList); + const matched = []; + let result = null; + let previousToken; + let mediaFeatureType; + for (let i = 0; i < split.length; i++) { + const tokens = split[i].slice(); + const match = []; + result = null; + mediaFeatureType = null; + previousToken = null; + if (tokens.length == 0) { + // @ts-ignore + result = { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0] ?? atRule, + syntax: '@media', + error: 'unexpected token', + tokens: [] + }; + continue; + } + while (tokens.length > 0) { + previousToken = tokens[0]; + // media-condition | media-type | custom-media + if (!(validateMediaCondition(tokens[0], atRule) || validateMediaFeature(tokens[0]) || validateCustomMediaCondition(tokens[0], atRule))) { + if (tokens[0].typ == exports.EnumToken.ParensTokenType) { + result = validateAtRuleMediaQueryList(tokens[0].chi, atRule); + } + else { + result = { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0] ?? atRule, + syntax: '@media', + error: 'expecting media feature or media condition', + tokens: [] + }; + } + if (result.valid == ValidationLevel.Drop) { + break; + } + result = null; + } + match.push(tokens.shift()); + if (tokens.length == 0) { + break; + } + if (!consumeWhitespace(tokens)) { + if (previousToken?.typ != exports.EnumToken.ParensTokenType) { + // @ts-ignore + result = { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0] ?? atRule, + syntax: '@media', + error: 'expected media query list', + tokens: [] + }; + break; + } + } + else if (![exports.EnumToken.MediaFeatureOrTokenType, exports.EnumToken.MediaFeatureAndTokenType].includes(tokens[0].typ)) { + // @ts-ignore + result = { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0] ?? atRule, + syntax: '@media', + error: 'expected and/or', + tokens: [] + }; + break; + } + if (mediaFeatureType == null) { + mediaFeatureType = tokens[0]; + } + if (mediaFeatureType.typ != tokens[0].typ) { + // @ts-ignore + result = { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0] ?? atRule, + syntax: '@media', + error: 'mixing and/or not allowed at the same level', + tokens: [] + }; + break; + } + match.push({ typ: exports.EnumToken.WhitespaceTokenType }, tokens.shift()); + consumeWhitespace(tokens); + if (tokens.length == 0) { + // @ts-ignore + result = { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0] ?? atRule, + syntax: '@media', + error: 'expected media-condition', + tokens: [] + }; + break; + } + match.push({ typ: exports.EnumToken.WhitespaceTokenType }); + } + if (result == null && match.length > 0) { + matched.push(match); + } + } + if (result != null) { + return result; + } + if (matched.length == 0) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@media', + error: 'expected media query list', + tokens: [] + }; + } + tokenList.length = 0; + let hasAll = false; + for (let i = 0; i < matched.length; i++) { + if (tokenList.length > 0) { + tokenList.push({ typ: exports.EnumToken.CommaTokenType }); + } + if (matched[i].length == 1 && matched.length > 1 && matched[i][0].typ == exports.EnumToken.MediaFeatureTokenType && matched[i][0].val == 'all') { + hasAll = true; + continue; + } + tokenList.push(...matched[i]); + } + if (hasAll && tokenList.length == 0) { + tokenList.push({ typ: exports.EnumToken.MediaFeatureTokenType, val: 'all' }); + } + // @ts-ignore + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@media', + error: '', + tokens: [] + }; +} +function validateCustomMediaCondition(token, atRule) { + if (token.typ == exports.EnumToken.MediaFeatureNotTokenType) { + return validateMediaCondition(token.val, atRule); + } + if (token.typ != exports.EnumToken.ParensTokenType) { + return false; + } + const chi = token.chi.filter((t) => t.typ != exports.EnumToken.CommentTokenType && t.typ != exports.EnumToken.WhitespaceTokenType); + if (chi.length != 1) { + return false; + } + return chi[0].typ == exports.EnumToken.DashedIdenTokenType; +} +function validateMediaCondition(token, atRule) { + if (token.typ == exports.EnumToken.MediaFeatureNotTokenType) { + return validateMediaCondition(token.val, atRule); + } + if (token.typ != exports.EnumToken.ParensTokenType && !(['when', 'else'].includes(atRule.nam) && token.typ == exports.EnumToken.FunctionTokenType && ['media', 'supports'].includes(token.val))) { + return false; + } + const chi = token.chi.filter((t) => t.typ != exports.EnumToken.CommentTokenType && t.typ != exports.EnumToken.WhitespaceTokenType); + if (chi.length != 1) { + return false; + } + if (chi[0].typ == exports.EnumToken.IdenTokenType) { + return true; + } + if (chi[0].typ == exports.EnumToken.MediaFeatureNotTokenType) { + return validateMediaCondition(chi[0].val, atRule); + } + if (chi[0].typ == exports.EnumToken.MediaQueryConditionTokenType) { + return chi[0].l.typ == exports.EnumToken.IdenTokenType; + } + return false; +} +function validateMediaFeature(token) { + let val = token; + if (token.typ == exports.EnumToken.MediaFeatureOnlyTokenType || token.typ == exports.EnumToken.MediaFeatureNotTokenType) { + val = token.val; + } + return val.typ == exports.EnumToken.MediaFeatureTokenType; +} + +function validateAtRuleCounterStyle(atRule, options, root) { + // media-query-list + if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@counter-style', + error: 'expected counter style name', + tokens: [] + }; + } + const tokens = atRule.tokens.filter((t) => ![exports.EnumToken.WhitespaceTokenType, exports.EnumToken.CommentTokenType].includes(t.typ)); + if (tokens.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@counter-style', + error: 'expected counter style name', + tokens + }; + } + if (tokens.length > 1) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[1] ?? atRule, + syntax: '@counter-style', + error: 'unexpected token', + tokens + }; + } + if (![exports.EnumToken.IdenTokenType, exports.EnumToken.DashedIdenTokenType].includes(tokens[0].typ)) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: '@counter-style', + error: 'expected counter style name', + tokens + }; + } + if (!('chi' in atRule)) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@counter-style', + error: 'expected counter style body', + tokens + }; + } + // @ts-ignore + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@counter-style', + error: '', + tokens + }; +} + +function validateAtRulePage(atRule, options, root) { + // media-query-list + if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Valid, + matches: [], + node: null, + syntax: '@page', + error: '', + tokens: [] + }; + } + // page-selector-list + for (const tokens of splitTokenList(atRule.tokens)) { + if (tokens.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0] ?? atRule, + syntax: '@page', + error: 'unexpected token', + tokens: [] + }; + } + // + | * + // ident pseudo-page* | pseudo-page+ + if (tokens[0].typ == exports.EnumToken.IdenTokenType) { + tokens.shift(); + if (tokens.length == 0) { + continue; + } + // @ts-ignore + if (tokens[0].typ != exports.EnumToken.WhitespaceTokenType) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0] ?? atRule, + syntax: '@page', + error: 'unexpected token', + tokens: [] + }; + } + } + while (tokens.length > 0) { + if (tokens[0].typ == exports.EnumToken.PseudoPageTokenType) { + tokens.shift(); + if (tokens.length == 0) { + continue; + } + // @ts-ignore + if (tokens[0].typ != exports.EnumToken.WhitespaceTokenType) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0] ?? atRule, + syntax: '@page', + error: 'unexpected token', + tokens: [] + }; + } + } + } + } + // @ts-ignore + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@page', + error: '', + tokens: [] + }; +} + +function validateAtRulePageMarginBox(atRule, options, root) { + if (Array.isArray(atRule.tokens) && atRule.tokens.length > 0) { + // @ts-ignore + return { + valid: ValidationLevel.Valid, + matches: [], + node: null, + syntax: '@' + atRule.nam, + error: '', + tokens: [] + }; + } + if (!('chi' in atRule)) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected margin-box body', + tokens: [] + }; + } + for (const token of atRule.chi) { + if (![exports.EnumToken.DeclarationNodeType, exports.EnumToken.CommentNodeType, exports.EnumToken.WhitespaceTokenType].includes(token.typ)) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: token, + syntax: 'declaration-list', + error: 'expected margin-box body', + tokens: [] + }; + } } // @ts-ignore - return result; + return { + valid: ValidationLevel.Valid, + matches: [], + node: null, + syntax: '@' + atRule.nam, + error: '', + tokens: [] + }; } function validateAtRuleSupports(atRule, options, root) { @@ -13646,7 +14453,7 @@ function validateAtRuleSupports(atRule, options, root) { valid: ValidationLevel.Drop, matches: [], node: atRule, - syntax: '@supports', + syntax: '@' + atRule.nam, error: 'expected supports query list', tokens: [] }; @@ -13664,7 +14471,7 @@ function validateAtRuleSupports(atRule, options, root) { valid: ValidationLevel.Drop, matches: [], node: atRule, - syntax: '@supports', + syntax: '@' + atRule.nam, error: 'expected at-rule body', tokens: [] }; @@ -13674,7 +14481,7 @@ function validateAtRuleSupports(atRule, options, root) { valid: ValidationLevel.Valid, matches: [], node: atRule, - syntax: '@supports', + syntax: '@' + atRule.nam, error: '', tokens: [] }; @@ -13687,7 +14494,7 @@ function validateAtRuleSupportsConditions(atRule, tokenList) { valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? atRule, - syntax: '@supports', + syntax: '@' + atRule.nam, error: 'unexpected token', tokens: [] }; @@ -13721,7 +14528,7 @@ function validateAtRuleSupportsConditions(atRule, tokenList) { valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? previousToken ?? atRule, - syntax: '@supports', + syntax: '@' + atRule.nam, error: 'expected whitespace', tokens: [] }; @@ -13733,7 +14540,7 @@ function validateAtRuleSupportsConditions(atRule, tokenList) { valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? atRule, - syntax: '@supports', + syntax: '@' + atRule.nam, error: 'expected and/or', tokens: [] }; @@ -13744,7 +14551,7 @@ function validateAtRuleSupportsConditions(atRule, tokenList) { valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? atRule, - syntax: '@supports', + syntax: '@' + atRule.nam, error: 'expected supports-condition', tokens: [] }; @@ -13756,7 +14563,7 @@ function validateAtRuleSupportsConditions(atRule, tokenList) { valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? atRule, - syntax: '@supports', + syntax: '@' + atRule.nam, error: 'expected whitespace', tokens: [] }; @@ -13769,13 +14576,13 @@ function validateSupportCondition(atRule, token) { if (token.typ == exports.EnumToken.MediaFeatureNotTokenType) { return validateSupportCondition(atRule, token.val); } - if (token.typ != exports.EnumToken.ParensTokenType) { + if (token.typ != exports.EnumToken.ParensTokenType && !(['when', 'else'].includes(atRule.nam) && token.typ == exports.EnumToken.FunctionTokenType && ['supports', 'font-format', 'font-tech'].includes(token.val))) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], node: token, - syntax: '@supports', + syntax: '@' + atRule.nam, error: 'expected supports condition-in-parens', tokens: [] }; @@ -13790,7 +14597,7 @@ function validateSupportCondition(atRule, token) { valid: ValidationLevel.Valid, matches: [], node: null, - syntax: '@supports', + syntax: '@' + atRule.nam, error: '', tokens: [] }; @@ -14046,69 +14853,306 @@ function validateAtRuleImport(atRule, options, root) { return { valid: ValidationLevel.Drop, matches: [], - node: tokens[0], - syntax: '@' + atRule.nam, - error: 'expecting whitespace', + node: tokens[0], + syntax: '@' + atRule.nam, + error: 'expecting whitespace', + tokens + }; + } + } + } + if (tokens.length > 0) { + return validateAtRuleMediaQueryList(tokens, atRule); + } + // @ts-ignore + return { + valid: ValidationLevel.Valid, + matches: [], + node: null, + syntax: '@' + atRule.nam, + error: '', + tokens: [] + }; +} + +function validateAtRuleLayer(atRule, options, root) { + // media-query-list + if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@layer', + error: '', + tokens: [] + }; + } + return validateLayerName(atRule.tokens); +} + +function validateAtRuleFontFeatureValues(atRule, options, root) { + if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: null, + syntax: '@' + atRule.nam, + error: 'expected at-rule prelude', + tokens: [] + }; + } + const result = validateFamilyName(atRule.tokens, atRule); + if (result.valid == ValidationLevel.Drop) { + return result; + } + if (!('chi' in atRule)) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected at-rule body', + tokens: [] + }; + } + // @ts-ignore + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: '', + tokens: [] + }; +} + +function validateAtRuleNamespace(atRule, options, root) { + if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@namespace', + error: 'expected at-rule prelude', + tokens: [] + }; + } + if ('chi' in atRule) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@namespace', + error: 'unexpected at-rule body', + tokens: [] + }; + } + const tokens = atRule.tokens.slice(); + consumeWhitespace(tokens); + if (tokens[0].typ == exports.EnumToken.IdenTokenType) { + tokens.shift(); + consumeWhitespace(tokens); + } + if (tokens.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@namespace', + error: 'expected string or url()', + tokens + }; + } + if (tokens[0].typ != exports.EnumToken.StringTokenType) { + const result = validateURL(tokens[0]); + if (result.valid != ValidationLevel.Valid) { + return result; + } + tokens.shift(); + consumeWhitespace(tokens); + } + else { + tokens.shift(); + consumeWhitespace(tokens); + } + if (tokens.length > 0) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: '@namespace', + error: 'unexpected token', + tokens + }; + } + // @ts-ignore + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@namespace', + error: '', + tokens + }; +} + +function validateAtRuleDocument(atRule, options, root) { + if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@document', + error: 'expecting at-rule prelude', + tokens: [] + }; + } + const tokens = atRule.tokens.slice(); + let result = null; + consumeWhitespace(tokens); + if (tokens.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@document', + error: 'expecting at-rule prelude', + tokens + }; + } + if (tokens[0].typ == exports.EnumToken.CommaTokenType) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: '@document', + error: 'unexpected token', + tokens + }; + } + while (tokens.length > 0) { + if (tokens[0].typ == exports.EnumToken.CommentTokenType) { + tokens.shift(); + consumeWhitespace(tokens); + } + result = validateURL(tokens[0]); + if (result.valid == ValidationLevel.Valid) { + tokens.shift(); + consumeWhitespace(tokens); + continue; + } + if (tokens[0].typ == exports.EnumToken.FunctionTokenType) { + if (!['url-prefix', 'domain', 'media-document', 'regexp'].some((t) => t.localeCompare(tokens[0].val, undefined, { sensitivity: 'base' }) == 0)) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: '@document', + error: 'unexpected token', + tokens + }; + } + const children = tokens[0].chi.slice(); + consumeWhitespace(children); + if (children.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: '@document', + error: 'expecting string argument', + tokens + }; + } + if (children[0].typ == exports.EnumToken.StringTokenType) { + children.shift(); + consumeWhitespace(children); + } + if (children.length > 0) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: children[0], + syntax: '@document', + error: 'unexpected token', tokens }; } + tokens.shift(); + consumeWhitespace(tokens); } } - if (tokens.length > 0) { - return validateAtRuleMediaQueryList(tokens, atRule); - } // @ts-ignore return { valid: ValidationLevel.Valid, matches: [], - node: null, - syntax: '@' + atRule.nam, + node: atRule, + syntax: '@document', error: '', - tokens: [] + tokens }; } -function validateAtRuleLayer(atRule, options, root) { - // media-query-list +function validateAtRuleKeyframes(atRule, options, root) { if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { // @ts-ignore return { - valid: ValidationLevel.Valid, + valid: ValidationLevel.Drop, matches: [], node: atRule, - syntax: '@layer', - error: '', + syntax: '@document', + error: 'expecting at-rule prelude', tokens: [] }; } - return validateLayerName(atRule.tokens); -} - -function validateAtRuleFontFeatureValues(atRule, options, root) { - if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { + const tokens = atRule.tokens.slice(); + consumeWhitespace(tokens); + if (tokens.length == 0) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], - node: null, - syntax: '@' + atRule.nam, - error: 'expected at-rule prelude', - tokens: [] + node: atRule, + syntax: '@keyframes', + error: 'expecting at-rule prelude', + tokens }; } - const result = validateFamilyName(atRule.tokens, atRule); - if (result.valid == ValidationLevel.Drop) { - return result; - } - if (!('chi' in atRule)) { + if (![exports.EnumToken.StringTokenType, exports.EnumToken.IdenTokenType].includes(tokens[0].typ)) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], node: atRule, - syntax: '@' + atRule.nam, - error: 'expected at-rule body', - tokens: [] + syntax: '@keyframes', + error: 'expecting ident or string token', + tokens + }; + } + tokens.shift(); + consumeWhitespace(tokens); + if (tokens.length > 0) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: '@keyframes', + error: 'unexpected token', + tokens }; } // @ts-ignore @@ -14116,314 +15160,562 @@ function validateAtRuleFontFeatureValues(atRule, options, root) { valid: ValidationLevel.Valid, matches: [], node: atRule, - syntax: '@' + atRule.nam, + syntax: '@keyframes', error: '', - tokens: [] + tokens }; } -function validateURL(token) { - if (token.typ == exports.EnumToken.UrlTokenTokenType) { +function validateAtRuleWhen(atRule, options, root) { + const slice = Array.isArray(atRule.tokens) ? atRule.tokens.slice() : []; + consumeWhitespace(slice); + if (slice.length == 0) { // @ts-ignore return { valid: ValidationLevel.Valid, matches: [], - node: token, - // @ts-ignore - syntax: 'url()', + node: atRule, + syntax: '@when', error: '', tokens: [] }; } - if (token.typ != exports.EnumToken.UrlFunctionTokenType) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: token, - // @ts-ignore - syntax: 'url()', - error: 'expected url()', - tokens: [] - }; - } - const children = token.chi.slice(); - consumeWhitespace(children); - if (children.length == 0 || ![exports.EnumToken.UrlTokenTokenType, exports.EnumToken.StringTokenType, exports.EnumToken.HashTokenType].includes(children[0].typ)) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: children[0] ?? token, - // @ts-ignore - syntax: 'url()', - error: 'expected url-token', - tokens: children - }; + const result = validateAtRuleWhenQueryList(atRule.tokens, atRule); + if (result.valid == ValidationLevel.Drop) { + return result; } - children.shift(); - consumeWhitespace(children); - if (children.length > 0) { + if (!('chi' in atRule)) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], - node: children[0] ?? token, - // @ts-ignore - syntax: 'url()', - error: 'unexpected token', - tokens: children + node: atRule, + syntax: '@when', + error: 'expected at-rule body', + tokens: [] }; } - // @ts-ignore return { valid: ValidationLevel.Valid, matches: [], - node: token, - // @ts-ignore - syntax: 'url()', + node: atRule, + syntax: '@when', error: '', - tokens: [] + tokens: result.tokens }; } - -function validateAtRuleNamespace(atRule, options, root) { - if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: atRule, - syntax: '@namespace', - error: 'expected at-rule prelude', - tokens: [] - }; - } - if ('chi' in atRule) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: atRule, - syntax: '@namespace', - error: 'unexpected at-rule body', - tokens: [] - }; +// media() = media( [ | | ] ) +// supports() = supports( ) +function validateAtRuleWhenQueryList(tokenList, atRule) { + const matched = []; + let result = null; + for (const split of splitTokenList(tokenList)) { + const match = []; + result = null; + consumeWhitespace(split); + if (split.length == 0) { + continue; + } + while (split.length > 0) { + if (split[0].typ != exports.EnumToken.FunctionTokenType || !['media', 'supports', 'font-tech', 'font-format'].includes(split[0].val)) { + result = { + valid: ValidationLevel.Drop, + matches: [], + node: split[0] ?? atRule, + syntax: '@when', + error: 'unexpected token', + tokens: [] + }; + break; + } + const chi = split[0].chi.slice(); + consumeWhitespace(chi); + if (split[0].val == 'media') { + // result = valida + if (chi.length != 1 || !(validateMediaFeature(chi[0]) || validateMediaCondition(split[0], atRule))) { + result = { + valid: ValidationLevel.Drop, + matches: [], + node: split[0] ?? atRule, + syntax: 'media( [ | | ] )', + error: 'unexpected token', + tokens: [] + }; + break; + } + } + else if (['supports', 'font-tech', 'font-format'].includes(split[0].val)) { + // result = valida + if (!validateSupportCondition(atRule, split[0])) { + result = { + valid: ValidationLevel.Drop, + matches: [], + node: split[0] ?? atRule, + syntax: 'media( [ | | ] )', + error: 'unexpected token', + tokens: [] + }; + break; + } + } + if (match.length > 0) { + match.push({ typ: exports.EnumToken.WhitespaceTokenType }); + } + match.push(split.shift()); + consumeWhitespace(split); + if (split.length == 0) { + break; + } + if (![exports.EnumToken.MediaFeatureAndTokenType, exports.EnumToken.MediaFeatureOrTokenType].includes(split[0].typ)) { + result = { + valid: ValidationLevel.Drop, + matches: [], + node: split[0] ?? atRule, + syntax: '@when', + error: 'expecting and/or media-condition', + tokens: [] + }; + break; + } + if (match.length > 0) { + match.push({ typ: exports.EnumToken.WhitespaceTokenType }); + } + match.push(split.shift()); + consumeWhitespace(split); + if (split.length == 0) { + result = { + valid: ValidationLevel.Drop, + matches: [], + node: split[0] ?? atRule, + syntax: '@when', + error: 'expecting media-condition', + tokens: [] + }; + break; + } + } + if (result == null && match.length > 0) { + matched.push(match); + } } - const tokens = atRule.tokens.slice(); - consumeWhitespace(tokens); - if (tokens[0].typ == exports.EnumToken.IdenTokenType) { - tokens.shift(); - consumeWhitespace(tokens); + if (result != null) { + return result; } - if (tokens.length == 0) { - // @ts-ignore + if (matched.length == 0) { return { valid: ValidationLevel.Drop, matches: [], - node: atRule, - syntax: '@namespace', - error: 'expected string or url()', - tokens + // @ts-ignore + node: result?.node ?? atRule, + syntax: '@when', + error: 'invalid at-rule body', + tokens: [] }; } - if (tokens[0].typ != exports.EnumToken.StringTokenType) { - const result = validateURL(tokens[0]); - if (result.valid != ValidationLevel.Valid) { - return result; + tokenList.length = 0; + for (const match of matched) { + if (tokenList.length > 0) { + tokenList.push({ + typ: exports.EnumToken.CommaTokenType + }); } - tokens.shift(); - consumeWhitespace(tokens); - } - else { - tokens.shift(); - consumeWhitespace(tokens); - } - if (tokens.length > 0) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0], - syntax: '@namespace', - error: 'unexpected token', - tokens - }; + tokenList.push(...match); } - // @ts-ignore return { valid: ValidationLevel.Valid, matches: [], node: atRule, - syntax: '@namespace', + syntax: '@when', error: '', - tokens + tokens: tokenList }; } -function validateAtRuleDocument(atRule, options, root) { +const validateAtRuleElse = validateAtRuleWhen; + +const validateContainerScrollStateFeature = validateContainerSizeFeature; +function validateAtRuleContainer(atRule, options, root) { + // media-query-list if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], node: atRule, - syntax: '@document', - error: 'expecting at-rule prelude', + syntax: '@' + atRule.nam, + error: 'expected supports query list', tokens: [] }; } - const tokens = atRule.tokens.slice(); - let result = null; - consumeWhitespace(tokens); - if (tokens.length == 0) { + const result = validateAtRuleContainerQueryList(atRule.tokens, atRule); + if (result.valid == ValidationLevel.Drop) { + return result; + } + if (!('chi' in atRule)) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], node: atRule, - syntax: '@document', - error: 'expecting at-rule prelude', - tokens + syntax: '@' + atRule.nam, + error: 'expected at-rule body', + tokens: [] }; } - if (tokens[0].typ == exports.EnumToken.CommaTokenType) { + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: '', + tokens: [] + }; +} +function validateAtRuleContainerQueryList(tokens, atRule) { + if (tokens.length == 0) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], - node: tokens[0], - syntax: '@document', - error: 'unexpected token', + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query list', tokens }; } - while (tokens.length > 0) { - if (tokens[0].typ == exports.EnumToken.CommentTokenType) { - tokens.shift(); - consumeWhitespace(tokens); - } - result = validateURL(tokens[0]); - if (result.valid == ValidationLevel.Valid) { - tokens.shift(); - consumeWhitespace(tokens); - continue; + let result = null; + let tokenType = null; + for (const queries of splitTokenList(tokens)) { + consumeWhitespace(queries); + if (queries.length == 0) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query list', + tokens + }; } - if (tokens[0].typ == exports.EnumToken.FunctionTokenType) { - if (!['url-prefix', 'domain', 'media-document', 'regexp'].some((t) => t.localeCompare(tokens[0].val, undefined, { sensitivity: 'base' }) == 0)) { - // @ts-ignore + result = null; + const match = []; + let token = null; + tokenType = null; + while (queries.length > 0) { + if (queries.length == 0) { return { valid: ValidationLevel.Drop, matches: [], - node: tokens[0], - syntax: '@document', - error: 'unexpected token', + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query list', tokens }; } - const children = tokens[0].chi.slice(); - consumeWhitespace(children); - if (children.length == 0) { - // @ts-ignore + if (queries[0].typ == exports.EnumToken.IdenTokenType) { + match.push(queries.shift()); + consumeWhitespace(queries); + } + if (queries.length == 0) { + break; + } + token = queries[0]; + if (token.typ == exports.EnumToken.MediaFeatureNotTokenType) { + token = token.val; + } + if (token.typ != exports.EnumToken.ParensTokenType && (token.typ != exports.EnumToken.FunctionTokenType || !['scroll-state', 'style'].includes(token.val))) { return { valid: ValidationLevel.Drop, matches: [], - node: tokens[0], - syntax: '@document', - error: 'expecting string argument', + node: queries[0], + syntax: '@' + atRule.nam, + error: 'expected container query-in-parens', tokens }; } - if (children[0].typ == exports.EnumToken.StringTokenType) { - children.shift(); - consumeWhitespace(children); + if (token.typ == exports.EnumToken.ParensTokenType) { + result = validateContainerSizeFeature(token.chi, atRule); } - if (children.length > 0) { - // @ts-ignore + else if (token.val == 'scroll-state') { + result = validateContainerScrollStateFeature(token.chi, atRule); + } + else { + result = validateContainerStyleFeature(token.chi, atRule); + } + if (result.valid == ValidationLevel.Drop) { + return result; + } + queries.shift(); + consumeWhitespace(queries); + if (queries.length == 0) { + break; + } + token = queries[0]; + if (token.typ != exports.EnumToken.MediaFeatureAndTokenType && token.typ != exports.EnumToken.MediaFeatureOrTokenType) { return { valid: ValidationLevel.Drop, matches: [], - node: children[0], - syntax: '@document', - error: 'unexpected token', + node: queries[0], + syntax: '@' + atRule.nam, + error: 'expecting and/or container query token', + tokens + }; + } + if (tokenType == null) { + tokenType = token.typ; + } + if (tokenType != token.typ) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: queries[0], + syntax: '@' + atRule.nam, + error: 'mixing and/or not allowed at the same level', + tokens + }; + } + queries.shift(); + consumeWhitespace(queries); + if (queries.length == 0) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: queries[0], + syntax: '@' + atRule.nam, + error: 'expected container query-in-parens', tokens }; } - tokens.shift(); - consumeWhitespace(tokens); } } - // @ts-ignore return { valid: ValidationLevel.Valid, matches: [], node: atRule, - syntax: '@document', + syntax: '@' + atRule.nam, error: '', tokens }; } - -function validateAtRuleKeyframes(atRule, options, root) { - if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: atRule, - syntax: '@document', - error: 'expecting at-rule prelude', - tokens: [] - }; +function validateContainerStyleFeature(tokens, atRule) { + tokens = tokens.slice(); + consumeWhitespace(tokens); + if (tokens.length == 1) { + if (tokens[0].typ == exports.EnumToken.ParensTokenType) { + return validateContainerStyleFeature(tokens[0].chi, atRule); + } + if ([exports.EnumToken.DashedIdenTokenType, exports.EnumToken.IdenTokenType].includes(tokens[0].typ) || + (tokens[0].typ == exports.EnumToken.MediaQueryConditionTokenType && tokens[0].op.typ == exports.EnumToken.ColonTokenType)) { + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: '', + tokens + }; + } } - const tokens = atRule.tokens.slice(); + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query features', + tokens + }; +} +function validateContainerSizeFeature(tokens, atRule) { + tokens = tokens.slice(); consumeWhitespace(tokens); if (tokens.length == 0) { - // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], node: atRule, - syntax: '@keyframes', - error: 'expecting at-rule prelude', + syntax: '@' + atRule.nam, + error: 'expected container query features', tokens }; } - if (![exports.EnumToken.StringTokenType, exports.EnumToken.IdenTokenType].includes(tokens[0].typ)) { - // @ts-ignore + if (tokens.length == 1) { + const token = tokens[0]; + if (token.typ == exports.EnumToken.MediaFeatureNotTokenType) { + return validateContainerSizeFeature([token.val], atRule); + } + if (token.typ == exports.EnumToken.ParensTokenType) { + return validateAtRuleContainerQueryStyleInParams(token.chi, atRule); + } + if (![exports.EnumToken.DashedIdenTokenType, exports.EnumToken.MediaQueryConditionTokenType].includes(tokens[0].typ)) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query features', + tokens + }; + } return { - valid: ValidationLevel.Drop, + valid: ValidationLevel.Valid, matches: [], node: atRule, - syntax: '@keyframes', - error: 'expecting ident or string token', + syntax: '@' + atRule.nam, + error: '', tokens }; } - tokens.shift(); + return validateAtRuleContainerQueryStyleInParams(tokens, atRule); +} +function validateAtRuleContainerQueryStyleInParams(tokens, atRule) { + tokens = tokens.slice(); consumeWhitespace(tokens); - if (tokens.length > 0) { - // @ts-ignore + if (tokens.length == 0) { return { valid: ValidationLevel.Drop, matches: [], - node: tokens[0], - syntax: '@keyframes', - error: 'unexpected token', + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query features', tokens }; } - // @ts-ignore + let token = tokens[0]; + let tokenType = null; + let result = null; + while (tokens.length > 0) { + token = tokens[0]; + if (token.typ == exports.EnumToken.MediaFeatureNotTokenType) { + token = token.val; + } + if (tokens[0].typ != exports.EnumToken.ParensTokenType) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query-in-parens', + tokens + }; + } + const slices = tokens[0].chi.slice(); + consumeWhitespace(slices); + if (slices.length == 1) { + if ([exports.EnumToken.MediaFeatureNotTokenType, exports.EnumToken.ParensTokenType].includes(slices[0].typ)) { + result = validateAtRuleContainerQueryStyleInParams(slices, atRule); + if (result.valid == ValidationLevel.Drop) { + return result; + } + } + else if (![exports.EnumToken.DashedIdenTokenType, exports.EnumToken.MediaQueryConditionTokenType].includes(slices[0].typ)) { + result = { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query features', + tokens + }; + } + } + else { + result = validateAtRuleContainerQueryStyleInParams(slices, atRule); + if (result.valid == ValidationLevel.Drop) { + return result; + } + } + tokens.shift(); + consumeWhitespace(tokens); + if (tokens.length == 0) { + break; + } + if (![exports.EnumToken.MediaFeatureAndTokenType, exports.EnumToken.MediaFeatureOrTokenType].includes(tokens[0].typ)) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: '@' + atRule.nam, + error: 'expecting and/or container query token', + tokens + }; + } + if (tokenType == null) { + tokenType = tokens[0].typ; + } + if (tokenType != tokens[0].typ) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: '@' + atRule.nam, + error: 'mixing and/or not allowed at the same level', + tokens + }; + } + tokens.shift(); + consumeWhitespace(tokens); + if (tokens.length == 0) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: '@' + atRule.nam, + error: 'expected container query-in-parens', + tokens + }; + } + } return { valid: ValidationLevel.Valid, matches: [], node: atRule, - syntax: '@keyframes', + syntax: '@' + atRule.nam, error: '', tokens }; } +function validateAtRuleCustomMedia(atRule, options, root) { + // media-query-list + if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Valid, + matches: [], + node: null, + syntax: null, + error: '', + tokens: [] + }; + } + const queries = atRule.tokens.slice(); + consumeWhitespace(queries); + if (queries.length == 0 || queries[0].typ != exports.EnumToken.DashedIdenTokenType) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@custom-media', + error: 'expecting dashed identifier', + tokens: [] + }; + } + queries.shift(); + const result = validateAtRuleMediaQueryList(queries, atRule); + if (result.valid == ValidationLevel.Drop) { + atRule.tokens = []; + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@custom-media', + error: '', + tokens: [] + }; + } + return result; +} + function validateAtRule(atRule, options, root) { if (atRule.nam == 'charset') { const valid = atRule.val.match(/^"[a-zA-Z][a-zA-Z0-9_-]+"$/i) != null; @@ -14466,9 +15758,21 @@ function validateAtRule(atRule, options, root) { if (atRule.nam == 'namespace') { return validateAtRuleNamespace(atRule); } + if (atRule.nam == 'when') { + return validateAtRuleWhen(atRule); + } + if (atRule.nam == 'else') { + return validateAtRuleElse(atRule); + } + if (atRule.nam == 'container') { + return validateAtRuleContainer(atRule); + } if (atRule.nam == 'document') { return validateAtRuleDocument(atRule); } + if (atRule.nam == 'custom-media') { + return validateAtRuleCustomMedia(atRule); + } if (['position-try', 'property', 'font-palette-values'].includes(atRule.nam)) { if (!('tokens' in atRule)) { return { @@ -14538,15 +15842,14 @@ function validateAtRule(atRule, options, root) { } } if (!(name in config.atRules)) { - // if (root?.typ == EnumToken.AtRuleNodeType) { - // - // const syntaxes: ValidationToken = (getParsedSyntax(ValidationSyntaxGroupEnum.AtRules, '@' + (root as AstAtRule).nam) as ValidationToken[])?.[0]; - // - // if ('chi' in syntaxes) { - // - // return validateSyntax(syntaxes.chi as ValidationToken[], [atRule], root, options); - // } - // } + if (options.lenient) { + return { + valid: ValidationLevel.Lenient, + node: atRule, + syntax: null, + error: '' + }; + } return { valid: ValidationLevel.Drop, node: atRule, @@ -14590,51 +15893,14 @@ const enumTokenHints = new Set([ exports.EnumToken.StartMatchTokenType, exports.EnumToken.EndMatchTokenType, exports.EnumToken.IncludeMatchTokenType, exports.EnumToken.DashMatchTokenType, exports.EnumToken.ContainMatchTokenType, exports.EnumToken.EOFTokenType ]); -const webkitPseudoAliasMap = { - '-webkit-autofill': 'autofill', - '-webkit-any': 'is', - '-moz-any': 'is', - '-webkit-border-after': 'border-block-end', - '-webkit-border-after-color': 'border-block-end-color', - '-webkit-border-after-style': 'border-block-end-style', - '-webkit-border-after-width': 'border-block-end-width', - '-webkit-border-before': 'border-block-start', - '-webkit-border-before-color': 'border-block-start-color', - '-webkit-border-before-style': 'border-block-start-style', - '-webkit-border-before-width': 'border-block-start-width', - '-webkit-border-end': 'border-inline-end', - '-webkit-border-end-color': 'border-inline-end-color', - '-webkit-border-end-style': 'border-inline-end-style', - '-webkit-border-end-width': 'border-inline-end-width', - '-webkit-border-start': 'border-inline-start', - '-webkit-border-start-color': 'border-inline-start-color', - '-webkit-border-start-style': 'border-inline-start-style', - '-webkit-border-start-width': 'border-inline-start-width', - '-webkit-box-align': 'align-items', - '-webkit-box-direction': 'flex-direction', - '-webkit-box-flex': 'flex-grow', - '-webkit-box-lines': 'flex-flow', - '-webkit-box-ordinal-group': 'order', - '-webkit-box-orient': 'flex-direction', - '-webkit-box-pack': 'justify-content', - '-webkit-column-break-after': 'break-after', - '-webkit-column-break-before': 'break-before', - '-webkit-column-break-inside': 'break-inside', - '-webkit-font-feature-settings': 'font-feature-settings', - '-webkit-hyphenate-character': 'hyphenate-character', - '-webkit-initial-letter': 'initial-letter', - '-webkit-margin-end': 'margin-block-end', - '-webkit-margin-start': 'margin-block-start', - '-webkit-padding-after': 'padding-block-end', - '-webkit-padding-before': 'padding-block-start', - '-webkit-padding-end': 'padding-inline-end', - '-webkit-padding-start': 'padding-inline-start', - '-webkit-min-device-pixel-ratio': 'min-resolution', - '-webkit-max-device-pixel-ratio': 'max-resolution' -}; function reject(reason) { throw new Error(reason ?? 'Parsing aborted'); } +/** + * parse css string + * @param iterator + * @param options + */ async function doParse(iterator, options = {}) { if (options.signal != null) { options.signal.addEventListener('abort', reject); @@ -14656,6 +15922,7 @@ async function doParse(iterator, options = {}) { setParent: true, removePrefix: false, validation: true, + lenient: true, ...options }; if (options.expandNestingRules) { @@ -14694,9 +15961,10 @@ async function doParse(iterator, options = {}) { } const iter = tokenize$1(iterator); let item; + const rawTokens = []; while (item = iter.next().value) { stats.bytesIn = item.bytesIn; - // + rawTokens.push(item); // doParse error if (item.hint != null && BadTokensTypes.includes(item.hint)) { // bad token @@ -14706,7 +15974,8 @@ async function doParse(iterator, options = {}) { tokens.push(item); } if (item.token == ';' || item.token == '{') { - let node = await parseNode(tokens, context, stats, options, errors, src, map); + let node = await parseNode(tokens, context, stats, options, errors, src, map, rawTokens); + rawTokens.length = 0; if (node != null) { // @ts-ignore stack.push(node); @@ -14732,7 +16001,8 @@ async function doParse(iterator, options = {}) { map = new Map; } else if (item.token == '}') { - await parseNode(tokens, context, stats, options, errors, src, map); + await parseNode(tokens, context, stats, options, errors, src, map, rawTokens); + rawTokens.length = 0; const previousNode = stack.pop(); // @ts-ignore context = stack[stack.length - 1] ?? ast; @@ -14755,7 +16025,8 @@ async function doParse(iterator, options = {}) { } } if (tokens.length > 0) { - await parseNode(tokens, context, stats, options, errors, src, map); + await parseNode(tokens, context, stats, options, errors, src, map, rawTokens); + rawTokens.length = 0; if (context != null && context.typ == exports.EnumToken.InvalidRuleTokenType) { const index = context.chi.findIndex(node => node == context); if (index > -1) { @@ -14767,6 +16038,7 @@ async function doParse(iterator, options = {}) { const previousNode = stack.pop(); // @ts-ignore context = stack[stack.length - 1] ?? ast; + // remove empty nodes // @ts-ignore if (options.removeEmpty && previousNode != null && previousNode.chi.length == 0 && context.chi[context.chi.length - 1] == previousNode) { // @ts-ignore @@ -14819,33 +16091,6 @@ async function doParse(iterator, options = {}) { minify(ast, options, true, errors, false); } } - // if (options.setParent) { - // - // const nodes: Array = [ast]; - // let node: AstNode; - // - // while ((node = nodes.shift()!)) { - // - // // @ts-ignore - // if (node.chi.length > 0) { - // - // // @ts-ignore - // for (const child of node.chi) { - // - // if (child.parent != node) { - // - // Object.defineProperty(child, 'parent', {...definedPropertySettings, value: node}); - // } - // - // if ('chi' in child && child.chi.length > 0) { - // - // // @ts-ignore - // nodes.push(child); - // } - // } - // } - // } - // } const endTime = performance.now(); if (options.signal != null) { options.signal.removeEventListener('abort', reject); @@ -14862,7 +16107,17 @@ async function doParse(iterator, options = {}) { } }; } -async function parseNode(results, context, stats, options, errors, src, map) { +function getLastNode(context) { + let i = context.chi.length; + while (i--) { + if ([exports.EnumToken.CommentTokenType, exports.EnumToken.CDOCOMMTokenType, exports.EnumToken.WhitespaceTokenType].includes(context.chi[i].typ)) { + continue; + } + return context.chi[i]; + } + return null; +} +async function parseNode(results, context, stats, options, errors, src, map, rawTokens) { let tokens = []; for (const t of results) { const node = getTokenType(t.token, t.hint); @@ -14917,24 +16172,6 @@ async function parseNode(results, context, stats, options, errors, src, map) { if (tokens[0]?.typ == exports.EnumToken.AtRuleTokenType) { const atRule = tokens.shift(); const position = map.get(atRule); - // if (atRule.val == 'charset') { - // - // if (context.typ != EnumToken.StyleSheetNodeType || context.chi.some(t => t.typ != EnumToken.CDOCOMMTokenType && t.typ != EnumToken.CommentNodeType)) { - // - // errors.push({ - // action: 'drop', - // message: 'doParse: invalid @charset', - // location: {src, ...position} - // }); - // - // return null; - // } - // - // if (options.removeCharset) { - // - // return null; - // } - // } // @ts-ignore while ([exports.EnumToken.WhitespaceTokenType].includes(tokens[0]?.typ)) { tokens.shift(); @@ -15031,8 +16268,43 @@ async function parseNode(results, context, stats, options, errors, src, map) { // https://www.w3.org/TR/css-nesting-1/#conditionals // allowed nesting at-rules // there must be a top level rule in the stack - if (atRule.val == 'charset' && options.removeCharset) { - return null; + if (atRule.val == 'charset') { + let spaces = 0; + // https://developer.mozilla.org/en-US/docs/Web/CSS/@charset + for (let k = 1; k < rawTokens.length; k++) { + if (rawTokens[k].hint == exports.EnumToken.WhitespaceTokenType) { + spaces += rawTokens[k].len; + continue; + } + if (rawTokens[k].hint == exports.EnumToken.CommentTokenType) { + continue; + } + if (rawTokens[k].hint == exports.EnumToken.CDOCOMMTokenType) { + continue; + } + if (spaces > 1) { + errors.push({ + action: 'drop', + message: '@charset must have only one space', + // @ts-ignore + location: { src, ...(map.get(atRule) ?? position) } + }); + return null; + } + if (rawTokens[k].hint != exports.EnumToken.StringTokenType || rawTokens[k].token[0] != '"') { + errors.push({ + action: 'drop', + message: '@charset expects a ""', + // @ts-ignore + location: { src, ...(map.get(atRule) ?? position) } + }); + return null; + } + break; + } + if (options.removeCharset) { + return null; + } } const t = parseAtRulePrelude(parseTokens(tokens, { minify: options.minify }), atRule); const raw = t.reduce((acc, curr) => { @@ -15060,11 +16332,36 @@ async function parseNode(results, context, stats, options, errors, src, map) { node.loc = loc; } if (options.validation) { - const valid = validateAtRule(node, options, context); + let isValid = true; + if (node.nam == 'else') { + const prev = getLastNode(context); + if (prev != null && prev.typ == exports.EnumToken.AtRuleNodeType && ['when', 'else'].includes(prev.nam)) { + if (prev.nam == 'else') { + isValid = Array.isArray(prev.tokens) && prev.tokens.length > 0; + } + } + else { + isValid = false; + } + } + const valid = isValid ? validateAtRule(node, options, context) : { + valid: ValidationLevel.Drop, + node, + syntax: '@' + node.nam, + error: '@' + node.nam + ' not allowed here'}; if (valid.valid == ValidationLevel.Drop) { + errors.push({ + action: 'drop', + message: valid.error + ' - "' + tokens.reduce((acc, curr) => acc + renderToken(curr, { minify: false }), '') + '"', + // @ts-ignore + location: { src, ...(map.get(valid.node) ?? position) } + }); // @ts-ignore node.typ = exports.EnumToken.InvalidAtRuleTokenType; } + else { + node.val = node.tokens.reduce((acc, curr) => acc + renderToken(curr, { minify: false, removeComments: true }), ''); + } } // @ts-ignore context.chi.push(node); @@ -15243,7 +16540,24 @@ async function parseNode(results, context, stats, options, errors, src, map) { }; const result = parseDeclarationNode(node, errors, src, position); if (result != null) { - if (options.validation) ; + // if (options.validation) { + // + // const valid: ValidationResult = validateDeclaration(result, options, context); + // + // console.error({valid}); + // + // if (valid.valid == ValidationLevel.Drop) { + // + // errors.push({ + // action: 'drop', + // message: valid.error + ' - "' + tokens.reduce((acc, curr) => acc + renderToken(curr, {minify: false}), '') + '"', + // // @ts-ignore + // location: {src, ...(map.get(valid.node) ?? position)} + // }); + // + // return null; + // } + // } // @ts-ignore context.chi.push(result); Object.defineProperty(result, 'parent', { ...definedPropertySettings, value: context }); @@ -15252,6 +16566,11 @@ async function parseNode(results, context, stats, options, errors, src, map) { } } } +/** + * parse at-rule prelude + * @param tokens + * @param atRule + */ function parseAtRulePrelude(tokens, atRule) { // @ts-ignore for (const { value, parent } of walkValues(tokens, null, null, true)) { @@ -15320,17 +16639,18 @@ function parseAtRulePrelude(tokens, atRule) { continue; } } - if (value.typ == exports.EnumToken.ParensTokenType) { + if (value.typ == exports.EnumToken.ParensTokenType || (value.typ == exports.EnumToken.FunctionTokenType && ['media', 'supports', 'style', 'scroll-state'].includes(value.val))) { // @todo parse range and declarations // parseDeclaration(parent.chi); let i; let nameIndex = -1; let valueIndex = -1; + const dashedIdent = value.typ == exports.EnumToken.FunctionTokenType && value.val == 'style'; for (let i = 0; i < value.chi.length; i++) { if (value.chi[i].typ == exports.EnumToken.CommentTokenType || value.chi[i].typ == exports.EnumToken.WhitespaceTokenType) { continue; } - if (value.chi[i].typ == exports.EnumToken.IdenTokenType) { + if ((dashedIdent && value.chi[i].typ == exports.EnumToken.DashedIdenTokenType) || value.chi[i].typ == exports.EnumToken.IdenTokenType || value.chi[i].typ == exports.EnumToken.FunctionTokenType || value.chi[i].typ == exports.EnumToken.ColorTokenType) { nameIndex = i; } break; @@ -15359,6 +16679,13 @@ function parseAtRulePrelude(tokens, atRule) { ].includes(value.chi[valueIndex].typ)) { const val = value.chi.splice(valueIndex, 1)[0]; const node = value.chi.splice(nameIndex, 1)[0]; + // 'background' + // @ts-ignore + if (node.typ == exports.EnumToken.ColorTokenType && node.kin == 'dpsys') { + // @ts-ignore + delete node.kin; + node.typ = exports.EnumToken.IdenTokenType; + } while (value.chi[0]?.typ == exports.EnumToken.WhitespaceTokenType) { value.chi.shift(); } @@ -15376,6 +16703,10 @@ function parseAtRulePrelude(tokens, atRule) { } return tokens; } +/** + * parse selector + * @param tokens + */ function parseSelector(tokens) { for (const { value, previousValue, nextValue, parent } of walkValues(tokens)) { if (value.typ == exports.EnumToken.CommentTokenType || @@ -15519,6 +16850,11 @@ function parseSelector(tokens) { // // return doParse(`.x{${src}`, options).then((result: ParseResult) => (result.ast.chi[0]).chi.filter(t => t.typ == EnumToken.DeclarationNodeType)); // } +/** + * parse string + * @param src + * @param options + */ function parseString(src, options = { location: false }) { return parseTokens([...tokenize$1(src)].map(t => { const token = getTokenType(t.token, t.hint); @@ -15600,7 +16936,7 @@ function getTokenType(val, hint) { chi: [] }; } - if (['linear-gradient', 'radial-gradient', 'repeating-linear-gradient', 'repeating-radial-gradient', 'conic-gradient', 'image', 'image-set', 'element', 'cross-fade'].includes(val)) { + if (['linear-gradient', 'radial-gradient', 'repeating-linear-gradient', 'repeating-radial-gradient', 'conic-gradient', 'image', 'image-set', 'element', 'cross-fade', 'paint'].includes(val)) { return { typ: exports.EnumToken.ImageFunctionTokenType, val, @@ -15700,6 +17036,11 @@ function getTokenType(val, hint) { val }; } +/** + * parse token list + * @param tokens + * @param options + */ function parseTokens(tokens, options = {}) { for (let i = 0; i < tokens.length; i++) { const t = tokens[i]; @@ -16076,6 +17417,11 @@ var WalkerValueEvent; WalkerValueEvent[WalkerValueEvent["Enter"] = 0] = "Enter"; WalkerValueEvent[WalkerValueEvent["Leave"] = 1] = "Leave"; })(WalkerValueEvent || (WalkerValueEvent = {})); +/** + * walk ast nodes + * @param node + * @param filter + */ function* walk(node, filter) { const parents = [node]; const root = node; @@ -16104,6 +17450,13 @@ function* walk(node, filter) { } } } +/** + * walk ast values + * @param values + * @param root + * @param filter + * @param reverse + */ function* walkValues(values, root = null, filter, reverse) { // const set = new Set(); const stack = values.slice(); @@ -16197,6 +17550,10 @@ function* walkValues(values, root = null, filter, reverse) { } } +/** + * expand nested css ast + * @param ast + */ function expand(ast) { // if (![exports.EnumToken.RuleNodeType, exports.EnumToken.StyleSheetNodeType, exports.EnumToken.AtRuleNodeType].includes(ast.typ)) { @@ -16241,7 +17598,7 @@ function expand(ast) { } return result; } -function expandRule(node, parent) { +function expandRule(node) { const ast = { ...node, chi: node.chi.slice() }; const result = []; if (ast.typ == exports.EnumToken.RuleNodeType) { @@ -16346,7 +17703,10 @@ function expandRule(node, parent) { if (astAtRule.val.includes('&')) { astAtRule.val = replaceCompound(astAtRule.val, ast.sel); } - astAtRule = expand(astAtRule); + const slice = astAtRule.chi.slice().filter(t => t.typ == exports.EnumToken.RuleNodeType && t.sel.includes('&')); + if (slice.length > 0) { + expandRule({ ...node, chi: astAtRule.chi.slice() }); + } } else { // @ts-ignore @@ -16385,6 +17745,11 @@ function expandRule(node, parent) { // @ts-ignore return ast.chi.length > 0 ? [ast].concat(result) : result; } +/** + * replace compound selector + * @param input + * @param replace + */ function replaceCompound(input, replace) { const tokens = parseString(input); let replacement = null; @@ -16856,20 +18221,8 @@ class PropertySet { return acc; }, []) }][Symbol.iterator](); - // return { - // next() { - // - // return iterator.next(); - // } - // } } return iterator; - // return { - // next() { - // - // return iterator.next(); - // } - // } } } @@ -17026,55 +18379,6 @@ class PropertyMap { } return this; } - matchTypes(declaration) { - const patterns = this.pattern.slice(); - const values = [...declaration.val]; - let i; - let j; - const map = new Map; - for (i = 0; i < patterns.length; i++) { - for (j = 0; j < values.length; j++) { - if (!map.has(patterns[i])) { - // @ts-ignore - map.set(patterns[i], this.config.properties?.[patterns[i]]?.constraints?.mapping?.max ?? 1); - } - let count = map.get(patterns[i]); - if (count > 0 && matchType(values[j], this.config.properties[patterns[i]])) { - Object.defineProperty(values[j], 'propertyName', { - enumerable: false, - writable: true, - value: patterns[i] - }); - map.set(patterns[i], --count); - values.splice(j--, 1); - } - } - } - if (this.config.set != null) { - for (const [key, val] of Object.entries(this.config.set)) { - if (map.has(key)) { - for (const v of val) { - // missing - if (map.get(v) == 1) { - let i = declaration.val.length; - while (i--) { - // @ts-ignore - if (declaration.val[i].propertyName == key) { - const val = { ...declaration.val[i] }; - Object.defineProperty(val, 'propertyName', { - enumerable: false, - writable: true, - value: v - }); - declaration.val.splice(i, 0, val, { typ: exports.EnumToken.WhitespaceTokenType }); - } - } - } - } - } - } - } - } [Symbol.iterator]() { let iterable; let requiredCount = 0; @@ -17113,9 +18417,15 @@ class PropertyMap { // @ts-ignore let typ = (exports.EnumToken[this.config.separator?.typ] ?? exports.EnumToken.CommaTokenType); // @ts-ignore - const sep = this.config.separator == null ? null : { ...this.config.separator, typ: exports.EnumToken[this.config.separator.typ] }; + const sep = this.config.separator == null ? null : { + ...this.config.separator, + typ: exports.EnumToken[this.config.separator.typ] + }; // @ts-ignore - const separator = this.config.separator ? renderToken({ ...this.config.separator, typ: exports.EnumToken[this.config.separator.typ] }) : ','; + const separator = this.config.separator ? renderToken({ + ...this.config.separator, + typ: exports.EnumToken[this.config.separator.typ] + }) : ','; this.matchTypes(declaration); values.push(value); for (i = 0; i < declaration.val.length; i++) { @@ -17310,7 +18620,6 @@ class PropertyMap { acc.push(curr); return acc; }, []); - // @todo remove renderToken call if (props.default.includes(curr[1][i].reduce((acc, curr) => acc + renderToken(curr) + ' ', '').trimEnd())) { if (!this.config.properties[curr[0]].required) { continue; @@ -17408,6 +18717,7 @@ class PropertyMap { } // @ts-ignore if (values.length == 1 && + // @ts-ignore typeof values[0].val == 'string' && this.config.default.includes(values[0].val.toLowerCase()) && this.config.default[0] != values[0].val.toLowerCase()) { @@ -17426,6 +18736,7 @@ class PropertyMap { // @ts-ignore next() { let v = iterable.next(); + // @ts-ignore while (v.done || v.value instanceof PropertySet) { if (v.value instanceof PropertySet) { // @ts-ignore @@ -17448,6 +18759,55 @@ class PropertyMap { } }; } + matchTypes(declaration) { + const patterns = this.pattern.slice(); + const values = [...declaration.val]; + let i; + let j; + const map = new Map; + for (i = 0; i < patterns.length; i++) { + for (j = 0; j < values.length; j++) { + if (!map.has(patterns[i])) { + // @ts-ignore + map.set(patterns[i], this.config.properties?.[patterns[i]]?.constraints?.mapping?.max ?? 1); + } + let count = map.get(patterns[i]); + if (count > 0 && matchType(values[j], this.config.properties[patterns[i]])) { + Object.defineProperty(values[j], 'propertyName', { + enumerable: false, + writable: true, + value: patterns[i] + }); + map.set(patterns[i], --count); + values.splice(j--, 1); + } + } + } + if (this.config.set != null) { + for (const [key, val] of Object.entries(this.config.set)) { + if (map.has(key)) { + for (const v of val) { + // missing + if (map.get(v) == 1) { + let i = declaration.val.length; + while (i--) { + // @ts-ignore + if (declaration.val[i].propertyName == key) { + const val = { ...declaration.val[i] }; + Object.defineProperty(val, 'propertyName', { + enumerable: false, + writable: true, + value: v + }); + declaration.val.splice(i, 0, val, { typ: exports.EnumToken.WhitespaceTokenType }); + } + } + } + } + } + } + } + } removeDefaults(map, value) { for (const [key, val] of map) { const config = this.config.properties[key]; @@ -17748,6 +19108,15 @@ const definedPropertySettings = { configurable: true, enumerable: false, writabl const notEndingWith = ['(', '['].concat(combinators); // @ts-ignore const features = Object.values(allFeatures).sort((a, b) => a.ordering - b.ordering); +/** + * minify ast + * @param ast + * @param options + * @param recursive + * @param errors + * @param nestingContent + * @param context + */ function minify(ast, options = {}, recursive = false, errors, nestingContent, context = {}) { if (!('nodes' in context)) { context.nodes = new Set; @@ -17817,7 +19186,8 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent, co continue; } if (node.typ == exports.EnumToken.AtRuleNodeType) { - if (node.nam == 'media' && node.val == 'all') { + // @ts-ignore + if (node.nam == 'media' && ['all', '', null].includes(node.val)) { // @ts-ignore ast.chi?.splice(i, 1, ...node.chi); i--; @@ -18132,6 +19502,18 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent, co } return ast; } +function hasDeclaration(node) { + // @ts-ignore + for (let i = 0; i < node.chi?.length; i++) { + // @ts-ignore + if (node.chi[i].typ == exports.EnumToken.CommentNodeType) { + continue; + } + // @ts-ignore + return node.chi[i].typ == exports.EnumToken.DeclarationNodeType; + } + return true; +} function reduceSelector(selector) { if (selector.length == 0) { return null; @@ -18232,18 +19614,10 @@ function reduceSelector(selector) { reducible: selector.every((selector) => !['>', '+', '~', '&'].includes(selector[0])) }; } -function hasDeclaration(node) { - // @ts-ignore - for (let i = 0; i < node.chi?.length; i++) { - // @ts-ignore - if (node.chi[i].typ == exports.EnumToken.CommentNodeType) { - continue; - } - // @ts-ignore - return node.chi[i].typ == exports.EnumToken.DeclarationNodeType; - } - return true; -} +/** + * split selector string + * @param buffer + */ function splitRule(buffer) { const result = [[]]; let str = ''; diff --git a/dist/index.d.ts b/dist/index.d.ts index 056675ea..47f9dde9 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -120,6 +120,15 @@ declare enum EnumToken { TimelineFunction = 16 } +/** + * minify ast + * @param ast + * @param options + * @param recursive + * @param errors + * @param nestingContent + * @param context + */ declare function minify(ast: AstNode, options?: ParserOptions | MinifyOptions, recursive?: boolean, errors?: ErrorDescription[], nestingContent?: boolean, context?: { [key: string]: any; }): AstNode; @@ -128,22 +137,56 @@ declare enum WalkerValueEvent { Enter = 0, Leave = 1 } +/** + * walk ast nodes + * @param node + * @param filter + */ declare function walk(node: AstNode, filter?: WalkerFilter): Generator; +/** + * walk ast values + * @param values + * @param root + * @param filter + * @param reverse + */ declare function walkValues(values: Token[], root?: AstNode | Token | null, filter?: WalkerValueFilter | { event: WalkerValueEvent; fn?: WalkerValueFilter; type?: EnumToken | EnumToken[] | ((token: Token) => boolean); }, reverse?: boolean): Generator; +/** + * expand nested css ast + * @param ast + */ declare function expand(ast: AstNode): AstNode; +/** + * render ast token + * @param token + * @param options + * @param cache + * @param reducer + * @param errors + */ declare function renderToken(token: Token, options?: RenderOptions, cache?: { [key: string]: any; }, reducer?: (acc: string, curr: Token) => string, errors?: ErrorDescription[]): string; +/** + * parse string + * @param src + * @param options + */ declare function parseString(src: string, options?: { location: boolean; }): Token[]; +/** + * parse token list + * @param tokens + * @param options + */ declare function parseTokens(tokens: Token[], options?: ParseTokenOptions): Token[]; export declare interface LiteralToken extends BaseToken { @@ -567,21 +610,15 @@ export declare interface MediaFeatureToken extends BaseToken { val: string; } -export declare interface MediaFeatureOnlyToken extends BaseToken { - - typ: EnumToken.MediaFeatureOnlyTokenType, - val: Token; -} - export declare interface MediaFeatureNotToken extends BaseToken { typ: EnumToken.MediaFeatureNotTokenType, val: Token; } -export declare interface MediaFeatureNotToken extends BaseToken { +export declare interface MediaFeatureOnlyToken extends BaseToken { - typ: EnumToken.MediaFeatureNotTokenType, + typ: EnumToken.MediaFeatureOnlyTokenType, val: Token; } @@ -861,6 +898,7 @@ export declare interface AstKeyFrameRule extends BaseToken { chi: Array; optimized?: OptimizedSelector; raw?: RawSelectorTokens; + tokens?: Token[] } export declare type RawSelectorTokens = string[][]; @@ -994,6 +1032,7 @@ export declare interface ErrorDescription { interface ValidationOptions { validation?: boolean; + lenient?: boolean; } export declare interface ParserOptions extends ValidationOptions, PropertyListOptions { @@ -1044,9 +1083,7 @@ export declare interface MinifyFeature { export declare interface MinifyFeature { ordering: number; - register: (options: MinifyOptions | ParserOptions) => void; - run: (ast: AstRule | AstAtRule, options: ParserOptions, parent: AstRule | AstAtRule | AstRuleStyleSheet, context: { [key: string]: any }) => void; @@ -1060,6 +1097,7 @@ export declare interface ResolvedPath { export declare interface RenderOptions { minify?: boolean; + beautify?: boolean; removeEmpty?: boolean; expandNestingRules?: boolean; preserveLicense?: boolean; @@ -1073,7 +1111,6 @@ export declare interface RenderOptions { cwd?: string; load?: (url: string, currentUrl: string) => Promise; resolve?: (url: string, currentUrl: string, currentWorkingDirectory?: string) => ResolvedPath; - } export declare interface TransformOptions extends ParserOptions, RenderOptions { diff --git a/dist/lib/ast/expand.js b/dist/lib/ast/expand.js index ef249099..cb8ed466 100644 --- a/dist/lib/ast/expand.js +++ b/dist/lib/ast/expand.js @@ -6,6 +6,10 @@ import { renderToken } from '../renderer/render.js'; import '../renderer/color/utils/constants.js'; import '../parser/utils/config.js'; +/** + * expand nested css ast + * @param ast + */ function expand(ast) { // if (![EnumToken.RuleNodeType, EnumToken.StyleSheetNodeType, EnumToken.AtRuleNodeType].includes(ast.typ)) { @@ -50,7 +54,7 @@ function expand(ast) { } return result; } -function expandRule(node, parent) { +function expandRule(node) { const ast = { ...node, chi: node.chi.slice() }; const result = []; if (ast.typ == EnumToken.RuleNodeType) { @@ -155,7 +159,10 @@ function expandRule(node, parent) { if (astAtRule.val.includes('&')) { astAtRule.val = replaceCompound(astAtRule.val, ast.sel); } - astAtRule = expand(astAtRule); + const slice = astAtRule.chi.slice().filter(t => t.typ == EnumToken.RuleNodeType && t.sel.includes('&')); + if (slice.length > 0) { + expandRule({ ...node, chi: astAtRule.chi.slice() }); + } } else { // @ts-ignore @@ -194,6 +201,11 @@ function expandRule(node, parent) { // @ts-ignore return ast.chi.length > 0 ? [ast].concat(result) : result; } +/** + * replace compound selector + * @param input + * @param replace + */ function replaceCompound(input, replace) { const tokens = parseString(input); let replacement = null; diff --git a/dist/lib/ast/math/expression.js b/dist/lib/ast/math/expression.js index 75bc8347..a3dd0a36 100644 --- a/dist/lib/ast/math/expression.js +++ b/dist/lib/ast/math/expression.js @@ -1,5 +1,5 @@ import { EnumToken } from '../types.js'; -import { compute, rem } from './math.js'; +import { rem, compute } from './math.js'; import { reduceNumber } from '../../renderer/render.js'; import { mathFuncs } from '../../syntax/syntax.js'; diff --git a/dist/lib/ast/minify.js b/dist/lib/ast/minify.js index 75daf842..0a70fc6d 100644 --- a/dist/lib/ast/minify.js +++ b/dist/lib/ast/minify.js @@ -2,10 +2,10 @@ import { parseString } from '../parser/parse.js'; import { EnumToken } from './types.js'; import { walkValues } from './walk.js'; import { replaceCompound } from './expand.js'; -import { isWhiteSpace, isIdent, isFunction, isIdentStart } from '../syntax/syntax.js'; +import { isIdent, isFunction, isWhiteSpace, isIdentStart } from '../syntax/syntax.js'; import '../parser/utils/config.js'; import { eq } from '../parser/utils/eq.js'; -import { renderToken, doRender } from '../renderer/render.js'; +import { doRender, renderToken } from '../renderer/render.js'; import * as index from './features/index.js'; const combinators = ['+', '>', '~', '||', '|']; @@ -13,6 +13,15 @@ const definedPropertySettings = { configurable: true, enumerable: false, writabl const notEndingWith = ['(', '['].concat(combinators); // @ts-ignore const features = Object.values(index).sort((a, b) => a.ordering - b.ordering); +/** + * minify ast + * @param ast + * @param options + * @param recursive + * @param errors + * @param nestingContent + * @param context + */ function minify(ast, options = {}, recursive = false, errors, nestingContent, context = {}) { if (!('nodes' in context)) { context.nodes = new Set; @@ -82,7 +91,8 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent, co continue; } if (node.typ == EnumToken.AtRuleNodeType) { - if (node.nam == 'media' && node.val == 'all') { + // @ts-ignore + if (node.nam == 'media' && ['all', '', null].includes(node.val)) { // @ts-ignore ast.chi?.splice(i, 1, ...node.chi); i--; @@ -397,6 +407,18 @@ function minify(ast, options = {}, recursive = false, errors, nestingContent, co } return ast; } +function hasDeclaration(node) { + // @ts-ignore + for (let i = 0; i < node.chi?.length; i++) { + // @ts-ignore + if (node.chi[i].typ == EnumToken.CommentNodeType) { + continue; + } + // @ts-ignore + return node.chi[i].typ == EnumToken.DeclarationNodeType; + } + return true; +} function reduceSelector(selector) { if (selector.length == 0) { return null; @@ -497,18 +519,10 @@ function reduceSelector(selector) { reducible: selector.every((selector) => !['>', '+', '~', '&'].includes(selector[0])) }; } -function hasDeclaration(node) { - // @ts-ignore - for (let i = 0; i < node.chi?.length; i++) { - // @ts-ignore - if (node.chi[i].typ == EnumToken.CommentNodeType) { - continue; - } - // @ts-ignore - return node.chi[i].typ == EnumToken.DeclarationNodeType; - } - return true; -} +/** + * split selector string + * @param buffer + */ function splitRule(buffer) { const result = [[]]; let str = ''; @@ -995,4 +1009,4 @@ function reduceRuleSelector(node) { } } -export { combinators, definedPropertySettings, hasDeclaration, matchSelectors, minify, reduceSelector, splitRule }; +export { combinators, definedPropertySettings, minify, splitRule }; diff --git a/dist/lib/ast/types.js b/dist/lib/ast/types.js index d31e7573..11f5d8da 100644 --- a/dist/lib/ast/types.js +++ b/dist/lib/ast/types.js @@ -2,6 +2,7 @@ var ValidationLevel; (function (ValidationLevel) { ValidationLevel[ValidationLevel["Valid"] = 0] = "Valid"; ValidationLevel[ValidationLevel["Drop"] = 1] = "Drop"; + ValidationLevel[ValidationLevel["Lenient"] = 2] = "Lenient"; /* preserve unknown at-rules, declarations and pseudo-classes */ })(ValidationLevel || (ValidationLevel = {})); var EnumToken; (function (EnumToken) { diff --git a/dist/lib/ast/walk.js b/dist/lib/ast/walk.js index ab94dae2..fa352b44 100644 --- a/dist/lib/ast/walk.js +++ b/dist/lib/ast/walk.js @@ -5,6 +5,11 @@ var WalkerValueEvent; WalkerValueEvent[WalkerValueEvent["Enter"] = 0] = "Enter"; WalkerValueEvent[WalkerValueEvent["Leave"] = 1] = "Leave"; })(WalkerValueEvent || (WalkerValueEvent = {})); +/** + * walk ast nodes + * @param node + * @param filter + */ function* walk(node, filter) { const parents = [node]; const root = node; @@ -33,6 +38,13 @@ function* walk(node, filter) { } } } +/** + * walk ast values + * @param values + * @param root + * @param filter + * @param reverse + */ function* walkValues(values, root = null, filter, reverse) { // const set = new Set(); const stack = values.slice(); diff --git a/dist/lib/parser/declaration/map.js b/dist/lib/parser/declaration/map.js index 960d32d8..c1d6b809 100644 --- a/dist/lib/parser/declaration/map.js +++ b/dist/lib/parser/declaration/map.js @@ -162,55 +162,6 @@ class PropertyMap { } return this; } - matchTypes(declaration) { - const patterns = this.pattern.slice(); - const values = [...declaration.val]; - let i; - let j; - const map = new Map; - for (i = 0; i < patterns.length; i++) { - for (j = 0; j < values.length; j++) { - if (!map.has(patterns[i])) { - // @ts-ignore - map.set(patterns[i], this.config.properties?.[patterns[i]]?.constraints?.mapping?.max ?? 1); - } - let count = map.get(patterns[i]); - if (count > 0 && matchType(values[j], this.config.properties[patterns[i]])) { - Object.defineProperty(values[j], 'propertyName', { - enumerable: false, - writable: true, - value: patterns[i] - }); - map.set(patterns[i], --count); - values.splice(j--, 1); - } - } - } - if (this.config.set != null) { - for (const [key, val] of Object.entries(this.config.set)) { - if (map.has(key)) { - for (const v of val) { - // missing - if (map.get(v) == 1) { - let i = declaration.val.length; - while (i--) { - // @ts-ignore - if (declaration.val[i].propertyName == key) { - const val = { ...declaration.val[i] }; - Object.defineProperty(val, 'propertyName', { - enumerable: false, - writable: true, - value: v - }); - declaration.val.splice(i, 0, val, { typ: EnumToken.WhitespaceTokenType }); - } - } - } - } - } - } - } - } [Symbol.iterator]() { let iterable; let requiredCount = 0; @@ -249,9 +200,15 @@ class PropertyMap { // @ts-ignore let typ = (EnumToken[this.config.separator?.typ] ?? EnumToken.CommaTokenType); // @ts-ignore - const sep = this.config.separator == null ? null : { ...this.config.separator, typ: EnumToken[this.config.separator.typ] }; + const sep = this.config.separator == null ? null : { + ...this.config.separator, + typ: EnumToken[this.config.separator.typ] + }; // @ts-ignore - const separator = this.config.separator ? renderToken({ ...this.config.separator, typ: EnumToken[this.config.separator.typ] }) : ','; + const separator = this.config.separator ? renderToken({ + ...this.config.separator, + typ: EnumToken[this.config.separator.typ] + }) : ','; this.matchTypes(declaration); values.push(value); for (i = 0; i < declaration.val.length; i++) { @@ -446,7 +403,6 @@ class PropertyMap { acc.push(curr); return acc; }, []); - // @todo remove renderToken call if (props.default.includes(curr[1][i].reduce((acc, curr) => acc + renderToken(curr) + ' ', '').trimEnd())) { if (!this.config.properties[curr[0]].required) { continue; @@ -544,6 +500,7 @@ class PropertyMap { } // @ts-ignore if (values.length == 1 && + // @ts-ignore typeof values[0].val == 'string' && this.config.default.includes(values[0].val.toLowerCase()) && this.config.default[0] != values[0].val.toLowerCase()) { @@ -562,6 +519,7 @@ class PropertyMap { // @ts-ignore next() { let v = iterable.next(); + // @ts-ignore while (v.done || v.value instanceof PropertySet) { if (v.value instanceof PropertySet) { // @ts-ignore @@ -584,6 +542,55 @@ class PropertyMap { } }; } + matchTypes(declaration) { + const patterns = this.pattern.slice(); + const values = [...declaration.val]; + let i; + let j; + const map = new Map; + for (i = 0; i < patterns.length; i++) { + for (j = 0; j < values.length; j++) { + if (!map.has(patterns[i])) { + // @ts-ignore + map.set(patterns[i], this.config.properties?.[patterns[i]]?.constraints?.mapping?.max ?? 1); + } + let count = map.get(patterns[i]); + if (count > 0 && matchType(values[j], this.config.properties[patterns[i]])) { + Object.defineProperty(values[j], 'propertyName', { + enumerable: false, + writable: true, + value: patterns[i] + }); + map.set(patterns[i], --count); + values.splice(j--, 1); + } + } + } + if (this.config.set != null) { + for (const [key, val] of Object.entries(this.config.set)) { + if (map.has(key)) { + for (const v of val) { + // missing + if (map.get(v) == 1) { + let i = declaration.val.length; + while (i--) { + // @ts-ignore + if (declaration.val[i].propertyName == key) { + const val = { ...declaration.val[i] }; + Object.defineProperty(val, 'propertyName', { + enumerable: false, + writable: true, + value: v + }); + declaration.val.splice(i, 0, val, { typ: EnumToken.WhitespaceTokenType }); + } + } + } + } + } + } + } + } removeDefaults(map, value) { for (const [key, val] of map) { const config = this.config.properties[key]; diff --git a/dist/lib/parser/declaration/set.js b/dist/lib/parser/declaration/set.js index f687b19b..60dc13b0 100644 --- a/dist/lib/parser/declaration/set.js +++ b/dist/lib/parser/declaration/set.js @@ -181,20 +181,8 @@ class PropertySet { return acc; }, []) }][Symbol.iterator](); - // return { - // next() { - // - // return iterator.next(); - // } - // } } return iterator; - // return { - // next() { - // - // return iterator.next(); - // } - // } } } diff --git a/dist/lib/parser/parse.js b/dist/lib/parser/parse.js index 8407a344..83fd0dfe 100644 --- a/dist/lib/parser/parse.js +++ b/dist/lib/parser/parse.js @@ -1,4 +1,4 @@ -import { isPseudo, isAtKeyword, isFunction, isNumber, isPercentage, isFlex, isDimension, parseDimension, isIdent, isHexColor, isHash, isIdentStart, mathFuncs, isColor, mediaTypes } from '../syntax/syntax.js'; +import { webkitPseudoAliasMap, isIdentStart, isIdent, mathFuncs, isColor, isHexColor, isPseudo, isAtKeyword, isFunction, isNumber, isPercentage, isFlex, isDimension, parseDimension, isHash, mediaTypes } from '../syntax/syntax.js'; import './utils/config.js'; import { EnumToken, funcLike, ValidationLevel } from '../ast/types.js'; import { minify, definedPropertySettings, combinators } from '../ast/minify.js'; @@ -13,6 +13,7 @@ import '../validation/parser/types.js'; import '../validation/parser/parse.js'; import { validateSelector } from '../validation/selector.js'; import { validateAtRule } from '../validation/atrule.js'; +import '../validation/syntaxes/complex-selector.js'; const urlTokenMatcher = /^(["']?)[a-zA-Z0-9_/.-][a-zA-Z0-9_/:.#?-]+(\1)$/; const trimWhiteSpace = [EnumToken.CommentTokenType, EnumToken.GtTokenType, EnumToken.GteTokenType, EnumToken.LtTokenType, EnumToken.LteTokenType, EnumToken.ColumnCombinatorTokenType]; @@ -29,51 +30,14 @@ const enumTokenHints = new Set([ EnumToken.StartMatchTokenType, EnumToken.EndMatchTokenType, EnumToken.IncludeMatchTokenType, EnumToken.DashMatchTokenType, EnumToken.ContainMatchTokenType, EnumToken.EOFTokenType ]); -const webkitPseudoAliasMap = { - '-webkit-autofill': 'autofill', - '-webkit-any': 'is', - '-moz-any': 'is', - '-webkit-border-after': 'border-block-end', - '-webkit-border-after-color': 'border-block-end-color', - '-webkit-border-after-style': 'border-block-end-style', - '-webkit-border-after-width': 'border-block-end-width', - '-webkit-border-before': 'border-block-start', - '-webkit-border-before-color': 'border-block-start-color', - '-webkit-border-before-style': 'border-block-start-style', - '-webkit-border-before-width': 'border-block-start-width', - '-webkit-border-end': 'border-inline-end', - '-webkit-border-end-color': 'border-inline-end-color', - '-webkit-border-end-style': 'border-inline-end-style', - '-webkit-border-end-width': 'border-inline-end-width', - '-webkit-border-start': 'border-inline-start', - '-webkit-border-start-color': 'border-inline-start-color', - '-webkit-border-start-style': 'border-inline-start-style', - '-webkit-border-start-width': 'border-inline-start-width', - '-webkit-box-align': 'align-items', - '-webkit-box-direction': 'flex-direction', - '-webkit-box-flex': 'flex-grow', - '-webkit-box-lines': 'flex-flow', - '-webkit-box-ordinal-group': 'order', - '-webkit-box-orient': 'flex-direction', - '-webkit-box-pack': 'justify-content', - '-webkit-column-break-after': 'break-after', - '-webkit-column-break-before': 'break-before', - '-webkit-column-break-inside': 'break-inside', - '-webkit-font-feature-settings': 'font-feature-settings', - '-webkit-hyphenate-character': 'hyphenate-character', - '-webkit-initial-letter': 'initial-letter', - '-webkit-margin-end': 'margin-block-end', - '-webkit-margin-start': 'margin-block-start', - '-webkit-padding-after': 'padding-block-end', - '-webkit-padding-before': 'padding-block-start', - '-webkit-padding-end': 'padding-inline-end', - '-webkit-padding-start': 'padding-inline-start', - '-webkit-min-device-pixel-ratio': 'min-resolution', - '-webkit-max-device-pixel-ratio': 'max-resolution' -}; function reject(reason) { throw new Error(reason ?? 'Parsing aborted'); } +/** + * parse css string + * @param iterator + * @param options + */ async function doParse(iterator, options = {}) { if (options.signal != null) { options.signal.addEventListener('abort', reject); @@ -95,6 +59,7 @@ async function doParse(iterator, options = {}) { setParent: true, removePrefix: false, validation: true, + lenient: true, ...options }; if (options.expandNestingRules) { @@ -133,9 +98,10 @@ async function doParse(iterator, options = {}) { } const iter = tokenize(iterator); let item; + const rawTokens = []; while (item = iter.next().value) { stats.bytesIn = item.bytesIn; - // + rawTokens.push(item); // doParse error if (item.hint != null && BadTokensTypes.includes(item.hint)) { // bad token @@ -145,7 +111,8 @@ async function doParse(iterator, options = {}) { tokens.push(item); } if (item.token == ';' || item.token == '{') { - let node = await parseNode(tokens, context, stats, options, errors, src, map); + let node = await parseNode(tokens, context, stats, options, errors, src, map, rawTokens); + rawTokens.length = 0; if (node != null) { // @ts-ignore stack.push(node); @@ -171,7 +138,8 @@ async function doParse(iterator, options = {}) { map = new Map; } else if (item.token == '}') { - await parseNode(tokens, context, stats, options, errors, src, map); + await parseNode(tokens, context, stats, options, errors, src, map, rawTokens); + rawTokens.length = 0; const previousNode = stack.pop(); // @ts-ignore context = stack[stack.length - 1] ?? ast; @@ -194,7 +162,8 @@ async function doParse(iterator, options = {}) { } } if (tokens.length > 0) { - await parseNode(tokens, context, stats, options, errors, src, map); + await parseNode(tokens, context, stats, options, errors, src, map, rawTokens); + rawTokens.length = 0; if (context != null && context.typ == EnumToken.InvalidRuleTokenType) { const index = context.chi.findIndex(node => node == context); if (index > -1) { @@ -206,6 +175,7 @@ async function doParse(iterator, options = {}) { const previousNode = stack.pop(); // @ts-ignore context = stack[stack.length - 1] ?? ast; + // remove empty nodes // @ts-ignore if (options.removeEmpty && previousNode != null && previousNode.chi.length == 0 && context.chi[context.chi.length - 1] == previousNode) { // @ts-ignore @@ -258,33 +228,6 @@ async function doParse(iterator, options = {}) { minify(ast, options, true, errors, false); } } - // if (options.setParent) { - // - // const nodes: Array = [ast]; - // let node: AstNode; - // - // while ((node = nodes.shift()!)) { - // - // // @ts-ignore - // if (node.chi.length > 0) { - // - // // @ts-ignore - // for (const child of node.chi) { - // - // if (child.parent != node) { - // - // Object.defineProperty(child, 'parent', {...definedPropertySettings, value: node}); - // } - // - // if ('chi' in child && child.chi.length > 0) { - // - // // @ts-ignore - // nodes.push(child); - // } - // } - // } - // } - // } const endTime = performance.now(); if (options.signal != null) { options.signal.removeEventListener('abort', reject); @@ -301,7 +244,17 @@ async function doParse(iterator, options = {}) { } }; } -async function parseNode(results, context, stats, options, errors, src, map) { +function getLastNode(context) { + let i = context.chi.length; + while (i--) { + if ([EnumToken.CommentTokenType, EnumToken.CDOCOMMTokenType, EnumToken.WhitespaceTokenType].includes(context.chi[i].typ)) { + continue; + } + return context.chi[i]; + } + return null; +} +async function parseNode(results, context, stats, options, errors, src, map, rawTokens) { let tokens = []; for (const t of results) { const node = getTokenType(t.token, t.hint); @@ -356,24 +309,6 @@ async function parseNode(results, context, stats, options, errors, src, map) { if (tokens[0]?.typ == EnumToken.AtRuleTokenType) { const atRule = tokens.shift(); const position = map.get(atRule); - // if (atRule.val == 'charset') { - // - // if (context.typ != EnumToken.StyleSheetNodeType || context.chi.some(t => t.typ != EnumToken.CDOCOMMTokenType && t.typ != EnumToken.CommentNodeType)) { - // - // errors.push({ - // action: 'drop', - // message: 'doParse: invalid @charset', - // location: {src, ...position} - // }); - // - // return null; - // } - // - // if (options.removeCharset) { - // - // return null; - // } - // } // @ts-ignore while ([EnumToken.WhitespaceTokenType].includes(tokens[0]?.typ)) { tokens.shift(); @@ -470,8 +405,43 @@ async function parseNode(results, context, stats, options, errors, src, map) { // https://www.w3.org/TR/css-nesting-1/#conditionals // allowed nesting at-rules // there must be a top level rule in the stack - if (atRule.val == 'charset' && options.removeCharset) { - return null; + if (atRule.val == 'charset') { + let spaces = 0; + // https://developer.mozilla.org/en-US/docs/Web/CSS/@charset + for (let k = 1; k < rawTokens.length; k++) { + if (rawTokens[k].hint == EnumToken.WhitespaceTokenType) { + spaces += rawTokens[k].len; + continue; + } + if (rawTokens[k].hint == EnumToken.CommentTokenType) { + continue; + } + if (rawTokens[k].hint == EnumToken.CDOCOMMTokenType) { + continue; + } + if (spaces > 1) { + errors.push({ + action: 'drop', + message: '@charset must have only one space', + // @ts-ignore + location: { src, ...(map.get(atRule) ?? position) } + }); + return null; + } + if (rawTokens[k].hint != EnumToken.StringTokenType || rawTokens[k].token[0] != '"') { + errors.push({ + action: 'drop', + message: '@charset expects a ""', + // @ts-ignore + location: { src, ...(map.get(atRule) ?? position) } + }); + return null; + } + break; + } + if (options.removeCharset) { + return null; + } } const t = parseAtRulePrelude(parseTokens(tokens, { minify: options.minify }), atRule); const raw = t.reduce((acc, curr) => { @@ -499,11 +469,36 @@ async function parseNode(results, context, stats, options, errors, src, map) { node.loc = loc; } if (options.validation) { - const valid = validateAtRule(node, options, context); + let isValid = true; + if (node.nam == 'else') { + const prev = getLastNode(context); + if (prev != null && prev.typ == EnumToken.AtRuleNodeType && ['when', 'else'].includes(prev.nam)) { + if (prev.nam == 'else') { + isValid = Array.isArray(prev.tokens) && prev.tokens.length > 0; + } + } + else { + isValid = false; + } + } + const valid = isValid ? validateAtRule(node, options, context) : { + valid: ValidationLevel.Drop, + node, + syntax: '@' + node.nam, + error: '@' + node.nam + ' not allowed here'}; if (valid.valid == ValidationLevel.Drop) { + errors.push({ + action: 'drop', + message: valid.error + ' - "' + tokens.reduce((acc, curr) => acc + renderToken(curr, { minify: false }), '') + '"', + // @ts-ignore + location: { src, ...(map.get(valid.node) ?? position) } + }); // @ts-ignore node.typ = EnumToken.InvalidAtRuleTokenType; } + else { + node.val = node.tokens.reduce((acc, curr) => acc + renderToken(curr, { minify: false, removeComments: true }), ''); + } } // @ts-ignore context.chi.push(node); @@ -682,7 +677,24 @@ async function parseNode(results, context, stats, options, errors, src, map) { }; const result = parseDeclarationNode(node, errors, src, position); if (result != null) { - if (options.validation) ; + // if (options.validation) { + // + // const valid: ValidationResult = validateDeclaration(result, options, context); + // + // console.error({valid}); + // + // if (valid.valid == ValidationLevel.Drop) { + // + // errors.push({ + // action: 'drop', + // message: valid.error + ' - "' + tokens.reduce((acc, curr) => acc + renderToken(curr, {minify: false}), '') + '"', + // // @ts-ignore + // location: {src, ...(map.get(valid.node) ?? position)} + // }); + // + // return null; + // } + // } // @ts-ignore context.chi.push(result); Object.defineProperty(result, 'parent', { ...definedPropertySettings, value: context }); @@ -691,6 +703,11 @@ async function parseNode(results, context, stats, options, errors, src, map) { } } } +/** + * parse at-rule prelude + * @param tokens + * @param atRule + */ function parseAtRulePrelude(tokens, atRule) { // @ts-ignore for (const { value, parent } of walkValues(tokens, null, null, true)) { @@ -759,17 +776,18 @@ function parseAtRulePrelude(tokens, atRule) { continue; } } - if (value.typ == EnumToken.ParensTokenType) { + if (value.typ == EnumToken.ParensTokenType || (value.typ == EnumToken.FunctionTokenType && ['media', 'supports', 'style', 'scroll-state'].includes(value.val))) { // @todo parse range and declarations // parseDeclaration(parent.chi); let i; let nameIndex = -1; let valueIndex = -1; + const dashedIdent = value.typ == EnumToken.FunctionTokenType && value.val == 'style'; for (let i = 0; i < value.chi.length; i++) { if (value.chi[i].typ == EnumToken.CommentTokenType || value.chi[i].typ == EnumToken.WhitespaceTokenType) { continue; } - if (value.chi[i].typ == EnumToken.IdenTokenType) { + if ((dashedIdent && value.chi[i].typ == EnumToken.DashedIdenTokenType) || value.chi[i].typ == EnumToken.IdenTokenType || value.chi[i].typ == EnumToken.FunctionTokenType || value.chi[i].typ == EnumToken.ColorTokenType) { nameIndex = i; } break; @@ -798,6 +816,13 @@ function parseAtRulePrelude(tokens, atRule) { ].includes(value.chi[valueIndex].typ)) { const val = value.chi.splice(valueIndex, 1)[0]; const node = value.chi.splice(nameIndex, 1)[0]; + // 'background' + // @ts-ignore + if (node.typ == EnumToken.ColorTokenType && node.kin == 'dpsys') { + // @ts-ignore + delete node.kin; + node.typ = EnumToken.IdenTokenType; + } while (value.chi[0]?.typ == EnumToken.WhitespaceTokenType) { value.chi.shift(); } @@ -815,6 +840,10 @@ function parseAtRulePrelude(tokens, atRule) { } return tokens; } +/** + * parse selector + * @param tokens + */ function parseSelector(tokens) { for (const { value, previousValue, nextValue, parent } of walkValues(tokens)) { if (value.typ == EnumToken.CommentTokenType || @@ -958,6 +987,11 @@ function parseSelector(tokens) { // // return doParse(`.x{${src}`, options).then((result: ParseResult) => (result.ast.chi[0]).chi.filter(t => t.typ == EnumToken.DeclarationNodeType)); // } +/** + * parse string + * @param src + * @param options + */ function parseString(src, options = { location: false }) { return parseTokens([...tokenize(src)].map(t => { const token = getTokenType(t.token, t.hint); @@ -1039,7 +1073,7 @@ function getTokenType(val, hint) { chi: [] }; } - if (['linear-gradient', 'radial-gradient', 'repeating-linear-gradient', 'repeating-radial-gradient', 'conic-gradient', 'image', 'image-set', 'element', 'cross-fade'].includes(val)) { + if (['linear-gradient', 'radial-gradient', 'repeating-linear-gradient', 'repeating-radial-gradient', 'conic-gradient', 'image', 'image-set', 'element', 'cross-fade', 'paint'].includes(val)) { return { typ: EnumToken.ImageFunctionTokenType, val, @@ -1139,6 +1173,11 @@ function getTokenType(val, hint) { val }; } +/** + * parse token list + * @param tokens + * @param options + */ function parseTokens(tokens, options = {}) { for (let i = 0; i < tokens.length; i++) { const t = tokens[i]; @@ -1474,4 +1513,4 @@ function parseTokens(tokens, options = {}) { return tokens; } -export { doParse, parseAtRulePrelude, parseSelector, parseString, parseTokens, urlTokenMatcher }; +export { doParse, parseSelector, parseString, parseTokens, urlTokenMatcher }; diff --git a/dist/lib/parser/tokenize.js b/dist/lib/parser/tokenize.js index f9f49b6f..b4a22182 100644 --- a/dist/lib/parser/tokenize.js +++ b/dist/lib/parser/tokenize.js @@ -16,7 +16,13 @@ function consumeWhiteSpace(parseInfo) { return count; } function pushToken(token, parseInfo, hint) { - const result = { token, hint, position: { ...parseInfo.position }, bytesIn: parseInfo.currentPosition.ind + 1 }; + const result = { + token, + len: parseInfo.currentPosition.ind - parseInfo.position.ind, + hint, + position: { ...parseInfo.position }, + bytesIn: parseInfo.currentPosition.ind + 1 + }; parseInfo.position.ind = parseInfo.currentPosition.ind; parseInfo.position.lin = parseInfo.currentPosition.lin; parseInfo.position.col = Math.max(parseInfo.currentPosition.col, 1); @@ -136,6 +142,10 @@ function next(parseInfo, count = 1) { } return char; } +/** + * tokenize css string + * @param stream + */ function* tokenize(stream) { const parseInfo = { stream, @@ -184,8 +194,10 @@ function* tokenize(stream) { buffer += value; } } - yield pushToken(buffer, parseInfo, EnumToken.BadCommentTokenType); - buffer = ''; + if (buffer.length > 0) { + yield pushToken(buffer, parseInfo, EnumToken.BadCommentTokenType); + buffer = ''; + } } break; case '&': diff --git a/dist/lib/renderer/color/hex.js b/dist/lib/renderer/color/hex.js index bc8deac8..c7c1f7ec 100644 --- a/dist/lib/renderer/color/hex.js +++ b/dist/lib/renderer/color/hex.js @@ -2,7 +2,7 @@ import { EnumToken } from '../../ast/types.js'; import '../../ast/minify.js'; import '../../ast/walk.js'; import '../../parser/parse.js'; -import { getNumber, minmax } from './color.js'; +import { minmax, getNumber } from './color.js'; import { hsl2rgb, hwb2rgb, cmyk2rgb, oklab2rgb, oklch2rgb, lab2rgb, lch2rgb } from './rgb.js'; import { NAMES_COLORS } from './utils/constants.js'; import { getComponents } from './utils/components.js'; diff --git a/dist/lib/renderer/color/hsl.js b/dist/lib/renderer/color/hsl.js index ab59f9d2..ae62079b 100644 --- a/dist/lib/renderer/color/hsl.js +++ b/dist/lib/renderer/color/hsl.js @@ -1,6 +1,6 @@ import { hwb2hsv } from './hsv.js'; import { getNumber } from './color.js'; -import { hex2rgb, lab2rgb, lch2rgb, oklab2rgb, oklch2rgb } from './rgb.js'; +import { lch2rgb, lab2rgb, oklch2rgb, oklab2rgb, hex2rgb } from './rgb.js'; import './utils/constants.js'; import { getComponents } from './utils/components.js'; import { EnumToken } from '../../ast/types.js'; diff --git a/dist/lib/renderer/color/hwb.js b/dist/lib/renderer/color/hwb.js index f28a0293..01e07e41 100644 --- a/dist/lib/renderer/color/hwb.js +++ b/dist/lib/renderer/color/hwb.js @@ -1,12 +1,12 @@ import { hsl2hsv } from './hsv.js'; import './utils/constants.js'; import { getComponents } from './utils/components.js'; -import { getNumber, getAngle } from './color.js'; +import { getAngle, getNumber } from './color.js'; import { EnumToken } from '../../ast/types.js'; import '../../ast/minify.js'; import '../../ast/walk.js'; import '../../parser/parse.js'; -import { lab2srgb, lch2srgb, oklab2srgb, oklch2srgb } from './srgb.js'; +import { lch2srgb, lab2srgb, oklch2srgb, oklab2srgb } from './srgb.js'; import '../sourcemap/lib/encode.js'; import '../../parser/utils/config.js'; diff --git a/dist/lib/renderer/color/lab.js b/dist/lib/renderer/color/lab.js index d62ff00a..3df9a0e3 100644 --- a/dist/lib/renderer/color/lab.js +++ b/dist/lib/renderer/color/lab.js @@ -1,7 +1,7 @@ import { e, k, D50 } from './utils/constants.js'; import { getComponents } from './utils/components.js'; import { srgb2xyz, xyzd502srgb } from './xyz.js'; -import { hex2srgb, rgb2srgb, hsl2srgb, hwb2srgb, oklch2srgb } from './srgb.js'; +import { oklch2srgb, hwb2srgb, hsl2srgb, rgb2srgb, hex2srgb } from './srgb.js'; import { getLCHComponents } from './lch.js'; import { OKLab_to_XYZ, getOKLABComponents } from './oklab.js'; import { getNumber } from './color.js'; diff --git a/dist/lib/renderer/color/lch.js b/dist/lib/renderer/color/lch.js index eeed9c9c..f0183310 100644 --- a/dist/lib/renderer/color/lch.js +++ b/dist/lib/renderer/color/lch.js @@ -5,7 +5,7 @@ import { EnumToken } from '../../ast/types.js'; import '../../ast/minify.js'; import '../../ast/walk.js'; import '../../parser/parse.js'; -import { srgb2lab, xyz2lab, hex2lab, rgb2lab, hsl2lab, hwb2lab, getLABComponents, oklab2lab, oklch2lab } from './lab.js'; +import { srgb2lab, xyz2lab, oklch2lab, oklab2lab, getLABComponents, hwb2lab, hsl2lab, rgb2lab, hex2lab } from './lab.js'; import '../sourcemap/lib/encode.js'; import '../../parser/utils/config.js'; diff --git a/dist/lib/renderer/color/oklab.js b/dist/lib/renderer/color/oklab.js index 7eb36f41..18e3cc31 100644 --- a/dist/lib/renderer/color/oklab.js +++ b/dist/lib/renderer/color/oklab.js @@ -1,7 +1,7 @@ import { multiplyMatrices } from './utils/matrix.js'; import './utils/constants.js'; import { getComponents } from './utils/components.js'; -import { srgb2lsrgbvalues, hex2srgb, rgb2srgb, hsl2srgb, hwb2srgb, lab2srgb, lch2srgb, lsrgb2srgbvalues } from './srgb.js'; +import { srgb2lsrgbvalues, lch2srgb, lab2srgb, hwb2srgb, hsl2srgb, rgb2srgb, hex2srgb, lsrgb2srgbvalues } from './srgb.js'; import { getNumber } from './color.js'; import { EnumToken } from '../../ast/types.js'; import '../../ast/minify.js'; @@ -106,7 +106,7 @@ function OKLab_to_sRGB(l, a, b) { 1.2914855378640917399 * b, 3); return lsrgb2srgbvalues( /* r: */ - +4.076741661347994 * L - + 4.076741661347994 * L - 3.307711590408193 * M + 0.230969928729428 * S, /* g: */ diff --git a/dist/lib/renderer/color/oklch.js b/dist/lib/renderer/color/oklch.js index 20bf9b56..1aa652a5 100644 --- a/dist/lib/renderer/color/oklch.js +++ b/dist/lib/renderer/color/oklch.js @@ -6,7 +6,7 @@ import '../../ast/minify.js'; import '../../ast/walk.js'; import '../../parser/parse.js'; import { lab2lchvalues } from './lch.js'; -import { srgb2oklab, hex2oklab, rgb2oklab, hsl2oklab, hwb2oklab, lab2oklab, lch2oklab, getOKLABComponents } from './oklab.js'; +import { srgb2oklab, lch2oklab, getOKLABComponents, lab2oklab, hwb2oklab, hsl2oklab, rgb2oklab, hex2oklab } from './oklab.js'; import '../sourcemap/lib/encode.js'; import '../../parser/utils/config.js'; diff --git a/dist/lib/renderer/color/p3.js b/dist/lib/renderer/color/p3.js index 91e4d58f..1563980c 100644 --- a/dist/lib/renderer/color/p3.js +++ b/dist/lib/renderer/color/p3.js @@ -1,4 +1,4 @@ -import { xyz2srgb, srgb2lsrgbvalues, lsrgb2srgbvalues } from './srgb.js'; +import { xyz2srgb, lsrgb2srgbvalues, srgb2lsrgbvalues } from './srgb.js'; import { multiplyMatrices } from './utils/matrix.js'; import './utils/constants.js'; import '../../ast/types.js'; diff --git a/dist/lib/renderer/color/prophotoRgb.js b/dist/lib/renderer/color/prophotoRgb.js index f93b8e2a..73a87466 100644 --- a/dist/lib/renderer/color/prophotoRgb.js +++ b/dist/lib/renderer/color/prophotoRgb.js @@ -1,4 +1,4 @@ -import { xyzd502srgb, srgb2xyz } from './xyz.js'; +import { srgb2xyz, xyzd502srgb } from './xyz.js'; import { XYZ_D65_to_D50 } from './xyzd50.js'; function prophotorgb2srgbvalues(r, g, b, a = null) { diff --git a/dist/lib/renderer/color/prophotorgb.js b/dist/lib/renderer/color/prophotorgb.js index f93b8e2a..73a87466 100644 --- a/dist/lib/renderer/color/prophotorgb.js +++ b/dist/lib/renderer/color/prophotorgb.js @@ -1,4 +1,4 @@ -import { xyzd502srgb, srgb2xyz } from './xyz.js'; +import { srgb2xyz, xyzd502srgb } from './xyz.js'; import { XYZ_D65_to_D50 } from './xyzd50.js'; function prophotorgb2srgbvalues(r, g, b, a = null) { diff --git a/dist/lib/renderer/color/rgb.js b/dist/lib/renderer/color/rgb.js index 2189731c..5ca886db 100644 --- a/dist/lib/renderer/color/rgb.js +++ b/dist/lib/renderer/color/rgb.js @@ -5,7 +5,7 @@ import '../../ast/minify.js'; import '../../ast/walk.js'; import '../../parser/parse.js'; import { expandHexValue } from './hex.js'; -import { hwb2srgb, hslvalues, hsl2srgbvalues, cmyk2srgb, oklab2srgb, oklch2srgb, lab2srgb, lch2srgb } from './srgb.js'; +import { hslvalues, hsl2srgbvalues, hwb2srgb, cmyk2srgb, oklab2srgb, oklch2srgb, lab2srgb, lch2srgb } from './srgb.js'; import '../sourcemap/lib/encode.js'; import '../../parser/utils/config.js'; diff --git a/dist/lib/renderer/color/srgb.js b/dist/lib/renderer/color/srgb.js index 46d525bc..9ce8f6c5 100644 --- a/dist/lib/renderer/color/srgb.js +++ b/dist/lib/renderer/color/srgb.js @@ -6,8 +6,8 @@ import '../../ast/minify.js'; import '../../ast/walk.js'; import '../../parser/parse.js'; import { expandHexValue } from './hex.js'; -import { lch2labvalues, getLABComponents, Lab_to_sRGB } from './lab.js'; -import { getOKLABComponents, OKLab_to_sRGB } from './oklab.js'; +import { lch2labvalues, Lab_to_sRGB, getLABComponents } from './lab.js'; +import { OKLab_to_sRGB, getOKLABComponents } from './oklab.js'; import { getLCHComponents } from './lch.js'; import { getOKLCHComponents } from './oklch.js'; import { XYZ_to_lin_sRGB } from './xyz.js'; diff --git a/dist/lib/renderer/color/utils/constants.js b/dist/lib/renderer/color/utils/constants.js index c3291cbe..f49e0244 100644 --- a/dist/lib/renderer/color/utils/constants.js +++ b/dist/lib/renderer/color/utils/constants.js @@ -28,7 +28,7 @@ const colorRange = { } }; const colorFuncColorSpace = ['srgb', 'srgb-linear', 'display-p3', 'prophoto-rgb', 'a98-rgb', 'rec2020', 'xyz', 'xyz-d65', 'xyz-d50']; -({ typ: EnumToken.IdenTokenType, val: 'none' }); +({ typ: EnumToken.IdenTokenType}); const D50 = [0.3457 / 0.3585, 1.00000, (1.0 - 0.3457 - 0.3585) / 0.3585]; const k = Math.pow(29, 3) / Math.pow(3, 3); const e = Math.pow(6, 3) / Math.pow(29, 3); diff --git a/dist/lib/renderer/color/xyz.js b/dist/lib/renderer/color/xyz.js index df61779e..6c3e0a5d 100644 --- a/dist/lib/renderer/color/xyz.js +++ b/dist/lib/renderer/color/xyz.js @@ -4,7 +4,7 @@ import '../../ast/types.js'; import '../../ast/minify.js'; import '../../ast/walk.js'; import '../../parser/parse.js'; -import { lsrgb2srgbvalues, srgb2lsrgbvalues } from './srgb.js'; +import { srgb2lsrgbvalues, lsrgb2srgbvalues } from './srgb.js'; import '../sourcemap/lib/encode.js'; import '../../parser/utils/config.js'; diff --git a/dist/lib/renderer/render.js b/dist/lib/renderer/render.js index 59802d84..17d496a8 100644 --- a/dist/lib/renderer/render.js +++ b/dist/lib/renderer/render.js @@ -41,17 +41,27 @@ function update(position, str) { } } } +/** + * render ast + * @param data + * @param options + */ function doRender(data, options = {}) { + const minify = options.minify ?? true; + const beautify = options.beautify ?? !minify; options = { - ...(options.minify ?? true ? { + ...(beautify ? { + indent: ' ', + newLine: '\n', + } : { indent: '', newLine: '', + }), + ...(minify ? { removeEmpty: true, removeComments: true } : { - indent: ' ', - newLine: '\n', - compress: false, + removeEmpty: false, removeComments: false, }), sourcemap: false, convertColor: true, expandNestingRules: false, preserveLicense: false, ...options }; @@ -119,7 +129,18 @@ function updateSourceMap(node, options, cache, sourcemap, position, str) { } update(position, str); } -// @ts-ignore +/** + * render ast node + * @param data + * @param options + * @param sourcemap + * @param position + * @param errors + * @param reducer + * @param cache + * @param level + * @param indents + */ function renderAstNode(data, options, sourcemap, position, errors, reducer, cache, level = 0, indents = []) { if (indents.length < level + 1) { indents.push(options.indent.repeat(level)); @@ -212,8 +233,15 @@ function renderAstNode(data, options, sourcemap, position, errors, reducer, cach // return renderToken(data as Token, options, cache, reducer, errors); throw new Error(`render: unexpected token ${JSON.stringify(data, null, 1)}`); } - return ''; } +/** + * render ast token + * @param token + * @param options + * @param cache + * @param reducer + * @param errors + */ function renderToken(token, options = {}, cache = Object.create(null), reducer, errors) { if (reducer == null) { reducer = function (acc, curr) { diff --git a/dist/lib/syntax/syntax.js b/dist/lib/syntax/syntax.js index 165d0644..0e813550 100644 --- a/dist/lib/syntax/syntax.js +++ b/dist/lib/syntax/syntax.js @@ -25,6 +25,341 @@ const mediaTypes = ['all', 'print', 'screen', 'aural', 'braille', 'embossed', 'handheld', 'projection', 'tty', 'tv', 'speech']; // https://www.w3.org/TR/css-values-4/#math-function const mathFuncs = ['calc', 'clamp', 'min', 'max', 'round', 'mod', 'rem', 'sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'atan2', 'pow', 'sqrt', 'hypot', 'log', 'exp', 'abs', 'sign']; +const webkitPseudoAliasMap = { + '-webkit-autofill': 'autofill', + '-webkit-any': 'is', + '-moz-any': 'is', + '-webkit-border-after': 'border-block-end', + '-webkit-border-after-color': 'border-block-end-color', + '-webkit-border-after-style': 'border-block-end-style', + '-webkit-border-after-width': 'border-block-end-width', + '-webkit-border-before': 'border-block-start', + '-webkit-border-before-color': 'border-block-start-color', + '-webkit-border-before-style': 'border-block-start-style', + '-webkit-border-before-width': 'border-block-start-width', + '-webkit-border-end': 'border-inline-end', + '-webkit-border-end-color': 'border-inline-end-color', + '-webkit-border-end-style': 'border-inline-end-style', + '-webkit-border-end-width': 'border-inline-end-width', + '-webkit-border-start': 'border-inline-start', + '-webkit-border-start-color': 'border-inline-start-color', + '-webkit-border-start-style': 'border-inline-start-style', + '-webkit-border-start-width': 'border-inline-start-width', + '-webkit-box-align': 'align-items', + '-webkit-box-direction': 'flex-direction', + '-webkit-box-flex': 'flex-grow', + '-webkit-box-lines': 'flex-flow', + '-webkit-box-ordinal-group': 'order', + '-webkit-box-orient': 'flex-direction', + '-webkit-box-pack': 'justify-content', + '-webkit-column-break-after': 'break-after', + '-webkit-column-break-before': 'break-before', + '-webkit-column-break-inside': 'break-inside', + '-webkit-font-feature-settings': 'font-feature-settings', + '-webkit-hyphenate-character': 'hyphenate-character', + '-webkit-initial-letter': 'initial-letter', + '-webkit-margin-end': 'margin-block-end', + '-webkit-margin-start': 'margin-block-start', + '-webkit-padding-after': 'padding-block-end', + '-webkit-padding-before': 'padding-block-start', + '-webkit-padding-end': 'padding-inline-end', + '-webkit-padding-start': 'padding-inline-start', + '-webkit-min-device-pixel-ratio': 'min-resolution', + '-webkit-max-device-pixel-ratio': 'max-resolution' +}; +// https://developer.mozilla.org/en-US/docs/Web/CSS/WebKit_Extensions +// https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-scrollbar +const webkitExtensions = new Set([ + '-webkit-app-region', + '-webkit-border-horizontal-spacing', + '-webkit-border-vertical-spacing', + '-webkit-box-reflect', + '-webkit-column-axis', + '-webkit-column-progression', + '-webkit-cursor-visibility', + '-webkit-font-smoothing', + '-webkit-hyphenate-limit-after', + '-webkit-hyphenate-limit-before', + '-webkit-hyphenate-limit-lines', + '-webkit-line-align', + '-webkit-line-box-contain', + '-webkit-line-clamp', + '-webkit-line-grid', + '-webkit-line-snap', + '-webkit-locale', + '-webkit-logical-height', + '-webkit-logical-width', + '-webkit-margin-after', + '-webkit-margin-before', + '-webkit-mask-box-image-outset', + '-webkit-mask-box-image-repeat', + '-webkit-mask-box-image-slice', + '-webkit-mask-box-image-source', + '-webkit-mask-box-image-width', + '-webkit-mask-box-image', + '-webkit-mask-composite', + '-webkit-mask-position-x', + '-webkit-mask-position-y', + '-webkit-mask-repeat-x', + '-webkit-mask-repeat-y', + '-webkit-mask-source-type', + '-webkit-max-logical-height', + '-webkit-max-logical-width', + '-webkit-min-logical-height', + '-webkit-min-logical-width', + '-webkit-nbsp-mode', + '-webkit-perspective-origin-x', + '-webkit-perspective-origin-y', + '-webkit-rtl-ordering', + '-webkit-tap-highlight-color', + '-webkit-text-decoration-skip', + '-webkit-text-decorations-in-effect', + '-webkit-text-fill-color', + '-webkit-text-security', + '-webkit-text-stroke-color', + '-webkit-text-stroke-width', + '-webkit-text-stroke', + '-webkit-text-zoom', + '-webkit-touch-callout', + '-webkit-transform-origin-x', + '-webkit-transform-origin-y', + '-webkit-transform-origin-z', + '-webkit-user-drag', + '-webkit-user-modify', + '-webkit-border-after', + '-webkit-border-after-color', + '-webkit-border-after-style', + '-webkit-border-after-width', + '-webkit-border-before', + '-webkit-border-before-color', + '-webkit-border-before-style', + '-webkit-border-before-width', + '-webkit-border-end', + '-webkit-border-end-color', + '-webkit-border-end-style', + '-webkit-border-end-width', + '-webkit-border-start', + '-webkit-border-start-color', + '-webkit-border-start-style', + '-webkit-border-start-width', + '-webkit-box-align', + '-webkit-box-direction', + '-webkit-box-flex-group', + '-webkit-box-flex', + '-webkit-box-lines', + '-webkit-box-ordinal-group', + '-webkit-box-orient', + '-webkit-box-pack', + '-webkit-column-break-after', + '-webkit-column-break-before', + '-webkit-column-break-inside', + '-webkit-font-feature-settings', + '-webkit-hyphenate-character', + '-webkit-initial-letter', + '-webkit-margin-end', + '-webkit-margin-start', + '-webkit-padding-after', + '-webkit-padding-before', + '-webkit-padding-end', + '-webkit-padding-start', + '-webkit-fill-available', + ':-webkit-animating-full-screen-transition', + ':-webkit-any', + ':-webkit-any-link', + ':-webkit-autofill', + ':-webkit-autofill-strong-password', + ':-webkit-drag', + ':-webkit-full-page-media', + ':-webkit-full-screen*', + ':-webkit-full-screen-ancestor', + ':-webkit-full-screen-document', + ':-webkit-full-screen-controls-hidden', + '::-webkit-file-upload-button*', + '::-webkit-inner-spin-button', + '::-webkit-input-placeholder', + '::-webkit-meter-bar', + '::-webkit-meter-even-less-good-value', + '::-webkit-meter-inner-element', + '::-webkit-meter-optimum-value', + '::-webkit-meter-suboptimum-value', + '::-webkit-progress-bar', + '::-webkit-progress-inner-element', + '::-webkit-progress-value', + '::-webkit-search-cancel-button', + '::-webkit-search-results-button', + '::-webkit-slider-runnable-track', + '::-webkit-slider-thumb', + '-webkit-animation', + '-webkit-device-pixel-ratio', + '-webkit-transform-2d', + '-webkit-transform-3d', + '-webkit-transition', + '::-webkit-scrollbar', + '::-webkit-scrollbar-button', + '::-webkit-scrollbar', + '::-webkit-scrollbar-thumb', + '::-webkit-scrollbar-track', + '::-webkit-scrollbar-track-piece', + '::-webkit-scrollbar:vertical', + '::-webkit-scrollbar-corner ', + '::-webkit-resizer', + ':vertical', + ':horizontal', +]); +// https://developer.mozilla.org/en-US/docs/Web/CSS/Mozilla_Extensions +const mozExtensions = new Set([ + '-moz-box-align', + '-moz-box-direction', + '-moz-box-flex', + '-moz-box-ordinal-group', + '-moz-box-orient', + '-moz-box-pack', + '-moz-float-edge', + '-moz-force-broken-image-icon', + '-moz-image-region', + '-moz-orient', + '-moz-osx-font-smoothing', + '-moz-user-focus', + '-moz-user-input', + '-moz-user-modify', + '-moz-animation', + '-moz-animation-delay', + '-moz-animation-direction', + '-moz-animation-duration', + '-moz-animation-fill-mode', + '-moz-animation-iteration-count', + '-moz-animation-name', + '-moz-animation-play-state', + '-moz-animation-timing-function', + '-moz-appearance', + '-moz-backface-visibility', + '-moz-background-clip', + '-moz-background-origin', + '-moz-background-inline-policy', + '-moz-background-size', + '-moz-border-end', + '-moz-border-end-color', + '-moz-border-end-style', + '-moz-border-end-width', + '-moz-border-image', + '-moz-border-start', + '-moz-border-start-color', + '-moz-border-start-style', + '-moz-border-start-width', + '-moz-box-sizing', + 'clip-path', + '-moz-column-count', + '-moz-column-fill', + '-moz-column-gap', + '-moz-column-width', + '-moz-column-rule', + '-moz-column-rule-width', + '-moz-column-rule-style', + '-moz-column-rule-color', + 'filter', + '-moz-font-feature-settings', + '-moz-font-language-override', + '-moz-hyphens', + '-moz-margin-end', + '-moz-margin-start', + 'mask', + '-moz-opacity', + '-moz-outline', + '-moz-outline-color', + '-moz-outline-offset', + '-moz-outline-style', + '-moz-outline-width', + '-moz-padding-end', + '-moz-padding-start', + '-moz-perspective', + '-moz-perspective-origin', + 'pointer-events', + '-moz-tab-size', + '-moz-text-align-last', + '-moz-text-decoration-color', + '-moz-text-decoration-line', + '-moz-text-decoration-style', + '-moz-text-size-adjust', + '-moz-transform', + '-moz-transform-origin', + '-moz-transform-style', + '-moz-transition', + '-moz-transition-delay', + '-moz-transition-duration', + '-moz-transition-property', + '-moz-transition-timing-function', + '-moz-user-select', + '-moz-initial', + '-moz-appearance', + '-moz-linear-gradient', + '-moz-radial-gradient', + '-moz-element', + '-moz-image-rect', + '::-moz-anonymous-block', + '::-moz-anonymous-positioned-block', + ':-moz-any', + ':-moz-any-link', + ':-moz-broken', + '::-moz-canvas', + '::-moz-color-swatch', + '::-moz-cell-content', + ':-moz-drag-over', + ':-moz-first-node', + '::-moz-focus-inner', + '::-moz-focus-outer', + ':-moz-full-screen', + ':-moz-full-screen-ancestor', + ':-moz-handler-blocked', + ':-moz-handler-crashed', + ':-moz-handler-disabled', + '::-moz-inline-table', + ':-moz-last-node', + '::-moz-list-bullet', + '::-moz-list-number', + ':-moz-loading', + ':-moz-locale-dir', + ':-moz-locale-dir', + ':-moz-lwtheme', + ':-moz-lwtheme-brighttext', + ':-moz-lwtheme-darktext', + '::-moz-meter-bar', + ':-moz-native-anonymous', + ':-moz-only-whitespace', + '::-moz-pagebreak', + '::-moz-pagecontent', + ':-moz-placeholder', + '::-moz-placeholder', + '::-moz-progress-bar', + '::-moz-range-progress', + '::-moz-range-thumb', + '::-moz-range-track', + ':-moz-read-only', + ':-moz-read-write', + '::-moz-scrolled-canvas', + '::-moz-scrolled-content', + '::-moz-selection', + ':-moz-submit-invalid', + ':-moz-suppressed', + '::-moz-svg-foreign-content', + '::-moz-table', + '::-moz-table-cell', + '::-moz-table-column', + '::-moz-table-column-group', + '::-moz-table-outer', + '::-moz-table-row', + '::-moz-table-row-group', + ':-moz-ui-invalid', + ':-moz-ui-valid', + ':-moz-user-disabled', + '::-moz-viewport', + '::-moz-viewport-scroll', + ':-moz-window-inactive', + '-moz-device-pixel-ratio', + '-moz-os-version', + '-moz-touch-enabled', + '-moz-windows-glass', + '-moz-alt-content' +]); function isLength(dimension) { return 'unit' in dimension && dimensionUnits.has(dimension.unit.toLowerCase()); } @@ -476,4 +811,4 @@ function isWhiteSpace(codepoint) { codepoint == 0xa || codepoint == 0xc || codepoint == 0xd; } -export { colorFontTech, fontFeaturesTech, fontFormat, isAngle, isAtKeyword, isColor, isColorspace, isDigit, isDimension, isFlex, isFrequency, isFunction, isHash, isHexColor, isHueInterpolationMethod, isIdent, isIdentCodepoint, isIdentStart, isLength, isNewLine, isNonPrintable, isNumber, isPercentage, isPolarColorspace, isPseudo, isRectangularOrthogonalColorspace, isResolution, isTime, isWhiteSpace, mathFuncs, mediaTypes, parseDimension }; +export { colorFontTech, fontFeaturesTech, fontFormat, isAngle, isAtKeyword, isColor, isColorspace, isDigit, isDimension, isFlex, isFrequency, isFunction, isHash, isHexColor, isHueInterpolationMethod, isIdent, isIdentCodepoint, isIdentStart, isLength, isNewLine, isNonPrintable, isNumber, isPercentage, isPolarColorspace, isPseudo, isRectangularOrthogonalColorspace, isResolution, isTime, isWhiteSpace, mathFuncs, mediaTypes, mozExtensions, parseDimension, webkitExtensions, webkitPseudoAliasMap }; diff --git a/dist/lib/validation/at-rules/container.js b/dist/lib/validation/at-rules/container.js new file mode 100644 index 00000000..48d06d97 --- /dev/null +++ b/dist/lib/validation/at-rules/container.js @@ -0,0 +1,353 @@ +import { ValidationLevel, EnumToken } from '../../ast/types.js'; +import '../../ast/minify.js'; +import '../../ast/walk.js'; +import '../../parser/parse.js'; +import '../../renderer/color/utils/constants.js'; +import '../../renderer/sourcemap/lib/encode.js'; +import '../../parser/utils/config.js'; +import { consumeWhitespace } from '../utils/whitespace.js'; +import { splitTokenList } from '../utils/list.js'; + +const validateContainerScrollStateFeature = validateContainerSizeFeature; +function validateAtRuleContainer(atRule, options, root) { + // media-query-list + if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected supports query list', + tokens: [] + }; + } + const result = validateAtRuleContainerQueryList(atRule.tokens, atRule); + if (result.valid == ValidationLevel.Drop) { + return result; + } + if (!('chi' in atRule)) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected at-rule body', + tokens: [] + }; + } + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: '', + tokens: [] + }; +} +function validateAtRuleContainerQueryList(tokens, atRule) { + if (tokens.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query list', + tokens + }; + } + let result = null; + let tokenType = null; + for (const queries of splitTokenList(tokens)) { + consumeWhitespace(queries); + if (queries.length == 0) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query list', + tokens + }; + } + result = null; + const match = []; + let token = null; + tokenType = null; + while (queries.length > 0) { + if (queries.length == 0) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query list', + tokens + }; + } + if (queries[0].typ == EnumToken.IdenTokenType) { + match.push(queries.shift()); + consumeWhitespace(queries); + } + if (queries.length == 0) { + break; + } + token = queries[0]; + if (token.typ == EnumToken.MediaFeatureNotTokenType) { + token = token.val; + } + if (token.typ != EnumToken.ParensTokenType && (token.typ != EnumToken.FunctionTokenType || !['scroll-state', 'style'].includes(token.val))) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: queries[0], + syntax: '@' + atRule.nam, + error: 'expected container query-in-parens', + tokens + }; + } + if (token.typ == EnumToken.ParensTokenType) { + result = validateContainerSizeFeature(token.chi, atRule); + } + else if (token.val == 'scroll-state') { + result = validateContainerScrollStateFeature(token.chi, atRule); + } + else { + result = validateContainerStyleFeature(token.chi, atRule); + } + if (result.valid == ValidationLevel.Drop) { + return result; + } + queries.shift(); + consumeWhitespace(queries); + if (queries.length == 0) { + break; + } + token = queries[0]; + if (token.typ != EnumToken.MediaFeatureAndTokenType && token.typ != EnumToken.MediaFeatureOrTokenType) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: queries[0], + syntax: '@' + atRule.nam, + error: 'expecting and/or container query token', + tokens + }; + } + if (tokenType == null) { + tokenType = token.typ; + } + if (tokenType != token.typ) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: queries[0], + syntax: '@' + atRule.nam, + error: 'mixing and/or not allowed at the same level', + tokens + }; + } + queries.shift(); + consumeWhitespace(queries); + if (queries.length == 0) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: queries[0], + syntax: '@' + atRule.nam, + error: 'expected container query-in-parens', + tokens + }; + } + } + } + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: '', + tokens + }; +} +function validateContainerStyleFeature(tokens, atRule) { + tokens = tokens.slice(); + consumeWhitespace(tokens); + if (tokens.length == 1) { + if (tokens[0].typ == EnumToken.ParensTokenType) { + return validateContainerStyleFeature(tokens[0].chi, atRule); + } + if ([EnumToken.DashedIdenTokenType, EnumToken.IdenTokenType].includes(tokens[0].typ) || + (tokens[0].typ == EnumToken.MediaQueryConditionTokenType && tokens[0].op.typ == EnumToken.ColonTokenType)) { + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: '', + tokens + }; + } + } + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query features', + tokens + }; +} +function validateContainerSizeFeature(tokens, atRule) { + tokens = tokens.slice(); + consumeWhitespace(tokens); + if (tokens.length == 0) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query features', + tokens + }; + } + if (tokens.length == 1) { + const token = tokens[0]; + if (token.typ == EnumToken.MediaFeatureNotTokenType) { + return validateContainerSizeFeature([token.val], atRule); + } + if (token.typ == EnumToken.ParensTokenType) { + return validateAtRuleContainerQueryStyleInParams(token.chi, atRule); + } + if (![EnumToken.DashedIdenTokenType, EnumToken.MediaQueryConditionTokenType].includes(tokens[0].typ)) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query features', + tokens + }; + } + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: '', + tokens + }; + } + return validateAtRuleContainerQueryStyleInParams(tokens, atRule); +} +function validateAtRuleContainerQueryStyleInParams(tokens, atRule) { + tokens = tokens.slice(); + consumeWhitespace(tokens); + if (tokens.length == 0) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query features', + tokens + }; + } + let token = tokens[0]; + let tokenType = null; + let result = null; + while (tokens.length > 0) { + token = tokens[0]; + if (token.typ == EnumToken.MediaFeatureNotTokenType) { + token = token.val; + } + if (tokens[0].typ != EnumToken.ParensTokenType) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query-in-parens', + tokens + }; + } + const slices = tokens[0].chi.slice(); + consumeWhitespace(slices); + if (slices.length == 1) { + if ([EnumToken.MediaFeatureNotTokenType, EnumToken.ParensTokenType].includes(slices[0].typ)) { + result = validateAtRuleContainerQueryStyleInParams(slices, atRule); + if (result.valid == ValidationLevel.Drop) { + return result; + } + } + else if (![EnumToken.DashedIdenTokenType, EnumToken.MediaQueryConditionTokenType].includes(slices[0].typ)) { + result = { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query features', + tokens + }; + } + } + else { + result = validateAtRuleContainerQueryStyleInParams(slices, atRule); + if (result.valid == ValidationLevel.Drop) { + return result; + } + } + tokens.shift(); + consumeWhitespace(tokens); + if (tokens.length == 0) { + break; + } + if (![EnumToken.MediaFeatureAndTokenType, EnumToken.MediaFeatureOrTokenType].includes(tokens[0].typ)) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: '@' + atRule.nam, + error: 'expecting and/or container query token', + tokens + }; + } + if (tokenType == null) { + tokenType = tokens[0].typ; + } + if (tokenType != tokens[0].typ) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: '@' + atRule.nam, + error: 'mixing and/or not allowed at the same level', + tokens + }; + } + tokens.shift(); + consumeWhitespace(tokens); + if (tokens.length == 0) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: '@' + atRule.nam, + error: 'expected container query-in-parens', + tokens + }; + } + } + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: '', + tokens + }; +} + +export { validateAtRuleContainer }; diff --git a/dist/lib/validation/at-rules/counter-style.js b/dist/lib/validation/at-rules/counter-style.js index e5cd6e0f..a51c92f4 100644 --- a/dist/lib/validation/at-rules/counter-style.js +++ b/dist/lib/validation/at-rules/counter-style.js @@ -15,7 +15,7 @@ function validateAtRuleCounterStyle(atRule, options, root) { matches: [], node: atRule, syntax: '@counter-style', - error: 'expected media query list', + error: 'expected counter style name', tokens: [] }; } @@ -23,7 +23,7 @@ function validateAtRuleCounterStyle(atRule, options, root) { if (tokens.length == 0) { // @ts-ignore return { - valid: ValidationLevel.Valid, + valid: ValidationLevel.Drop, matches: [], node: atRule, syntax: '@counter-style', diff --git a/dist/lib/validation/at-rules/custom-media.js b/dist/lib/validation/at-rules/custom-media.js new file mode 100644 index 00000000..48f61b54 --- /dev/null +++ b/dist/lib/validation/at-rules/custom-media.js @@ -0,0 +1,52 @@ +import { ValidationLevel, EnumToken } from '../../ast/types.js'; +import '../../ast/minify.js'; +import '../../ast/walk.js'; +import '../../parser/parse.js'; +import '../../renderer/color/utils/constants.js'; +import '../../renderer/sourcemap/lib/encode.js'; +import '../../parser/utils/config.js'; +import { consumeWhitespace } from '../utils/whitespace.js'; +import { validateAtRuleMediaQueryList } from './media.js'; + +function validateAtRuleCustomMedia(atRule, options, root) { + // media-query-list + if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Valid, + matches: [], + node: null, + syntax: null, + error: '', + tokens: [] + }; + } + const queries = atRule.tokens.slice(); + consumeWhitespace(queries); + if (queries.length == 0 || queries[0].typ != EnumToken.DashedIdenTokenType) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@custom-media', + error: 'expecting dashed identifier', + tokens: [] + }; + } + queries.shift(); + const result = validateAtRuleMediaQueryList(queries, atRule); + if (result.valid == ValidationLevel.Drop) { + atRule.tokens = []; + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@custom-media', + error: '', + tokens: [] + }; + } + return result; +} + +export { validateAtRuleCustomMedia }; diff --git a/dist/lib/validation/at-rules/else.js b/dist/lib/validation/at-rules/else.js new file mode 100644 index 00000000..a087e958 --- /dev/null +++ b/dist/lib/validation/at-rules/else.js @@ -0,0 +1,5 @@ +import { validateAtRuleWhen } from './when.js'; + +const validateAtRuleElse = validateAtRuleWhen; + +export { validateAtRuleElse }; diff --git a/dist/lib/validation/at-rules/font-feature-values.js b/dist/lib/validation/at-rules/font-feature-values.js index 223c2753..200a24a9 100644 --- a/dist/lib/validation/at-rules/font-feature-values.js +++ b/dist/lib/validation/at-rules/font-feature-values.js @@ -7,6 +7,9 @@ import '../../renderer/sourcemap/lib/encode.js'; import '../../parser/utils/config.js'; import { validateFamilyName } from '../syntaxes/family-name.js'; import '../syntaxes/complex-selector.js'; +import '../parser/types.js'; +import '../parser/parse.js'; +import '../config.js'; function validateAtRuleFontFeatureValues(atRule, options, root) { if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { diff --git a/dist/lib/validation/at-rules/import.js b/dist/lib/validation/at-rules/import.js index 74be5186..5ee98dd6 100644 --- a/dist/lib/validation/at-rules/import.js +++ b/dist/lib/validation/at-rules/import.js @@ -10,6 +10,9 @@ import { validateAtRuleMediaQueryList } from './media.js'; import { consumeWhitespace } from '../utils/whitespace.js'; import { validateLayerName } from '../syntaxes/layer-name.js'; import '../syntaxes/complex-selector.js'; +import '../parser/types.js'; +import '../parser/parse.js'; +import '../config.js'; function validateAtRuleImport(atRule, options, root) { if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { diff --git a/dist/lib/validation/at-rules/layer.js b/dist/lib/validation/at-rules/layer.js index baa20816..521e4d40 100644 --- a/dist/lib/validation/at-rules/layer.js +++ b/dist/lib/validation/at-rules/layer.js @@ -7,6 +7,9 @@ import '../../renderer/sourcemap/lib/encode.js'; import '../../parser/utils/config.js'; import { validateLayerName } from '../syntaxes/layer-name.js'; import '../syntaxes/complex-selector.js'; +import '../parser/types.js'; +import '../parser/parse.js'; +import '../config.js'; function validateAtRuleLayer(atRule, options, root) { // media-query-list diff --git a/dist/lib/validation/at-rules/media.js b/dist/lib/validation/at-rules/media.js index 657e6217..8f7de66f 100644 --- a/dist/lib/validation/at-rules/media.js +++ b/dist/lib/validation/at-rules/media.js @@ -13,15 +13,28 @@ function validateAtRuleMedia(atRule, options, root) { if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { // @ts-ignore return { - valid: ValidationLevel.Drop, + valid: ValidationLevel.Valid, + matches: [], + node: null, + syntax: null, + error: '', + tokens: [] + }; + } + let result = null; + const slice = atRule.tokens.slice(); + consumeWhitespace(slice); + if (slice.length == 0) { + return { + valid: ValidationLevel.Valid, matches: [], node: atRule, syntax: '@media', - error: 'expected media query list', + error: '', tokens: [] }; } - const result = validateAtRuleMediaQueryList(atRule.tokens, atRule); + result = validateAtRuleMediaQueryList(atRule.tokens, atRule); if (result.valid == ValidationLevel.Drop) { return result; } @@ -47,10 +60,20 @@ function validateAtRuleMedia(atRule, options, root) { }; } function validateAtRuleMediaQueryList(tokenList, atRule) { - for (const tokens of splitTokenList(tokenList)) { + const split = splitTokenList(tokenList); + const matched = []; + let result = null; + let previousToken; + let mediaFeatureType; + for (let i = 0; i < split.length; i++) { + const tokens = split[i].slice(); + const match = []; + result = null; + mediaFeatureType = null; + previousToken = null; if (tokens.length == 0) { // @ts-ignore - return { + result = { valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? atRule, @@ -58,26 +81,38 @@ function validateAtRuleMediaQueryList(tokenList, atRule) { error: 'unexpected token', tokens: [] }; + continue; } - let previousToken = null; while (tokens.length > 0) { - // media-condition - if (validateMediaCondition(tokens[0])) { - previousToken = tokens[0]; - tokens.shift(); - } - // media-type - else if (validateMediaFeature(tokens[0])) { - previousToken = tokens[0]; - tokens.shift(); + previousToken = tokens[0]; + // media-condition | media-type | custom-media + if (!(validateMediaCondition(tokens[0], atRule) || validateMediaFeature(tokens[0]) || validateCustomMediaCondition(tokens[0], atRule))) { + if (tokens[0].typ == EnumToken.ParensTokenType) { + result = validateAtRuleMediaQueryList(tokens[0].chi, atRule); + } + else { + result = { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0] ?? atRule, + syntax: '@media', + error: 'expecting media feature or media condition', + tokens: [] + }; + } + if (result.valid == ValidationLevel.Drop) { + break; + } + result = null; } + match.push(tokens.shift()); if (tokens.length == 0) { break; } if (!consumeWhitespace(tokens)) { if (previousToken?.typ != EnumToken.ParensTokenType) { // @ts-ignore - return { + result = { valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? atRule, @@ -85,11 +120,12 @@ function validateAtRuleMediaQueryList(tokenList, atRule) { error: 'expected media query list', tokens: [] }; + break; } } - if (![EnumToken.MediaFeatureOrTokenType, EnumToken.MediaFeatureAndTokenType].includes(tokens[0].typ)) { + else if (![EnumToken.MediaFeatureOrTokenType, EnumToken.MediaFeatureAndTokenType].includes(tokens[0].typ)) { // @ts-ignore - return { + result = { valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? atRule, @@ -97,31 +133,70 @@ function validateAtRuleMediaQueryList(tokenList, atRule) { error: 'expected and/or', tokens: [] }; + break; + } + if (mediaFeatureType == null) { + mediaFeatureType = tokens[0]; } - if (tokens.length == 1) { + if (mediaFeatureType.typ != tokens[0].typ) { // @ts-ignore - return { + result = { valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? atRule, syntax: '@media', - error: 'expected media-condition', + error: 'mixing and/or not allowed at the same level', tokens: [] }; + break; } - tokens.shift(); - if (!consumeWhitespace(tokens)) { + match.push({ typ: EnumToken.WhitespaceTokenType }, tokens.shift()); + consumeWhitespace(tokens); + if (tokens.length == 0) { // @ts-ignore - return { + result = { valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? atRule, syntax: '@media', - error: 'expected whitespace', + error: 'expected media-condition', tokens: [] }; + break; } + match.push({ typ: EnumToken.WhitespaceTokenType }); } + if (result == null && match.length > 0) { + matched.push(match); + } + } + if (result != null) { + return result; + } + if (matched.length == 0) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@media', + error: 'expected media query list', + tokens: [] + }; + } + tokenList.length = 0; + let hasAll = false; + for (let i = 0; i < matched.length; i++) { + if (tokenList.length > 0) { + tokenList.push({ typ: EnumToken.CommaTokenType }); + } + if (matched[i].length == 1 && matched.length > 1 && matched[i][0].typ == EnumToken.MediaFeatureTokenType && matched[i][0].val == 'all') { + hasAll = true; + continue; + } + tokenList.push(...matched[i]); + } + if (hasAll && tokenList.length == 0) { + tokenList.push({ typ: EnumToken.MediaFeatureTokenType, val: 'all' }); } // @ts-ignore return { @@ -133,9 +208,9 @@ function validateAtRuleMediaQueryList(tokenList, atRule) { tokens: [] }; } -function validateMediaCondition(token) { +function validateCustomMediaCondition(token, atRule) { if (token.typ == EnumToken.MediaFeatureNotTokenType) { - return validateMediaCondition(token.val); + return validateMediaCondition(token.val, atRule); } if (token.typ != EnumToken.ParensTokenType) { return false; @@ -144,11 +219,24 @@ function validateMediaCondition(token) { if (chi.length != 1) { return false; } + return chi[0].typ == EnumToken.DashedIdenTokenType; +} +function validateMediaCondition(token, atRule) { + if (token.typ == EnumToken.MediaFeatureNotTokenType) { + return validateMediaCondition(token.val, atRule); + } + if (token.typ != EnumToken.ParensTokenType && !(['when', 'else'].includes(atRule.nam) && token.typ == EnumToken.FunctionTokenType && ['media', 'supports'].includes(token.val))) { + return false; + } + const chi = token.chi.filter((t) => t.typ != EnumToken.CommentTokenType && t.typ != EnumToken.WhitespaceTokenType); + if (chi.length != 1) { + return false; + } if (chi[0].typ == EnumToken.IdenTokenType) { return true; } if (chi[0].typ == EnumToken.MediaFeatureNotTokenType) { - return validateMediaCondition(chi[0].val); + return validateMediaCondition(chi[0].val, atRule); } if (chi[0].typ == EnumToken.MediaQueryConditionTokenType) { return chi[0].l.typ == EnumToken.IdenTokenType; @@ -163,4 +251,4 @@ function validateMediaFeature(token) { return val.typ == EnumToken.MediaFeatureTokenType; } -export { validateAtRuleMedia, validateAtRuleMediaQueryList }; +export { validateAtRuleMedia, validateAtRuleMediaQueryList, validateMediaCondition, validateMediaFeature }; diff --git a/dist/lib/validation/at-rules/supports.js b/dist/lib/validation/at-rules/supports.js index 2977d08f..f6797492 100644 --- a/dist/lib/validation/at-rules/supports.js +++ b/dist/lib/validation/at-rules/supports.js @@ -19,7 +19,7 @@ function validateAtRuleSupports(atRule, options, root) { valid: ValidationLevel.Drop, matches: [], node: atRule, - syntax: '@supports', + syntax: '@' + atRule.nam, error: 'expected supports query list', tokens: [] }; @@ -37,7 +37,7 @@ function validateAtRuleSupports(atRule, options, root) { valid: ValidationLevel.Drop, matches: [], node: atRule, - syntax: '@supports', + syntax: '@' + atRule.nam, error: 'expected at-rule body', tokens: [] }; @@ -47,7 +47,7 @@ function validateAtRuleSupports(atRule, options, root) { valid: ValidationLevel.Valid, matches: [], node: atRule, - syntax: '@supports', + syntax: '@' + atRule.nam, error: '', tokens: [] }; @@ -60,7 +60,7 @@ function validateAtRuleSupportsConditions(atRule, tokenList) { valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? atRule, - syntax: '@supports', + syntax: '@' + atRule.nam, error: 'unexpected token', tokens: [] }; @@ -94,7 +94,7 @@ function validateAtRuleSupportsConditions(atRule, tokenList) { valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? previousToken ?? atRule, - syntax: '@supports', + syntax: '@' + atRule.nam, error: 'expected whitespace', tokens: [] }; @@ -106,7 +106,7 @@ function validateAtRuleSupportsConditions(atRule, tokenList) { valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? atRule, - syntax: '@supports', + syntax: '@' + atRule.nam, error: 'expected and/or', tokens: [] }; @@ -117,7 +117,7 @@ function validateAtRuleSupportsConditions(atRule, tokenList) { valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? atRule, - syntax: '@supports', + syntax: '@' + atRule.nam, error: 'expected supports-condition', tokens: [] }; @@ -129,7 +129,7 @@ function validateAtRuleSupportsConditions(atRule, tokenList) { valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? atRule, - syntax: '@supports', + syntax: '@' + atRule.nam, error: 'expected whitespace', tokens: [] }; @@ -142,13 +142,13 @@ function validateSupportCondition(atRule, token) { if (token.typ == EnumToken.MediaFeatureNotTokenType) { return validateSupportCondition(atRule, token.val); } - if (token.typ != EnumToken.ParensTokenType) { + if (token.typ != EnumToken.ParensTokenType && !(['when', 'else'].includes(atRule.nam) && token.typ == EnumToken.FunctionTokenType && ['supports', 'font-format', 'font-tech'].includes(token.val))) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], node: token, - syntax: '@supports', + syntax: '@' + atRule.nam, error: 'expected supports condition-in-parens', tokens: [] }; @@ -163,7 +163,7 @@ function validateSupportCondition(atRule, token) { valid: ValidationLevel.Valid, matches: [], node: null, - syntax: '@supports', + syntax: '@' + atRule.nam, error: '', tokens: [] }; diff --git a/dist/lib/validation/at-rules/when.js b/dist/lib/validation/at-rules/when.js new file mode 100644 index 00000000..7ed9ee6b --- /dev/null +++ b/dist/lib/validation/at-rules/when.js @@ -0,0 +1,178 @@ +import { ValidationLevel, EnumToken } from '../../ast/types.js'; +import '../../ast/minify.js'; +import '../../ast/walk.js'; +import '../../parser/parse.js'; +import '../../renderer/color/utils/constants.js'; +import '../../renderer/sourcemap/lib/encode.js'; +import '../../parser/utils/config.js'; +import { consumeWhitespace } from '../utils/whitespace.js'; +import { splitTokenList } from '../utils/list.js'; +import { validateMediaFeature, validateMediaCondition } from './media.js'; +import { validateSupportCondition } from './supports.js'; + +function validateAtRuleWhen(atRule, options, root) { + const slice = Array.isArray(atRule.tokens) ? atRule.tokens.slice() : []; + consumeWhitespace(slice); + if (slice.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@when', + error: '', + tokens: [] + }; + } + const result = validateAtRuleWhenQueryList(atRule.tokens, atRule); + if (result.valid == ValidationLevel.Drop) { + return result; + } + if (!('chi' in atRule)) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@when', + error: 'expected at-rule body', + tokens: [] + }; + } + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@when', + error: '', + tokens: result.tokens + }; +} +// media() = media( [ | | ] ) +// supports() = supports( ) +function validateAtRuleWhenQueryList(tokenList, atRule) { + const matched = []; + let result = null; + for (const split of splitTokenList(tokenList)) { + const match = []; + result = null; + consumeWhitespace(split); + if (split.length == 0) { + continue; + } + while (split.length > 0) { + if (split[0].typ != EnumToken.FunctionTokenType || !['media', 'supports', 'font-tech', 'font-format'].includes(split[0].val)) { + result = { + valid: ValidationLevel.Drop, + matches: [], + node: split[0] ?? atRule, + syntax: '@when', + error: 'unexpected token', + tokens: [] + }; + break; + } + const chi = split[0].chi.slice(); + consumeWhitespace(chi); + if (split[0].val == 'media') { + // result = valida + if (chi.length != 1 || !(validateMediaFeature(chi[0]) || validateMediaCondition(split[0], atRule))) { + result = { + valid: ValidationLevel.Drop, + matches: [], + node: split[0] ?? atRule, + syntax: 'media( [ | | ] )', + error: 'unexpected token', + tokens: [] + }; + break; + } + } + else if (['supports', 'font-tech', 'font-format'].includes(split[0].val)) { + // result = valida + if (!validateSupportCondition(atRule, split[0])) { + result = { + valid: ValidationLevel.Drop, + matches: [], + node: split[0] ?? atRule, + syntax: 'media( [ | | ] )', + error: 'unexpected token', + tokens: [] + }; + break; + } + } + if (match.length > 0) { + match.push({ typ: EnumToken.WhitespaceTokenType }); + } + match.push(split.shift()); + consumeWhitespace(split); + if (split.length == 0) { + break; + } + if (![EnumToken.MediaFeatureAndTokenType, EnumToken.MediaFeatureOrTokenType].includes(split[0].typ)) { + result = { + valid: ValidationLevel.Drop, + matches: [], + node: split[0] ?? atRule, + syntax: '@when', + error: 'expecting and/or media-condition', + tokens: [] + }; + break; + } + if (match.length > 0) { + match.push({ typ: EnumToken.WhitespaceTokenType }); + } + match.push(split.shift()); + consumeWhitespace(split); + if (split.length == 0) { + result = { + valid: ValidationLevel.Drop, + matches: [], + node: split[0] ?? atRule, + syntax: '@when', + error: 'expecting media-condition', + tokens: [] + }; + break; + } + } + if (result == null && match.length > 0) { + matched.push(match); + } + } + if (result != null) { + return result; + } + if (matched.length == 0) { + return { + valid: ValidationLevel.Drop, + matches: [], + // @ts-ignore + node: result?.node ?? atRule, + syntax: '@when', + error: 'invalid at-rule body', + tokens: [] + }; + } + tokenList.length = 0; + for (const match of matched) { + if (tokenList.length > 0) { + tokenList.push({ + typ: EnumToken.CommaTokenType + }); + } + tokenList.push(...match); + } + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@when', + error: '', + tokens: tokenList + }; +} + +export { validateAtRuleWhen, validateAtRuleWhenQueryList }; diff --git a/dist/lib/validation/atrule.js b/dist/lib/validation/atrule.js index 45c11e56..7e92eb09 100644 --- a/dist/lib/validation/atrule.js +++ b/dist/lib/validation/atrule.js @@ -5,7 +5,7 @@ import '../parser/parse.js'; import '../renderer/color/utils/constants.js'; import '../renderer/sourcemap/lib/encode.js'; import '../parser/utils/config.js'; -import { getParsedSyntax, getSyntaxConfig } from './config.js'; +import { getSyntaxConfig, getParsedSyntax } from './config.js'; import { validateAtRuleMedia } from './at-rules/media.js'; import { validateAtRuleCounterStyle } from './at-rules/counter-style.js'; import { validateAtRulePage } from './at-rules/page.js'; @@ -17,6 +17,10 @@ import { validateAtRuleFontFeatureValues } from './at-rules/font-feature-values. import { validateAtRuleNamespace } from './at-rules/namespace.js'; import { validateAtRuleDocument } from './at-rules/document.js'; import { validateAtRuleKeyframes } from './at-rules/keyframes.js'; +import { validateAtRuleWhen } from './at-rules/when.js'; +import { validateAtRuleElse } from './at-rules/else.js'; +import { validateAtRuleContainer } from './at-rules/container.js'; +import { validateAtRuleCustomMedia } from './at-rules/custom-media.js'; function validateAtRule(atRule, options, root) { if (atRule.nam == 'charset') { @@ -60,9 +64,21 @@ function validateAtRule(atRule, options, root) { if (atRule.nam == 'namespace') { return validateAtRuleNamespace(atRule); } + if (atRule.nam == 'when') { + return validateAtRuleWhen(atRule); + } + if (atRule.nam == 'else') { + return validateAtRuleElse(atRule); + } + if (atRule.nam == 'container') { + return validateAtRuleContainer(atRule); + } if (atRule.nam == 'document') { return validateAtRuleDocument(atRule); } + if (atRule.nam == 'custom-media') { + return validateAtRuleCustomMedia(atRule); + } if (['position-try', 'property', 'font-palette-values'].includes(atRule.nam)) { if (!('tokens' in atRule)) { return { @@ -132,15 +148,14 @@ function validateAtRule(atRule, options, root) { } } if (!(name in config.atRules)) { - // if (root?.typ == EnumToken.AtRuleNodeType) { - // - // const syntaxes: ValidationToken = (getParsedSyntax(ValidationSyntaxGroupEnum.AtRules, '@' + (root as AstAtRule).nam) as ValidationToken[])?.[0]; - // - // if ('chi' in syntaxes) { - // - // return validateSyntax(syntaxes.chi as ValidationToken[], [atRule], root, options); - // } - // } + if (options.lenient) { + return { + valid: ValidationLevel.Lenient, + node: atRule, + syntax: null, + error: '' + }; + } return { valid: ValidationLevel.Drop, node: atRule, diff --git a/dist/lib/validation/config.js b/dist/lib/validation/config.js index 9d7cfbd3..d8dfb7bf 100644 --- a/dist/lib/validation/config.js +++ b/dist/lib/validation/config.js @@ -1,6 +1,6 @@ import config from './config.json.js'; import './parser/types.js'; -import { parseSyntax, walkValidationToken, renderSyntax } from './parser/parse.js'; +import { parseSyntax } from './parser/parse.js'; const parsedSyntaxes = new Map(); Object.freeze(config); @@ -9,25 +9,30 @@ function getSyntaxConfig() { return config; } function getParsedSyntax(group, key) { - if (!(key in config[group])) { - const matches = key.match(/(@?)(-[a-zA-Z]+)-(.*?)$/); - if (matches != null) { - key = matches[1] + matches[3]; - } - if (!(key in config[group])) { - return null; + // @ts-ignore + let obj = config[group]; + const keys = Array.isArray(key) ? key : [key]; + for (let i = 0; i < keys.length; i++) { + key = keys[i]; + if (!(key in obj)) { + if ((i == 0 && key.charAt(0) == '@') || key.charAt(0) == '-') { + const matches = key.match(/^(@?)(-[a-zA-Z]+)-(.*?)$/); + if (matches != null) { + key = matches[1] + matches[3]; + } + } + if (!(key in obj)) { + return null; + } } + // @ts-ignore + obj = obj[key]; } - const index = group + '.' + key; + const index = group + '.' + keys.join('.'); // @ts-ignore if (!parsedSyntaxes.has(index)) { // @ts-ignore - const syntax = parseSyntax(config[group][key].syntax); - for (const node of syntax.chi) { - for (const { token, parent } of walkValidationToken(node)) { - token.text = renderSyntax(token); - } - } + const syntax = parseSyntax(obj.syntax); // @ts-ignore parsedSyntaxes.set(index, syntax.chi); } diff --git a/dist/lib/validation/config.json.js b/dist/lib/validation/config.json.js index 0de7cd81..92915d39 100644 --- a/dist/lib/validation/config.json.js +++ b/dist/lib/validation/config.json.js @@ -995,9 +995,6 @@ var declarations = { "inline-size": { syntax: "<'width'>" }, - "input-security": { - syntax: "auto | none" - }, inset: { syntax: "<'top'>{1,4}" }, @@ -1556,6 +1553,9 @@ var declarations = { "shape-rendering": { syntax: "auto | optimizeSpeed | crispEdges | geometricPrecision" }, + "speak-as": { + syntax: "normal | spell-out || digits || [ literal-punctuation | no-punctuation ]" + }, "stop-color": { syntax: "<'color'>" }, @@ -1668,7 +1668,7 @@ var declarations = { syntax: "none | auto | " }, "text-spacing-trim": { - syntax: "space-all | normal | space-first | trim-start | trim-both | trim-all | auto" + syntax: "space-all | normal | space-first | trim-start" }, "text-transform": { syntax: "none | capitalize | uppercase | lowercase | full-width | full-size-kana" @@ -1764,7 +1764,7 @@ var declarations = { syntax: "normal | pre | nowrap | pre-wrap | pre-line | break-spaces | [ <'white-space-collapse'> || <'text-wrap'> ]" }, "white-space-collapse": { - syntax: "collapse | discard | preserve | preserve-breaks | preserve-spaces | break-spaces" + syntax: "collapse | preserve | preserve-breaks | preserve-spaces | break-spaces" }, widows: { syntax: "" @@ -1826,10 +1826,10 @@ var functions = { syntax: "attr( ? [, ]? )" }, blur: { - syntax: "blur( )" + syntax: "blur( ? )" }, brightness: { - syntax: "brightness( )" + syntax: "brightness( [ | ]? )" }, calc: { syntax: "calc( )" @@ -1853,7 +1853,7 @@ var functions = { syntax: "conic-gradient( [ from ]? [ at ]?, )" }, contrast: { - syntax: "contrast( [ ] )" + syntax: "contrast( [ | ]? )" }, cos: { syntax: "cos( )" @@ -1868,7 +1868,7 @@ var functions = { syntax: "cross-fade( , ? )" }, "drop-shadow": { - syntax: "drop-shadow( {2,3} ? )" + syntax: "drop-shadow( [ ? && {2,3} ] )" }, element: { syntax: "element( )" @@ -1886,7 +1886,7 @@ var functions = { syntax: "fit-content( )" }, grayscale: { - syntax: "grayscale( )" + syntax: "grayscale( [ | ]? )" }, hsl: { syntax: "hsl( [ / ]? ) | hsl( , , , ? )" @@ -1895,7 +1895,7 @@ var functions = { syntax: "hsla( [ / ]? ) | hsla( , , , ? )" }, "hue-rotate": { - syntax: "hue-rotate( )" + syntax: "hue-rotate( [ | ]? )" }, hwb: { syntax: "hwb( [ | none] [ | none] [ | none] [ / [ | none] ]? )" @@ -1913,7 +1913,7 @@ var functions = { syntax: "inset( {1,4} [ round <'border-radius'> ]? )" }, invert: { - syntax: "invert( )" + syntax: "invert( [ | ]? )" }, lab: { syntax: "lab( [ | | none] [ | | none] [ | | none] [ / [ | none] ]? )" @@ -1961,7 +1961,7 @@ var functions = { syntax: "oklch( [ | | none] [ | | none] [ | none] [ / [ | none] ]? )" }, opacity: { - syntax: "opacity( [ ] )" + syntax: "opacity( [ | ]? )" }, paint: { syntax: "paint( , ? )" @@ -1970,13 +1970,13 @@ var functions = { syntax: "palette-mix( , [ [normal | light | dark | | ] && ? ]#{2})" }, path: { - syntax: "path( [ , ]? )" + syntax: "path( <'fill-rule'>? , )" }, perspective: { syntax: "perspective( [ | none ] )" }, polygon: { - syntax: "polygon( ? , [ ]# )" + syntax: "polygon( <'fill-rule'>? , [ ]# )" }, pow: { syntax: "pow( , )" @@ -2024,7 +2024,7 @@ var functions = { syntax: "round( ?, , )" }, saturate: { - syntax: "saturate( )" + syntax: "saturate( [ | ]? )" }, scale: { syntax: "scale( [ | ]#{1,2} )" @@ -2045,7 +2045,7 @@ var functions = { syntax: "scroll( [ || ]? )" }, sepia: { - syntax: "sepia( )" + syntax: "sepia( [ | ]? )" }, sign: { syntax: "sign( )" @@ -2200,10 +2200,10 @@ var syntaxes = { syntax: "normal | multiply | screen | overlay | darken | lighten | color-dodge | color-burn | hard-light | soft-light | difference | exclusion | hue | saturation | color | luminosity" }, "blur()": { - syntax: "blur( )" + syntax: "blur( ? )" }, "brightness()": { - syntax: "brightness( )" + syntax: "brightness( [ | ]? )" }, "calc()": { syntax: "calc( )" @@ -2245,7 +2245,13 @@ var syntaxes = { syntax: "" }, color: { - syntax: " | | | | | | | | | | | | | | currentcolor | transparent" + syntax: " | currentColor | | | " + }, + "color-base": { + syntax: " | | | | transparent" + }, + "color-function": { + syntax: " | | | | | | | | | " }, "color()": { syntax: "color( [from ]? [ / [ | none ] ]? )" @@ -2317,7 +2323,7 @@ var syntaxes = { syntax: "[ contextual | no-contextual ]" }, "contrast()": { - syntax: "contrast( [ ] )" + syntax: "contrast( [ | ]? )" }, "coord-box": { syntax: " | view-box" @@ -2359,7 +2365,7 @@ var syntaxes = { syntax: "[ [ | ]+ ]#" }, "deprecated-system-color": { - syntax: "ActiveBorder | ActiveCaption | AppWorkspace | Background | ButtonFace | ButtonHighlight | ButtonShadow | ButtonText | CaptionText | GrayText | Highlight | HighlightText | InactiveBorder | InactiveCaption | InactiveCaptionText | InfoBackground | InfoText | Menu | MenuText | Scrollbar | ThreeDDarkShadow | ThreeDFace | ThreeDHighlight | ThreeDLightShadow | ThreeDShadow | Window | WindowFrame | WindowText" + syntax: "ActiveBorder | ActiveCaption | AppWorkspace | Background | ButtonHighlight | ButtonShadow | CaptionText | InactiveBorder | InactiveCaption | InactiveCaptionText | InfoBackground | InfoText | Menu | MenuText | Scrollbar | ThreeDDarkShadow | ThreeDFace | ThreeDHighlight | ThreeDLightShadow | ThreeDShadow | Window | WindowFrame | WindowText" }, "discretionary-lig-values": { syntax: "[ discretionary-ligatures | no-discretionary-ligatures ]" @@ -2383,7 +2389,7 @@ var syntaxes = { syntax: "block | inline | run-in" }, "drop-shadow()": { - syntax: "drop-shadow( {2,3} ? )" + syntax: "drop-shadow( [ ? && {2,3} ] )" }, "easing-function": { syntax: "linear | | " @@ -2436,9 +2442,6 @@ var syntaxes = { "feature-value-name": { syntax: "" }, - "fill-rule": { - syntax: "nonzero | evenodd" - }, "filter-function": { syntax: " | | | | | | | | | " }, @@ -2488,7 +2491,7 @@ var syntaxes = { syntax: " | | | | | " }, "grayscale()": { - syntax: "grayscale( )" + syntax: "grayscale( [ | ]? )" }, "grid-line": { syntax: "auto | | [ && ? ] | [ span && [ || ] ]" @@ -2509,7 +2512,7 @@ var syntaxes = { syntax: "[ shorter | longer | increasing | decreasing ] hue" }, "hue-rotate()": { - syntax: "hue-rotate( )" + syntax: "hue-rotate( [ | ]? )" }, "hwb()": { syntax: "hwb( [ | none] [ | none] [ | none] [ / [ | none] ]? )" @@ -2530,7 +2533,7 @@ var syntaxes = { syntax: "image-set( # )" }, "image-set-option": { - syntax: "[ | ]x [ || type() ]" + syntax: "[ | ] [ || type() ]" }, "image-src": { syntax: " | " @@ -2545,7 +2548,7 @@ var syntaxes = { syntax: "inset( {1,4} [ round <'border-radius'> ]? )" }, "invert()": { - syntax: "invert( )" + syntax: "invert( [ | ]? )" }, "keyframe-block": { syntax: "# {\n \n}" @@ -2689,7 +2692,7 @@ var syntaxes = { syntax: "repeat( [ | auto-fill ], + )" }, "named-color": { - syntax: "transparent | aliceblue | antiquewhite | aqua | aquamarine | azure | beige | bisque | black | blanchedalmond | blue | blueviolet | brown | burlywood | cadetblue | chartreuse | chocolate | coral | cornflowerblue | cornsilk | crimson | cyan | darkblue | darkcyan | darkgoldenrod | darkgray | darkgreen | darkgrey | darkkhaki | darkmagenta | darkolivegreen | darkorange | darkorchid | darkred | darksalmon | darkseagreen | darkslateblue | darkslategray | darkslategrey | darkturquoise | darkviolet | deeppink | deepskyblue | dimgray | dimgrey | dodgerblue | firebrick | floralwhite | forestgreen | fuchsia | gainsboro | ghostwhite | gold | goldenrod | gray | green | greenyellow | grey | honeydew | hotpink | indianred | indigo | ivory | khaki | lavender | lavenderblush | lawngreen | lemonchiffon | lightblue | lightcoral | lightcyan | lightgoldenrodyellow | lightgray | lightgreen | lightgrey | lightpink | lightsalmon | lightseagreen | lightskyblue | lightslategray | lightslategrey | lightsteelblue | lightyellow | lime | limegreen | linen | magenta | maroon | mediumaquamarine | mediumblue | mediumorchid | mediumpurple | mediumseagreen | mediumslateblue | mediumspringgreen | mediumturquoise | mediumvioletred | midnightblue | mintcream | mistyrose | moccasin | navajowhite | navy | oldlace | olive | olivedrab | orange | orangered | orchid | palegoldenrod | palegreen | paleturquoise | palevioletred | papayawhip | peachpuff | peru | pink | plum | powderblue | purple | rebeccapurple | red | rosybrown | royalblue | saddlebrown | salmon | sandybrown | seagreen | seashell | sienna | silver | skyblue | slateblue | slategray | slategrey | snow | springgreen | steelblue | tan | teal | thistle | tomato | turquoise | violet | wheat | white | whitesmoke | yellow | yellowgreen" + syntax: "aliceblue | antiquewhite | aqua | aquamarine | azure | beige | bisque | black | blanchedalmond | blue | blueviolet | brown | burlywood | cadetblue | chartreuse | chocolate | coral | cornflowerblue | cornsilk | crimson | cyan | darkblue | darkcyan | darkgoldenrod | darkgray | darkgreen | darkgrey | darkkhaki | darkmagenta | darkolivegreen | darkorange | darkorchid | darkred | darksalmon | darkseagreen | darkslateblue | darkslategray | darkslategrey | darkturquoise | darkviolet | deeppink | deepskyblue | dimgray | dimgrey | dodgerblue | firebrick | floralwhite | forestgreen | fuchsia | gainsboro | ghostwhite | gold | goldenrod | gray | green | greenyellow | grey | honeydew | hotpink | indianred | indigo | ivory | khaki | lavender | lavenderblush | lawngreen | lemonchiffon | lightblue | lightcoral | lightcyan | lightgoldenrodyellow | lightgray | lightgreen | lightgrey | lightpink | lightsalmon | lightseagreen | lightskyblue | lightslategray | lightslategrey | lightsteelblue | lightyellow | lime | limegreen | linen | magenta | maroon | mediumaquamarine | mediumblue | mediumorchid | mediumpurple | mediumseagreen | mediumslateblue | mediumspringgreen | mediumturquoise | mediumvioletred | midnightblue | mintcream | mistyrose | moccasin | navajowhite | navy | oldlace | olive | olivedrab | orange | orangered | orchid | palegoldenrod | palegreen | paleturquoise | palevioletred | papayawhip | peachpuff | peru | pink | plum | powderblue | purple | rebeccapurple | red | rosybrown | royalblue | saddlebrown | salmon | sandybrown | seagreen | seashell | sienna | silver | skyblue | slateblue | slategray | slategrey | snow | springgreen | steelblue | tan | teal | thistle | tomato | turquoise | violet | wheat | white | whitesmoke | yellow | yellowgreen" }, "namespace-prefix": { syntax: "" @@ -2722,7 +2725,7 @@ var syntaxes = { syntax: "oklch( [ | | none] [ | | none] [ | none] [ / [ | none] ]? )" }, "opacity()": { - syntax: "opacity( [ ] )" + syntax: "opacity( [ | ]? )" }, "opacity-value": { syntax: " | " @@ -2770,7 +2773,7 @@ var syntaxes = { syntax: "palette-mix( , [ [normal | light | dark | | ] && ? ]#{2})" }, "path()": { - syntax: "path( [ , ]? )" + syntax: "path( <'fill-rule'>? , )" }, "perspective()": { syntax: "perspective( [ | none ] )" @@ -2779,7 +2782,7 @@ var syntaxes = { syntax: "hsl | hwb | lch | oklch" }, "polygon()": { - syntax: "polygon( ? , [ ]# )" + syntax: "polygon( <'fill-rule'>? , [ ]# )" }, position: { syntax: "[ [ left | center | right ] || [ top | center | bottom ] | [ left | center | right | ] [ top | center | bottom | ]? | [ [ left | right ] ] && [ [ top | bottom ] ] ]" @@ -2878,7 +2881,7 @@ var syntaxes = { syntax: "nearest | up | down | to-zero" }, "saturate()": { - syntax: "saturate( )" + syntax: "saturate( [ | ]? )" }, "scale()": { syntax: "scale( [ | ]#{1,2} )" @@ -2914,7 +2917,7 @@ var syntaxes = { syntax: "center | start | end | self-start | self-end | flex-start | flex-end" }, "sepia()": { - syntax: "sepia( )" + syntax: "sepia( [ | ]? )" }, shadow: { syntax: "inset? && {2,4} && ?" @@ -3479,19 +3482,103 @@ var atRules = { syntax: "@charset \"\";" }, "@counter-style": { - syntax: "@counter-style {\n [ system: ; ] ||\n [ symbols: ; ] ||\n [ additive-symbols: ; ] ||\n [ negative: ; ] ||\n [ prefix: ; ] ||\n [ suffix: ; ] ||\n [ range: ; ] ||\n [ pad: ; ] ||\n [ speak-as: ; ] ||\n [ fallback: ; ]\n}" + syntax: "@counter-style {\n [ system: ; ] ||\n [ symbols: ; ] ||\n [ additive-symbols: ; ] ||\n [ negative: ; ] ||\n [ prefix: ; ] ||\n [ suffix: ; ] ||\n [ range: ; ] ||\n [ pad: ; ] ||\n [ speak-as: ; ] ||\n [ fallback: ; ]\n}", + descriptors: { + "additive-symbols": { + syntax: "[ && ]#" + }, + fallback: { + syntax: "" + }, + negative: { + syntax: " ?" + }, + pad: { + syntax: " && " + }, + prefix: { + syntax: "" + }, + range: { + syntax: "[ [ | infinite ]{2} ]# | auto" + }, + "speak-as": { + syntax: "auto | bullets | numbers | words | spell-out | " + }, + suffix: { + syntax: "" + }, + symbols: { + syntax: "+" + }, + system: { + syntax: "cyclic | numeric | alphabetic | symbolic | additive | [ fixed ? ] | [ extends ]" + } + } }, "@document": { syntax: "@document [ | url-prefix() | domain() | media-document() | regexp() ]# {\n \n}" }, "@font-face": { - syntax: "@font-face {\n [ font-family: ; ] ||\n [ src: ; ] ||\n [ unicode-range: ; ] ||\n [ font-variant: ; ] ||\n [ font-feature-settings: ; ] ||\n [ font-variation-settings: ; ] ||\n [ font-stretch: ; ] ||\n [ font-weight: ; ] ||\n [ font-style: ; ] ||\n [ size-adjust: ; ] ||\n [ ascent-override: ; ] ||\n [ descent-override: ; ] ||\n [ line-gap-override: ; ]\n}" + syntax: "@font-face {\n [ font-family: ; ] ||\n [ src: ; ] ||\n [ unicode-range: ; ] ||\n [ font-variant: ; ] ||\n [ font-feature-settings: ; ] ||\n [ font-variation-settings: ; ] ||\n [ font-stretch: ; ] ||\n [ font-weight: ; ] ||\n [ font-style: ; ] ||\n [ size-adjust: ; ] ||\n [ ascent-override: ; ] ||\n [ descent-override: ; ] ||\n [ line-gap-override: ; ]\n}", + descriptors: { + "ascent-override": { + syntax: "normal | " + }, + "descent-override": { + syntax: "normal | " + }, + "font-display": { + syntax: "[ auto | block | swap | fallback | optional ]" + }, + "font-family": { + syntax: "" + }, + "font-feature-settings": { + syntax: "normal | #" + }, + "font-stretch": { + syntax: "{1,2}" + }, + "font-style": { + syntax: "normal | italic | oblique {0,2}" + }, + "font-variation-settings": { + syntax: "normal | [ ]#" + }, + "font-weight": { + syntax: "{1,2}" + }, + "line-gap-override": { + syntax: "normal | " + }, + "size-adjust": { + syntax: "" + }, + src: { + syntax: "[ [ format( # ) ]? | local( ) ]#" + }, + "unicode-range": { + syntax: "#" + } + } }, "@font-feature-values": { syntax: "@font-feature-values # {\n \n}" }, "@font-palette-values": { - syntax: "@font-palette-values {\n \n}" + syntax: "@font-palette-values {\n \n}", + descriptors: { + "base-palette": { + syntax: "light | dark | " + }, + "font-family": { + syntax: "#" + }, + "override-colors": { + syntax: "[ ]#" + } + } }, "@import": { syntax: "@import [ | ]\n [ layer | layer() ]?\n [ supports( [ | ] ) ]?\n ? ;" @@ -3509,13 +3596,38 @@ var atRules = { syntax: "@namespace ? [ | ];" }, "@page": { - syntax: "@page {\n \n}" + syntax: "@page {\n \n}", + descriptors: { + bleed: { + syntax: "auto | " + }, + marks: { + syntax: "none | [ crop || cross ]" + }, + "page-orientation": { + syntax: "upright | rotate-left | rotate-right " + }, + size: { + syntax: "{1,2} | auto | [ || [ portrait | landscape ] ]" + } + } }, "@position-try": { syntax: "@position-try {\n \n}" }, "@property": { - syntax: "@property {\n \n}" + syntax: "@property {\n \n}", + descriptors: { + inherits: { + syntax: "true | false" + }, + "initial-value": { + syntax: "?" + }, + syntax: { + syntax: "" + } + } }, "@scope": { syntax: "@scope [()]? [to ()]? {\n \n}" @@ -3527,7 +3639,15 @@ var atRules = { syntax: "@supports {\n \n}" }, "@view-transition": { - syntax: "@view-transition {\n \n}" + syntax: "@view-transition {\n \n}", + descriptors: { + navigation: { + syntax: "auto | none" + }, + types: { + syntax: "none | +" + } + } } }; var config = { diff --git a/dist/lib/validation/declaration.js b/dist/lib/validation/declaration.js index c51a3c86..95aa34a2 100644 --- a/dist/lib/validation/declaration.js +++ b/dist/lib/validation/declaration.js @@ -21,7 +21,9 @@ function validateDeclaration(declaration, options, root) { } // root is at-rule - check if declaration allowed if (root?.typ == EnumToken.AtRuleNodeType) { + // const syntax = getParsedSyntax("atRules" /* ValidationSyntaxGroupEnum.AtRules */, '@' + root.nam)?.[0]; + // console.error({syntax}); if (syntax != null) { if (!('chi' in syntax)) { return { @@ -39,15 +41,36 @@ function validateDeclaration(declaration, options, root) { error: '' }; } - if (!(name in config.declarations) && !(name in config.syntaxes)) { - return { - valid: ValidationLevel.Drop, - node: declaration, - syntax: null, - error: `unknown declaration "${declaration.nam}"` - }; + // console.error({syntax}); + const config = getSyntaxConfig(); + // @ts-ignore + const obj = config["atRules" /* ValidationSyntaxGroupEnum.AtRules */]['@' + root.nam]; + if ('descriptors' in obj) { + const descriptors = obj.descriptors; + if (!(name in descriptors)) { + return { + valid: ValidationLevel.Drop, + node: declaration, + syntax: `<${declaration.nam}>`, + error: ` declaration <${declaration.nam}> is not allowed in <@${root.nam}>` + }; + } + const syntax = getParsedSyntax("atRules" /* ValidationSyntaxGroupEnum.AtRules */, ['@' + root.nam, 'descriptors', name]); + return validateSyntax(syntax, declaration.val, root, options); } - return validateSyntax(syntax.chi, [declaration], root, options); + // console.error({name}); + // if (!(name in config.declarations) && !(name in config.syntaxes)) { + // + // return { + // + // valid: ValidationLevel.Drop, + // node: declaration, + // syntax: null, + // error: `unknown declaration "${declaration.nam}"` + // } + // } + // + // return validateSyntax((syntax as ValidationAtRuleDefinitionToken).chi as ValidationToken[], [declaration], root, options); } } if (name.startsWith('--')) { @@ -62,10 +85,17 @@ function validateDeclaration(declaration, options, root) { return { valid: ValidationLevel.Drop, node: declaration, - syntax: null, + syntax: `<${declaration.nam}>`, error: `unknown declaration "${declaration.nam}"` }; } + // return { + // + // valid: ValidationLevel.Valid, + // node: declaration, + // syntax: null, + // error: '' + // } return validateSyntax(getParsedSyntax("declarations" /* ValidationSyntaxGroupEnum.Declarations */, name), declaration.val); } diff --git a/dist/lib/validation/parser/parse.js b/dist/lib/validation/parser/parse.js index 963fc3a5..2e089bfa 100644 --- a/dist/lib/validation/parser/parse.js +++ b/dist/lib/validation/parser/parse.js @@ -1,6 +1,20 @@ import { ValidationTokenEnum } from './types.js'; import { isIdent, isPseudo } from '../../syntax/syntax.js'; +var WalkValidationTokenEnum; +(function (WalkValidationTokenEnum) { + WalkValidationTokenEnum[WalkValidationTokenEnum["IgnoreChildren"] = 0] = "IgnoreChildren"; + WalkValidationTokenEnum[WalkValidationTokenEnum["IgnoreNode"] = 1] = "IgnoreNode"; + WalkValidationTokenEnum[WalkValidationTokenEnum["IgnoreAll"] = 2] = "IgnoreAll"; +})(WalkValidationTokenEnum || (WalkValidationTokenEnum = {})); +var WalkValidationTokenKeyTypeEnum; +(function (WalkValidationTokenKeyTypeEnum) { + WalkValidationTokenKeyTypeEnum["Array"] = "array"; + WalkValidationTokenKeyTypeEnum["Children"] = "chi"; + WalkValidationTokenKeyTypeEnum["Left"] = "l"; + WalkValidationTokenKeyTypeEnum["Right"] = "r"; + WalkValidationTokenKeyTypeEnum["Prelude"] = "prelude"; +})(WalkValidationTokenKeyTypeEnum || (WalkValidationTokenKeyTypeEnum = {})); const skipped = [ ValidationTokenEnum.Star, ValidationTokenEnum.HashMark, @@ -167,13 +181,66 @@ function* tokenize(syntax, position = { ind: 0, lin: 1, col: 0 }, currentPositio yield getTokenType(buffer, position, currentPosition); } } +function columnCallback(token, parent, key) { + if (key == WalkValidationTokenKeyTypeEnum.Prelude) { + return WalkValidationTokenEnum.IgnoreAll; + } + if (token.typ == ValidationTokenEnum.ColumnToken || token.typ == ValidationTokenEnum.Whitespace) { + return WalkValidationTokenEnum.IgnoreNode; + } + return WalkValidationTokenEnum.IgnoreChildren; +} +function toColumnArray(ast, parent) { + const result = new Map; + // @ts-ignore + for (const { token } of walkValidationToken(ast, null, columnCallback)) { + result.set(JSON.stringify(token), token); + } + const node = { + typ: ValidationTokenEnum.ColumnArrayToken, + chi: [...result.values()] + }; + if (parent != null) { + replaceNode(parent, ast, node); + } + return node; +} +function replaceNode(parent, target, node) { + if ('l' in parent && parent.l == target) { + parent.l = node; + } + if ('r' in parent && parent.r == target) { + parent.r = node; + } + if ('chi' in parent) { + // @ts-ignore + for (let i = 0; i < parent.chi.length; i++) { + // @ts-ignore + if (parent.chi[i] == target) { + // @ts-ignore + parent.chi[i] = node; + break; + } + } + } +} +function transform(context) { + for (const { token, parent } of walkValidationToken(context)) { + switch (token.typ) { + case ValidationTokenEnum.ColumnToken: + toColumnArray(token, parent); + break; + } + } + return context; +} function parseSyntax(syntax) { const root = Object.defineProperty({ typ: ValidationTokenEnum.Root, chi: [] }, 'pos', { ...objectProperties, value: { ind: 0, lin: 1, col: 0 } }); // return minify(doParseSyntax(syntaxes, tokenize(syntaxes), root)) as ValidationRootToken; - return minify(doParseSyntax(syntax, tokenize(syntax), root)); + return minify(transform(doParseSyntax(syntax, tokenize(syntax), root))); } function matchParens(syntax, iterator) { let item; @@ -941,6 +1008,10 @@ function renderSyntax(token, parent) { (token.chi == null ? '' : ' {\n' + token.chi.reduce((acc, curr) => acc + renderSyntax(curr), '')).slice(1, -1) + '\n}'; case ValidationTokenEnum.Block: return '{' + token.chi.reduce((acc, t) => acc + renderSyntax(t), '') + '}'; + case ValidationTokenEnum.DeclarationDefinitionToken: + return token.nam + ': ' + renderSyntax(token.val); + case ValidationTokenEnum.ColumnArrayToken: + return token.chi.reduce((acc, curr) => acc + (acc.trim().length > 0 ? '||' : '') + renderSyntax(curr), ''); default: throw new Error('Unhandled token: ' + JSON.stringify({ token })); } @@ -1032,37 +1103,44 @@ function minify(ast) { } return ast; } -function* walkValidationToken(token, parent, callback) { +function* walkValidationToken(token, parent, callback, key) { if (Array.isArray(token)) { for (const child of token) { - yield* walkValidationToken(child, parent); + yield* walkValidationToken(child, parent, callback, WalkValidationTokenKeyTypeEnum.Array); } return; } - yield { token, parent }; - if ('prelude' in token) { + let action = null; + if (callback != null) { + // @ts-ignore + action = callback(token, parent, key); + } + if (action != WalkValidationTokenEnum.IgnoreNode && action != WalkValidationTokenEnum.IgnoreAll) { + yield { token, parent }; + } + if (action != WalkValidationTokenEnum.IgnoreChildren && action != WalkValidationTokenEnum.IgnoreAll && 'prelude' in token) { for (const child of token.prelude) { - yield* walkValidationToken(child, token); + yield* walkValidationToken(child, token, callback, WalkValidationTokenKeyTypeEnum.Prelude); } } - if ('chi' in token) { + if (action != WalkValidationTokenEnum.IgnoreChildren && 'chi' in token) { // @ts-ignore for (const child of token.chi) { - yield* walkValidationToken(child, token); + yield* walkValidationToken(child, token, callback, WalkValidationTokenKeyTypeEnum.Children); } } - if ('l' in token) { + if (action != WalkValidationTokenEnum.IgnoreChildren && action != WalkValidationTokenEnum.IgnoreAll && 'l' in token) { // @ts-ignore for (const child of token.l) { - yield* walkValidationToken(child, token); + yield* walkValidationToken(child, token, callback, WalkValidationTokenKeyTypeEnum.Left); } } - if ('r' in token) { + if (action != WalkValidationTokenEnum.IgnoreChildren && action != WalkValidationTokenEnum.IgnoreAll && 'r' in token) { // @ts-ignore for (const child of token.r) { - yield* walkValidationToken(child, token); + yield* walkValidationToken(child, token, callback, WalkValidationTokenKeyTypeEnum.Right); } } } -export { parseSyntax, renderSyntax, walkValidationToken }; +export { WalkValidationTokenEnum, WalkValidationTokenKeyTypeEnum, parseSyntax, renderSyntax, walkValidationToken }; diff --git a/dist/lib/validation/parser/types.js b/dist/lib/validation/parser/types.js index 137834cd..1c60d73b 100644 --- a/dist/lib/validation/parser/types.js +++ b/dist/lib/validation/parser/types.js @@ -41,6 +41,7 @@ var ValidationTokenEnum; ValidationTokenEnum[ValidationTokenEnum["DeclarationDefinitionToken"] = 37] = "DeclarationDefinitionToken"; ValidationTokenEnum[ValidationTokenEnum["SemiColon"] = 38] = "SemiColon"; ValidationTokenEnum[ValidationTokenEnum["Character"] = 39] = "Character"; + ValidationTokenEnum[ValidationTokenEnum["ColumnArrayToken"] = 40] = "ColumnArrayToken"; })(ValidationTokenEnum || (ValidationTokenEnum = {})); var ValidationSyntaxGroupEnum; (function (ValidationSyntaxGroupEnum) { diff --git a/dist/lib/validation/selector.js b/dist/lib/validation/selector.js index e6c6c7c2..bce24553 100644 --- a/dist/lib/validation/selector.js +++ b/dist/lib/validation/selector.js @@ -8,11 +8,14 @@ import '../parser/utils/config.js'; import { validateRelativeSelectorList } from './syntaxes/relative-selector-list.js'; import './syntaxes/complex-selector.js'; import { validateKeyframeBlockList } from './syntaxes/keyframe-block-list.js'; +import './parser/types.js'; +import './parser/parse.js'; +import './config.js'; import { validateSelectorList } from './syntaxes/selector-list.js'; function validateSelector(selector, options, root) { if (root == null) { - return validateRelativeSelectorList(selector, root); + return validateSelectorList(selector, root, options); } // @ts-ignore if (root.typ == EnumToken.AtRuleNodeType && root.nam.match(/^(-[a-z]+-)?keyframes$/)) { @@ -25,14 +28,14 @@ function validateSelector(selector, options, root) { isNested++; if (isNested > 0) { // @ts-ignore - return validateRelativeSelectorList(selector, root, { nestedSelector: true }); + return validateRelativeSelectorList(selector, root, { ...(options ?? {}), nestedSelector: true }); } } currentRoot = currentRoot.parent; } const nestedSelector = isNested > 0; // @ts-ignore - return nestedSelector ? validateRelativeSelectorList(selector, root, { nestedSelector }) : validateSelectorList(selector, root, { nestedSelector }); + return nestedSelector ? validateRelativeSelectorList(selector, root, { ...(options ?? {}), nestedSelector }) : validateSelectorList(selector, root, { ...(options ?? {}), nestedSelector }); } export { validateSelector }; diff --git a/dist/lib/validation/syntax.js b/dist/lib/validation/syntax.js index 871a1a86..a6ebd7b2 100644 --- a/dist/lib/validation/syntax.js +++ b/dist/lib/validation/syntax.js @@ -1,5 +1,5 @@ import { ValidationTokenEnum, specialValues } from './parser/types.js'; -import './parser/parse.js'; +import { renderSyntax } from './parser/parse.js'; import { ValidationLevel, EnumToken, funcLike } from '../ast/types.js'; import '../ast/minify.js'; import '../ast/walk.js'; @@ -8,8 +8,10 @@ import { isLength } from '../syntax/syntax.js'; import '../parser/utils/config.js'; import '../renderer/color/utils/constants.js'; import '../renderer/sourcemap/lib/encode.js'; -import { getParsedSyntax, getSyntaxConfig } from './config.js'; +import { getSyntaxConfig, getParsedSyntax } from './config.js'; import { validateSelector } from './selector.js'; +import './syntaxes/complex-selector.js'; +import { validateImage } from './syntaxes/image.js'; const config = getSyntaxConfig(); function consumeToken(tokens) { @@ -30,6 +32,12 @@ function splice(tokens, matches) { return tokens; } function validateSyntax(syntaxes, tokens, root, options, context = { level: 0 }) { + console.error(JSON.stringify({ + syntax: syntaxes.reduce((acc, curr) => acc + renderSyntax(curr), ''), + syntaxes, + tokens, + s: new Error('bar').stack + }, null, 1)); if (syntaxes == null) { // @ts-ignore return { @@ -485,7 +493,7 @@ function validateSyntax(syntaxes, tokens, root, options, context = { level: 0 }) return result; } function isOptionalSyntax(syntaxes) { - return syntaxes.every(t => t.typ == ValidationTokenEnum.Whitespace || t.isOptional || t.isRepeatable || (t.typ == ValidationTokenEnum.PropertyType && isOptionalSyntax(getParsedSyntax("syntaxes" /* ValidationSyntaxGroupEnum.Syntaxes */, t.val) ?? getParsedSyntax("declarations" /* ValidationSyntaxGroupEnum.Declarations */, t.val)))); + return syntaxes.length > 0 && syntaxes.every(t => t.typ == ValidationTokenEnum.Whitespace || t.isOptional || t.isRepeatable || (t.typ == ValidationTokenEnum.PropertyType && isOptionalSyntax(getParsedSyntax("syntaxes" /* ValidationSyntaxGroupEnum.Syntaxes */, t.val) ?? getParsedSyntax("declarations" /* ValidationSyntaxGroupEnum.Declarations */, t.val) ?? []))); } function doValidateSyntax(syntax, token, tokens, root, options, context) { let valid = false; @@ -706,7 +714,21 @@ function doValidateSyntax(syntax, token, tokens, root, options, context) { break; case ValidationTokenEnum.PropertyType: // - if (['media-feature', 'mf-plain'].includes(syntax.val)) { + if ('image' == syntax.val) { + valid = token.typ == EnumToken.UrlFunctionTokenType || token.typ == EnumToken.ImageFunctionTokenType; + if (!valid) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: token, + syntax, + error: 'unexpected ', + tokens + }; + } + result = validateImage(token); + } + else if (['media-feature', 'mf-plain'].includes(syntax.val)) { valid = token.typ == EnumToken.DeclarationNodeType; result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, @@ -751,6 +773,18 @@ function doValidateSyntax(syntax, token, tokens, root, options, context) { } else if (syntax.val == 'group-rule-body') { valid = [EnumToken.AtRuleNodeType, EnumToken.RuleNodeType].includes(token.typ); + if (!valid && token.typ == EnumToken.DeclarationNodeType && root?.typ == EnumToken.AtRuleNodeType && root.nam == 'media') { + // allowed only if nested rule + let parent = root; + while (parent != null) { + if (parent.typ == EnumToken.RuleNodeType) { + valid = true; + break; + } + // @ts-ignore + parent = parent.parent; + } + } // @ts-ignore result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, @@ -1419,6 +1453,18 @@ function doValidateSyntax(syntax, token, tokens, root, options, context) { tokens }; break; + case ValidationTokenEnum.DeclarationDefinitionToken: + if (token.typ != EnumToken.DeclarationNodeType || token.nam != syntax.nam) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: token, + syntax, + error: '', + tokens + }; + } + return validateSyntax([syntax.val], token.val, root, options, context); default: throw new Error('not implemented: ' + JSON.stringify({ syntax, token, tokens }, null, 1)); } diff --git a/dist/lib/validation/syntaxes/complex-selector-list.js b/dist/lib/validation/syntaxes/complex-selector-list.js index eddc57ac..9d075f1e 100644 --- a/dist/lib/validation/syntaxes/complex-selector-list.js +++ b/dist/lib/validation/syntaxes/complex-selector-list.js @@ -1,4 +1,4 @@ -import { ValidationLevel, EnumToken } from '../../ast/types.js'; +import { ValidationLevel } from '../../ast/types.js'; import '../../ast/minify.js'; import '../../ast/walk.js'; import '../../parser/parse.js'; @@ -7,6 +7,7 @@ import '../../renderer/sourcemap/lib/encode.js'; import '../../parser/utils/config.js'; import { validateSelector } from './selector.js'; import { consumeWhitespace } from '../utils/whitespace.js'; +import { splitTokenList } from '../utils/list.js'; function validateComplexSelectorList(tokens, root, options) { tokens = tokens.slice(); @@ -22,20 +23,23 @@ function validateComplexSelectorList(tokens, root, options) { tokens }; } - let i = -1; - let j = 0; let result = null; - while (i + 1 < tokens.length) { - if (tokens[++i].typ == EnumToken.CommaTokenType) { - result = validateSelector(tokens.slice(j, i), root, options); - if (result.valid == ValidationLevel.Drop) { - return result; - } - j = i + 1; - i = j; + for (const t of splitTokenList(tokens)) { + result = validateSelector(t, root, options); + if (result.valid == ValidationLevel.Drop) { + return result; } } - return validateSelector(i == j ? tokens.slice(i) : tokens.slice(j, i + 1), root, options); + // @ts-ignore + return result ?? { + valid: ValidationLevel.Drop, + matches: [], + // @ts-ignore + node: root, + syntax: null, + error: 'expecting complex selector list', + tokens + }; } export { validateComplexSelectorList }; diff --git a/dist/lib/validation/syntaxes/complex-selector.js b/dist/lib/validation/syntaxes/complex-selector.js index 2070c099..9ffc52c4 100644 --- a/dist/lib/validation/syntaxes/complex-selector.js +++ b/dist/lib/validation/syntaxes/complex-selector.js @@ -1,4 +1,5 @@ import { consumeWhitespace } from '../utils/whitespace.js'; +import { splitTokenList } from '../utils/list.js'; import { EnumToken, ValidationLevel } from '../../ast/types.js'; import '../../ast/minify.js'; import '../../ast/walk.js'; @@ -6,9 +7,11 @@ import '../../parser/parse.js'; import '../../renderer/color/utils/constants.js'; import '../../renderer/sourcemap/lib/encode.js'; import '../../parser/utils/config.js'; +import { validateCompoundSelector } from './compound-selector.js'; const combinatorsTokens = [EnumToken.ChildCombinatorTokenType, EnumToken.ColumnCombinatorTokenType, - EnumToken.DescendantCombinatorTokenType, EnumToken.NextSiblingCombinatorTokenType, EnumToken.SubsequentSiblingCombinatorTokenType]; + // EnumToken.DescendantCombinatorTokenType, + EnumToken.NextSiblingCombinatorTokenType, EnumToken.SubsequentSiblingCombinatorTokenType]; // [ ? ]* function validateComplexSelector(tokens, root, options) { // [ ? * [ * ]* ]! @@ -25,257 +28,24 @@ function validateComplexSelector(tokens, root, options) { tokens }; } - while (tokens.length > 0) { - if (combinatorsTokens.includes(tokens[0].typ)) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - // @ts-ignore - node: tokens[0], - syntax: null, - error: 'unexpected combinator', - tokens - }; - } - if (tokens[0].typ == EnumToken.NestingSelectorTokenType) { - if (!options?.nestedSelector) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - // @ts-ignore - node: tokens[0], - syntax: null, - error: 'nested selector not allowed', - tokens - }; - } - while (tokens.length > 0 && tokens[0].typ == EnumToken.NestingSelectorTokenType) { - tokens.shift(); - consumeWhitespace(tokens); - } - if (tokens.length == 0) { - break; - } - } - if (EnumToken.IdenTokenType == tokens[0].typ) { - tokens.shift(); - consumeWhitespace(tokens); - if (tokens.length == 0) { - break; - } - } - if (EnumToken.UniversalSelectorTokenType == tokens[0].typ) { - tokens.shift(); - consumeWhitespace(tokens); - } - while (tokens.length > 0) { - if (tokens[0].typ == EnumToken.PseudoClassFuncTokenType) { - if (tokens[0].val.startsWith(':-webkit-')) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - // @ts-ignore - node: tokens[0], - syntax: null, - error: 'invalid pseudo-class', - tokens - }; - } - } - if ([ - EnumToken.ClassSelectorTokenType, - EnumToken.HashTokenType, - EnumToken.PseudoClassTokenType, - EnumToken.PseudoClassFuncTokenType - ].includes(tokens[0].typ)) { - tokens.shift(); - consumeWhitespace(tokens); - continue; - } - if (tokens[0].typ == EnumToken.NestingSelectorTokenType) { - if (!options?.nestedSelector) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - // @ts-ignore - node: tokens[0], - syntax: null, - error: 'nested selector not allowed', - tokens - }; - } - tokens.shift(); - consumeWhitespace(tokens); - continue; - } - // validate namespace - if (tokens[0].typ == EnumToken.NameSpaceAttributeTokenType) { - if (!((tokens[0].l == null || tokens[0].l.typ == EnumToken.IdenTokenType || (tokens[0].l.typ == EnumToken.LiteralTokenType && tokens[0].l.val == '*')) && - tokens[0].r.typ == EnumToken.IdenTokenType)) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0], - syntax: null, - error: 'expecting wq-name', - tokens - }; - } - tokens.shift(); - consumeWhitespace(tokens); - continue; - } - // validate attribute - else if (tokens[0].typ == EnumToken.AttrTokenType) { - const children = tokens[0].chi.slice(); - consumeWhitespace(children); - if (children.length == 0) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0], - syntax: null, - error: 'invalid attribute selector', - tokens - }; - } - if (![ - EnumToken.IdenTokenType, - EnumToken.NameSpaceAttributeTokenType, - EnumToken.MatchExpressionTokenType - ].includes(children[0].typ)) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0], - syntax: null, - error: 'invalid attribute selector', - tokens - }; - } - if (children[0].typ == EnumToken.MatchExpressionTokenType) { - if (![EnumToken.IdenTokenType, - EnumToken.NameSpaceAttributeTokenType].includes(children[0].l.typ) || - ![ - EnumToken.EqualMatchTokenType, EnumToken.DashMatchTokenType, - EnumToken.StartMatchTokenType, EnumToken.ContainMatchTokenType, - EnumToken.EndMatchTokenType, EnumToken.IncludeMatchTokenType - ].includes(children[0].op.typ) || - ![EnumToken.StringTokenType, - EnumToken.IdenTokenType].includes(children[0].r.typ)) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0], - syntax: null, - error: 'invalid attribute selector', - tokens - }; - } - if (children[0].attr != null && !['i', 's'].includes(children[0].attr)) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0], - syntax: null, - error: 'invalid attribute selector', - tokens - }; - } - } - children.shift(); - consumeWhitespace(children); - if (children.length > 0) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: children[0], - syntax: null, - error: 'unexpected token', - tokens - }; - } - tokens.shift(); - consumeWhitespace(tokens); - continue; - } - break; - } - if (tokens.length == 0) { - break; - } - // combinator - if (!combinatorsTokens.includes(tokens[0].typ)) { - if (tokens[0].typ == EnumToken.NestingSelectorTokenType) { - if (!options?.nestedSelector) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - // @ts-ignore - node: tokens[0], - syntax: null, - error: 'nested selector not allowed', - tokens - }; - } - tokens.shift(); - consumeWhitespace(tokens); - continue; - } - if (tokens.length > 0 && - [ - EnumToken.IdenTokenType, - EnumToken.AttrTokenType, - EnumToken.NameSpaceAttributeTokenType, - EnumToken.ClassSelectorTokenType, - EnumToken.HashTokenType, - EnumToken.PseudoClassTokenType, - EnumToken.PseudoClassFuncTokenType - ].includes(tokens[0].typ)) { - continue; - } - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0], - syntax: null, - error: 'expecting combinator or subclass-selector', - tokens - }; - } - const token = tokens.shift(); - consumeWhitespace(tokens); - if (tokens.length == 0) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: token, - syntax: null, - error: 'expected compound-selector', - tokens - }; + // const config = getSyntaxConfig(); + // + // let match: number = 0; + let result = null; + // const combinators: EnumToken[] = combinatorsTokens.filter((t: EnumToken) => t != EnumToken.DescendantCombinatorTokenType); + for (const t of splitTokenList(tokens, combinatorsTokens)) { + result = validateCompoundSelector(t, root, options); + if (result.valid == ValidationLevel.Drop) { + return result; } } // @ts-ignore - return { - valid: ValidationLevel.Valid, + return result ?? { + valid: ValidationLevel.Drop, matches: [], - node: null, + node: root, syntax: null, - error: '', + error: 'expecting compound-selector', tokens }; } diff --git a/dist/lib/validation/syntaxes/compound-selector.js b/dist/lib/validation/syntaxes/compound-selector.js new file mode 100644 index 00000000..78451522 --- /dev/null +++ b/dist/lib/validation/syntaxes/compound-selector.js @@ -0,0 +1,226 @@ +import { ValidationLevel, EnumToken } from '../../ast/types.js'; +import '../../ast/minify.js'; +import '../../ast/walk.js'; +import '../../parser/parse.js'; +import { mozExtensions, webkitExtensions } from '../../syntax/syntax.js'; +import '../../parser/utils/config.js'; +import '../../renderer/color/utils/constants.js'; +import '../../renderer/sourcemap/lib/encode.js'; +import { consumeWhitespace } from '../utils/whitespace.js'; +import { getSyntaxConfig } from '../config.js'; + +function validateCompoundSelector(tokens, root, options) { + if (tokens.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + // @ts-ignore + node: root, + // @ts-ignore + syntax: null, + error: 'expected selector', + tokens + }; + } + tokens = tokens.slice(); + consumeWhitespace(tokens); + const config = getSyntaxConfig(); + let match = 0; + let length = tokens.length; + while (tokens.length > 0) { + while (tokens.length > 0 && tokens[0].typ == EnumToken.NestingSelectorTokenType) { + if (!options?.nestedSelector) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + // @ts-ignore + node: tokens[0], + syntax: null, + error: 'nested selector not allowed', + tokens + }; + } + match++; + tokens.shift(); + consumeWhitespace(tokens); + } + // + while (tokens.length > 0 && + [ + EnumToken.IdenTokenType, + EnumToken.NameSpaceAttributeTokenType, + EnumToken.ClassSelectorTokenType, + EnumToken.HashTokenType, + EnumToken.UniversalSelectorTokenType + ].includes(tokens[0].typ)) { + match++; + tokens.shift(); + consumeWhitespace(tokens); + } + while (tokens.length > 0 && tokens[0].typ == EnumToken.PseudoClassFuncTokenType) { + if (!mozExtensions.has(tokens[0].val + '()') && + !webkitExtensions.has(tokens[0].val + '()') && + !((tokens[0].val + '()') in config.selectors)) { + if (!options?.lenient || /^(:?)-webkit-/.test(tokens[0].val)) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + // @ts-ignore + node: tokens[0], + syntax: null, + error: 'unknown pseudo-class: ' + tokens[0].val + '()', + tokens + }; + } + } + match++; + tokens.shift(); + consumeWhitespace(tokens); + } + while (tokens.length > 0 && tokens[0].typ == EnumToken.PseudoClassTokenType) { + const isPseudoElement = tokens[0].val.startsWith('::'); + if ( + // https://developer.mozilla.org/en-US/docs/Web/CSS/WebKit_Extensions#pseudo-elements + !(isPseudoElement && tokens[0].val.startsWith('::-webkit-')) && + !mozExtensions.has(tokens[0].val) && + !webkitExtensions.has(tokens[0].val) && + !(tokens[0].val in config.selectors) && + !(!isPseudoElement && + (':' + tokens[0].val) in config.selectors)) { + if (!options?.lenient || /^(:?)-webkit-/.test(tokens[0].val)) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + // @ts-ignore + node: tokens[0], + syntax: null, + error: 'unknown pseudo-class: ' + tokens[0].val, + tokens + }; + } + } + match++; + tokens.shift(); + consumeWhitespace(tokens); + } + while (tokens.length > 0 && tokens[0].typ == EnumToken.AttrTokenType) { + const children = tokens[0].chi.slice(); + consumeWhitespace(children); + if (children.length == 0) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: null, + error: 'invalid attribute selector', + tokens + }; + } + if (![ + EnumToken.IdenTokenType, + EnumToken.NameSpaceAttributeTokenType, + EnumToken.MatchExpressionTokenType + ].includes(children[0].typ)) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: null, + error: 'invalid attribute selector', + tokens + }; + } + if (children[0].typ == EnumToken.MatchExpressionTokenType) { + if (children.length != 1) { + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: null, + error: 'invalid ', + tokens + }; + } + if (![ + EnumToken.IdenTokenType, + EnumToken.NameSpaceAttributeTokenType + ].includes(children[0].l.typ) || + ![ + EnumToken.EqualMatchTokenType, EnumToken.DashMatchTokenType, + EnumToken.StartMatchTokenType, EnumToken.ContainMatchTokenType, + EnumToken.EndMatchTokenType, EnumToken.IncludeMatchTokenType + ].includes(children[0].op.typ) || + ![ + EnumToken.StringTokenType, + EnumToken.IdenTokenType + ].includes(children[0].r.typ)) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: null, + error: 'invalid attribute selector', + tokens + }; + } + if (children[0].attr != null && !['i', 's'].includes(children[0].attr)) { + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: null, + error: 'invalid attribute selector', + tokens + }; + } + } + match++; + tokens.shift(); + consumeWhitespace(tokens); + } + if (length == tokens.length) { + return { + valid: ValidationLevel.Drop, + matches: [], + // @ts-ignore + node: tokens[0], + // @ts-ignore + syntax: null, + error: 'expected compound selector', + tokens + }; + } + length = tokens.length; + } + return match == 0 ? { + valid: ValidationLevel.Drop, + matches: [], + // @ts-ignore + node: root, + // @ts-ignore + syntax: null, + error: 'expected compound selector', + tokens + } : + // @ts-ignore + { + valid: ValidationLevel.Valid, + matches: [], + // @ts-ignore + node: root, + // @ts-ignore + syntax: null, + error: null, + tokens + }; +} + +export { validateCompoundSelector }; diff --git a/dist/lib/validation/syntaxes/image.js b/dist/lib/validation/syntaxes/image.js new file mode 100644 index 00000000..5730a028 --- /dev/null +++ b/dist/lib/validation/syntaxes/image.js @@ -0,0 +1,29 @@ +import { EnumToken, ValidationLevel } from '../../ast/types.js'; +import '../../ast/minify.js'; +import '../../ast/walk.js'; +import '../../parser/parse.js'; +import '../../renderer/color/utils/constants.js'; +import '../../renderer/sourcemap/lib/encode.js'; +import '../../parser/utils/config.js'; +import { validateSyntax } from '../syntax.js'; +import { getParsedSyntax } from '../config.js'; +import { validateURL } from './url.js'; + +function validateImage(token) { + if (token.typ == EnumToken.UrlFunctionTokenType) { + return validateURL(token); + } + if (token.typ == EnumToken.ImageFunctionTokenType) { + return validateSyntax(getParsedSyntax("syntaxes" /* ValidationSyntaxGroupEnum.Syntaxes */, token.val + '()'), token.chi); + } + return { + valid: ValidationLevel.Drop, + matches: [], + node: token, + syntax: 'image()', + error: 'expected or ', + tokens: [] + }; +} + +export { validateImage }; diff --git a/dist/lib/validation/syntaxes/keyframe-block-list.js b/dist/lib/validation/syntaxes/keyframe-block-list.js index 93a922d6..9c41e035 100644 --- a/dist/lib/validation/syntaxes/keyframe-block-list.js +++ b/dist/lib/validation/syntaxes/keyframe-block-list.js @@ -7,7 +7,7 @@ import '../../renderer/sourcemap/lib/encode.js'; import '../../parser/utils/config.js'; import { validateKeyframeSelector } from './keyframe-selector.js'; -function validateKeyframeBlockList(tokens, atRule) { +function validateKeyframeBlockList(tokens, atRule, options) { let i = 0; let j = 0; let result = null; diff --git a/dist/lib/validation/syntaxes/keyframe-selector.js b/dist/lib/validation/syntaxes/keyframe-selector.js index d47c5c98..d1e60c24 100644 --- a/dist/lib/validation/syntaxes/keyframe-selector.js +++ b/dist/lib/validation/syntaxes/keyframe-selector.js @@ -7,7 +7,7 @@ import '../../renderer/color/utils/constants.js'; import '../../renderer/sourcemap/lib/encode.js'; import '../../parser/utils/config.js'; -function validateKeyframeSelector(tokens, atRule) { +function validateKeyframeSelector(tokens, atRule, options) { consumeWhitespace(tokens); if (tokens.length == 0) { // @ts-ignore diff --git a/dist/lib/validation/syntaxes/relative-selector-list.js b/dist/lib/validation/syntaxes/relative-selector-list.js index f5c6f9bf..80a62ca6 100644 --- a/dist/lib/validation/syntaxes/relative-selector-list.js +++ b/dist/lib/validation/syntaxes/relative-selector-list.js @@ -1,4 +1,4 @@ -import { EnumToken, ValidationLevel } from '../../ast/types.js'; +import { ValidationLevel } from '../../ast/types.js'; import '../../ast/minify.js'; import '../../ast/walk.js'; import '../../parser/parse.js'; @@ -6,22 +6,52 @@ import '../../renderer/color/utils/constants.js'; import '../../renderer/sourcemap/lib/encode.js'; import '../../parser/utils/config.js'; import { validateRelativeSelector } from './relative-selector.js'; +import { consumeWhitespace } from '../utils/whitespace.js'; +import { splitTokenList } from '../utils/list.js'; function validateRelativeSelectorList(tokens, root, options) { - let i = 0; - let j = 0; - let result = null; - while (i + 1 < tokens.length) { - if (tokens[++i].typ == EnumToken.CommaTokenType) { - result = validateRelativeSelector(tokens.slice(j, i), root, options); - if (result.valid == ValidationLevel.Drop) { - return result; - } - j = i + 1; - i = j; + tokens = tokens.slice(); + consumeWhitespace(tokens); + if (tokens.length == 0) { + return { + valid: ValidationLevel.Drop, + matches: [], + // @ts-ignore + node: root, + // @ts-ignore + syntax: null, + error: 'expecting relative selector list', + tokens + }; + } + for (const t of splitTokenList(tokens)) { + if (t.length == 0) { + return { + valid: ValidationLevel.Drop, + matches: [], + // @ts-ignore + node: root, + // @ts-ignore + syntax: null, + error: 'unexpected comma', + tokens + }; + } + const result = validateRelativeSelector(t, root, options); + if (result.valid == ValidationLevel.Drop) { + return result; } } - return validateRelativeSelector(i == j ? tokens.slice(i) : tokens.slice(j, i + 1), root, options); + return { + valid: ValidationLevel.Valid, + matches: [], + // @ts-ignore + node: root, + // @ts-ignore + syntax: null, + error: '', + tokens + }; } export { validateRelativeSelectorList }; diff --git a/dist/lib/validation/utils/list.js b/dist/lib/validation/utils/list.js index f3c8a559..d4ac4017 100644 --- a/dist/lib/validation/utils/list.js +++ b/dist/lib/validation/utils/list.js @@ -6,12 +6,12 @@ import '../../renderer/color/utils/constants.js'; import '../../renderer/sourcemap/lib/encode.js'; import '../../parser/utils/config.js'; -function splitTokenList(tokenList) { +function splitTokenList(tokenList, split = [EnumToken.CommaTokenType]) { return tokenList.reduce((acc, curr) => { if (curr.typ == EnumToken.CommentTokenType) { return acc; } - if (curr.typ == EnumToken.CommaTokenType) { + if (split.includes(curr.typ)) { acc.push([]); } else { diff --git a/dist/node/index.js b/dist/node/index.js index c2f11f28..fe921cce 100644 --- a/dist/node/index.js +++ b/dist/node/index.js @@ -13,7 +13,7 @@ import '../lib/validation/config.js'; import '../lib/validation/parser/types.js'; import '../lib/validation/parser/parse.js'; import '../lib/validation/syntaxes/complex-selector.js'; -import { resolve, dirname } from '../lib/fs/resolve.js'; +import { dirname, resolve } from '../lib/fs/resolve.js'; import { load } from './load.js'; /** diff --git a/dist/web/index.js b/dist/web/index.js index ff320be3..80c9f1d7 100644 --- a/dist/web/index.js +++ b/dist/web/index.js @@ -12,7 +12,7 @@ import '../lib/validation/config.js'; import '../lib/validation/parser/types.js'; import '../lib/validation/parser/parse.js'; import '../lib/validation/syntaxes/complex-selector.js'; -import { resolve, dirname } from '../lib/fs/resolve.js'; +import { dirname, resolve } from '../lib/fs/resolve.js'; import { load } from './load.js'; /** diff --git a/jsr.json b/jsr.json index 1e9e8492..55df19af 100644 --- a/jsr.json +++ b/jsr.json @@ -1,6 +1,6 @@ { "name": "@tbela99/css-parser", - "version": "0.8.0", + "version": "v0.9.0-alpha1", "publish": { "include": [ "src", diff --git a/package.json b/package.json index 11b02983..c419b580 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@tbela99/css-parser", "description": "CSS parser for node and the browser", - "version": "0.8.0", + "version": "v0.9.0", "exports": { ".": "./dist/node/index.js", "./umd": "./dist/index-umd-web.js", @@ -56,15 +56,15 @@ "@rollup/plugin-typescript": "^12.1.2", "@types/chai": "^5.0.1", "@types/mocha": "^10.0.10", - "@types/node": "^22.10.2", - "@types/web": "^0.0.187", - "@web/test-runner": "^0.19.0", + "@types/node": "^22.13.5", + "@types/web": "^0.0.206", + "@web/test-runner": "^0.20.0", "@web/test-runner-playwright": "^0.11.0", "c8": "^10.1.3", "esno": "^4.8.0", - "mocha": "^11.0.1", - "playwright": "^1.49.1", - "rollup": "^4.29.2", + "mocha": "^11.1.0", + "playwright": "^1.50.1", + "rollup": "^4.34.8", "rollup-plugin-dts": "^6.1.1", "tslib": "^2.8.1" } diff --git a/src/@types/ast.d.ts b/src/@types/ast.d.ts index 4de59360..160e2eb1 100644 --- a/src/@types/ast.d.ts +++ b/src/@types/ast.d.ts @@ -69,6 +69,7 @@ export declare interface AstKeyFrameRule extends BaseToken { chi: Array; optimized?: OptimizedSelector; raw?: RawSelectorTokens; + tokens?: Token[] } export declare type RawSelectorTokens = string[][]; @@ -80,6 +81,13 @@ export declare interface OptimizedSelector { reducible: boolean; } +export declare interface OptimizedSelectorToken { + match: boolean; + optimized: Token[]; + selector: Token[][], + reducible: boolean; +} + export declare interface AstAtRule extends BaseToken { typ: EnumToken.AtRuleNodeType, diff --git a/src/@types/index.d.ts b/src/@types/index.d.ts index 58f41fb9..0e3e1d2b 100644 --- a/src/@types/index.d.ts +++ b/src/@types/index.d.ts @@ -1,5 +1,5 @@ import {VisitorNodeMap} from "./visitor.d.ts"; -import {AstAtRule, AstRule, AstRuleStyleSheet, Position, AstDeclaration} from "./ast.d.ts"; +import {AstAtRule, AstDeclaration, AstRule, AstRuleStyleSheet, Position} from "./ast.d.ts"; import {SourceMap} from "../lib/renderer/sourcemap"; import {PropertyListOptions} from "./parse.d.ts"; import {EnumToken} from "../lib"; @@ -39,6 +39,7 @@ export declare interface MinifyFeature { export interface ValidationOptions { validation?: boolean; + lenient?: boolean; } export declare interface ParserOptions extends ValidationOptions, PropertyListOptions { @@ -78,9 +79,7 @@ export declare interface MinifyOptions extends ParserOptions { export declare interface MinifyFeature { ordering: number; - register: (options: MinifyOptions | ParserOptions) => void; - run: (ast: AstRule | AstAtRule, options: ParserOptions, parent: AstRule | AstAtRule | AstRuleStyleSheet, context: { [key: string]: any }) => void; @@ -94,6 +93,7 @@ export declare interface ResolvedPath { export declare interface RenderOptions { minify?: boolean; + beautify?: boolean; removeEmpty?: boolean; expandNestingRules?: boolean; preserveLicense?: boolean; @@ -107,7 +107,6 @@ export declare interface RenderOptions { cwd?: string; load?: (url: string, currentUrl: string) => Promise; resolve?: (url: string, currentUrl: string, currentWorkingDirectory?: string) => ResolvedPath; - } export declare interface TransformOptions extends ParserOptions, RenderOptions { @@ -151,6 +150,7 @@ export declare interface ParseTokenOptions extends ParserOptions { export declare interface TokenizeResult { token: string; + len: number; hint?: EnumToken; position: Position; bytesIn: number; diff --git a/src/@types/token.d.ts b/src/@types/token.d.ts index 0a3cc4a3..4a668fd7 100644 --- a/src/@types/token.d.ts +++ b/src/@types/token.d.ts @@ -449,12 +449,6 @@ export declare interface MediaFeatureOnlyToken extends BaseToken { val: Token; } -export declare interface MediaFeatureNotToken extends BaseToken { - - typ: EnumToken.MediaFeatureNotTokenType, - val: Token; -} - export declare interface MediaFeatureAndToken extends BaseToken { typ: EnumToken.MediaFeatureAndTokenType; diff --git a/src/@types/validation.d.ts b/src/@types/validation.d.ts index baf6ffb5..190f5b3b 100644 --- a/src/@types/validation.d.ts +++ b/src/@types/validation.d.ts @@ -2,6 +2,7 @@ import {ValidationLevel, ValidationSyntaxGroupEnum} from "../lib"; import {AstNode} from "./ast.d.ts"; import {Token} from "./token.d.ts"; import type {ValidationToken} from "../lib/validation/parser"; +import type {ValidationOptions} from "./index"; export declare interface ValidationSyntaxNode { @@ -9,7 +10,7 @@ export declare interface ValidationSyntaxNode { ast?: ValidationToken[]; } -export interface ValidationSelectorOptions { +export interface ValidationSelectorOptions extends ValidationOptions{ nestedSelector?: boolean; } diff --git a/src/lib/ast/expand.ts b/src/lib/ast/expand.ts index a37f135a..9e2a251e 100644 --- a/src/lib/ast/expand.ts +++ b/src/lib/ast/expand.ts @@ -5,6 +5,10 @@ import {renderToken} from "../renderer"; import type {AstAtRule, AstNode, AstRule, AstRuleStyleSheet, Token} from "../../@types"; import {EnumToken} from "./types"; +/** + * expand nested css ast + * @param ast + */ export function expand(ast: AstNode): AstNode { // if (![EnumToken.RuleNodeType, EnumToken.StyleSheetNodeType, EnumToken.AtRuleNodeType].includes(ast.typ)) { @@ -65,7 +69,7 @@ export function expand(ast: AstNode): AstNode { return result; } -function expandRule(node: AstRule, parent?: AstRule): Array { +function expandRule(node: AstRule): Array { const ast: AstRule = {...node, chi: node.chi.slice()}; const result: Array = []; @@ -206,7 +210,7 @@ function expandRule(node: AstRule, parent?: AstRule): Array ast.chi.splice(i--, 1); - result.push(...expandRule(rule, node)); + result.push(...expandRule(rule)); } else if (ast.chi[i].typ == EnumToken.AtRuleNodeType) { @@ -220,7 +224,12 @@ function expandRule(node: AstRule, parent?: AstRule): Array astAtRule.val = replaceCompound(astAtRule.val, ast.sel); } - astAtRule = expand(astAtRule); + const slice = (astAtRule.chi as AstNode[]).slice().filter(t => t.typ == EnumToken.RuleNodeType && ((t as AstRule).sel as string).includes('&')); + + if (slice.length > 0) { + expandRule({...node, chi: (astAtRule.chi as AstNode[]).slice()}); + } + } else { // @ts-ignore @@ -229,7 +238,7 @@ function expandRule(node: AstRule, parent?: AstRule): Array // @ts-ignore astAtRule.chi.length = 0; - for (const r of (>expandRule(clone, node))) { + for (const r of (>expandRule(clone))) { if (r.typ == EnumToken.AtRuleNodeType && 'chi' in r) { @@ -249,7 +258,7 @@ function expandRule(node: AstRule, parent?: AstRule): Array } else if (r.typ == EnumToken.RuleNodeType) { // @ts-ignore - astAtRule.chi.push(...expandRule(r, node)); + astAtRule.chi.push(...expandRule(r)); } else { // @ts-ignore @@ -269,6 +278,11 @@ function expandRule(node: AstRule, parent?: AstRule): Array return ast.chi.length > 0 ? [ast].concat(result) : result; } +/** + * replace compound selector + * @param input + * @param replace + */ export function replaceCompound(input: string, replace: string): string { const tokens: Token[] = parseString(input); diff --git a/src/lib/ast/index.ts b/src/lib/ast/index.ts index 1b3c070b..fdc913d1 100644 --- a/src/lib/ast/index.ts +++ b/src/lib/ast/index.ts @@ -2,5 +2,4 @@ export * from './types'; export * from './minify'; export * from './walk'; -export * from './expand'; -export * from './validate'; \ No newline at end of file +export * from './expand'; \ No newline at end of file diff --git a/src/lib/ast/minify.ts b/src/lib/ast/minify.ts index 40e29422..589f9833 100644 --- a/src/lib/ast/minify.ts +++ b/src/lib/ast/minify.ts @@ -29,6 +29,15 @@ const notEndingWith: string[] = ['(', '['].concat(combinators); // @ts-ignore const features: MinifyFeature[] = Object.values(allFeatures).sort((a, b) => a.ordering - b.ordering) +/** + * minify ast + * @param ast + * @param options + * @param recursive + * @param errors + * @param nestingContent + * @param context + */ export function minify(ast: AstNode, options: ParserOptions | MinifyOptions = {}, recursive: boolean = false, errors?: ErrorDescription[], nestingContent?: boolean, context: { [key: string]: any } = {}): AstNode { @@ -89,6 +98,7 @@ export function minify(ast: AstNode, options: ParserOptions | MinifyOptions = {} return acc; } + // @ts-ignore if ('chi' in ast && ast.chi.length > 0) { @@ -127,7 +137,8 @@ export function minify(ast: AstNode, options: ParserOptions | MinifyOptions = {} if (node.typ == EnumToken.AtRuleNodeType) { - if ((node).nam == 'media' && (node).val == 'all') { + // @ts-ignore + if ((node).nam == 'media' && ['all', '', null].includes((node).val)) { // @ts-ignore ast.chi?.splice(i, 1, ...node.chi); @@ -165,6 +176,7 @@ export function minify(ast: AstNode, options: ParserOptions | MinifyOptions = {} if (node.typ == EnumToken.RuleNodeType) { reduceRuleSelector(node); + let wrapper: AstRule; let match; @@ -540,7 +552,24 @@ export function minify(ast: AstNode, options: ParserOptions | MinifyOptions = {} return ast; } -export function reduceSelector(selector: string[][]) { +function hasDeclaration(node: AstRule): boolean { + + // @ts-ignore + for (let i = 0; i < node.chi?.length; i++) { + + // @ts-ignore + if (node.chi[i].typ == EnumToken.CommentNodeType) { + + continue; + } + // @ts-ignore + return node.chi[i].typ == EnumToken.DeclarationNodeType; + } + + return true; +} + +function reduceSelector(selector: string[][]): OptimizedSelector | null { if (selector.length == 0) { return null; @@ -679,23 +708,10 @@ export function reduceSelector(selector: string[][]) { }; } -export function hasDeclaration(node: AstRule): boolean { - - // @ts-ignore - for (let i = 0; i < node.chi?.length; i++) { - - // @ts-ignore - if (node.chi[i].typ == EnumToken.CommentNodeType) { - - continue; - } - // @ts-ignore - return node.chi[i].typ == EnumToken.DeclarationNodeType; - } - - return true; -} - +/** + * split selector string + * @param buffer + */ export function splitRule(buffer: string): string[][] { const result: string[][] = [[]]; @@ -825,7 +841,7 @@ export function splitRule(buffer: string): string[][] { return result; } -export function matchSelectors(selector1: string[][], selector2: string[][], parentType: EnumToken, errors: ErrorDescription[]): null | MatchedSelector { +function matchSelectors(selector1: string[][], selector2: string[][], parentType: EnumToken, errors: ErrorDescription[]): null | MatchedSelector { let match: string[][] = [[]]; const j: number = Math.min( diff --git a/src/lib/ast/types.ts b/src/lib/ast/types.ts index 8a873d36..2728e320 100644 --- a/src/lib/ast/types.ts +++ b/src/lib/ast/types.ts @@ -1,6 +1,7 @@ export enum ValidationLevel { Valid, - Drop + Drop, + Lenient/* preserve unknown at-rules, declarations and pseudo-classes */ } export enum EnumToken { diff --git a/src/lib/ast/validate.ts b/src/lib/ast/validate.ts deleted file mode 100644 index 658ca16d..00000000 --- a/src/lib/ast/validate.ts +++ /dev/null @@ -1,20 +0,0 @@ -import {EnumToken} from "./types"; -import type {AstNode} from "../../@types/index.d.ts"; - -export function validateNode(node: AstNode, expected?: EnumToken): boolean { - - if (expected != null && node.typ != expected) { - - return false; - } - - else if (!(node.typ in EnumToken)) { - - return false; - } - - // @ts-ignore - // return validateNodeImpl(node); - - return true -} \ No newline at end of file diff --git a/src/lib/ast/walk.ts b/src/lib/ast/walk.ts index e7749e15..dfea988a 100644 --- a/src/lib/ast/walk.ts +++ b/src/lib/ast/walk.ts @@ -12,13 +12,17 @@ import type { WalkResult } from "../../@types/index.d.ts"; import {EnumToken} from "./types"; -import {renderToken} from "../renderer"; export enum WalkerValueEvent { Enter, Leave } +/** + * walk ast nodes + * @param node + * @param filter + */ export function* walk(node: AstNode, filter?: WalkerFilter): Generator { const parents: AstNode[] = [node]; @@ -64,6 +68,13 @@ export function* walk(node: AstNode, filter?: WalkerFilter): Generator 0) { - let value: Token = reverse ? stack.pop() : stack.shift(); + let value: Token = reverse ? stack.pop() : stack.shift(); let option: WalkerOption = null; if (filter.fn != null && filter.event == WalkerValueEvent.Enter) { @@ -149,8 +160,7 @@ export function* walkValues(values: Token[], root: AstNode | Token | null = null if (reverse) { stack.push(...sliced); - } - else { + } else { stack.unshift(...sliced); } diff --git a/src/lib/parser/declaration/map.ts b/src/lib/parser/declaration/map.ts index 9a513b9b..022d5928 100644 --- a/src/lib/parser/declaration/map.ts +++ b/src/lib/parser/declaration/map.ts @@ -1,9 +1,11 @@ import type { AstDeclaration, - IdentToken, PropertiesConfig, + IdentToken, + PropertiesConfig, PropertyMapType, ShorthandMapType, - ShorthandPropertyType, StringToken, + ShorthandPropertyType, + StringToken, Token, WhitespaceToken } from "../../../@types"; @@ -238,81 +240,9 @@ export class PropertyMap { return this; } - private matchTypes(declaration: AstDeclaration) { - - const patterns: string[] = this.pattern.slice(); - const values: Token[] = [...declaration.val]; - - let i: number; - let j: number; - - const map: Map = new Map; - - for (i = 0; i < patterns.length; i++) { - - for (j = 0; j < values.length; j++) { - - if (!map.has(patterns[i])) { - - // @ts-ignore - map.set(patterns[i], this.config.properties?.[patterns[i]]?.constraints?.mapping?.max ?? 1); - } - - let count: number = map.get(patterns[i]); - - if (count > 0 && matchType(values[j], this.config.properties[patterns[i]])) { - - Object.defineProperty(values[j], 'propertyName', { - enumerable: false, - writable: true, - value: patterns[i] - }); - - map.set(patterns[i], --count); - values.splice(j--, 1); - } - } - } - - if (this.config.set != null) { - - for (const [key, val] of Object.entries(this.config.set)) { - - if (map.has(key)) { - - for (const v of val) { - - // missing - if (map.get(v) == 1) { - - let i: number = declaration.val.length; - - while (i--) { - - // @ts-ignore - if (declaration.val[i].propertyName == key) { - - const val: Token = {...declaration.val[i]}; - - Object.defineProperty(val, 'propertyName', { - enumerable: false, - writable: true, - value: v - }); - - declaration.val.splice(i, 0, val, {typ: EnumToken.WhitespaceTokenType}); - } - } - } - } - } - } - } - } - [Symbol.iterator]() { - let iterable: IterableIterator; + let iterable: IterableIterator; let requiredCount: number = 0; let property: string; let isShorthand: boolean = true; @@ -365,9 +295,15 @@ export class PropertyMap { let typ: EnumToken = (EnumToken[this.config.separator?.typ] ?? EnumToken.CommaTokenType); // @ts-ignore - const sep: Token | null = this.config.separator == null ? null : {...this.config.separator, typ: EnumToken[this.config.separator.typ]} as Token; + const sep: Token | null = this.config.separator == null ? null : { + ...this.config.separator, + typ: EnumToken[this.config.separator.typ as keyof typeof EnumToken] + } as Token; // @ts-ignore - const separator: string = this.config.separator ? renderToken({...this.config.separator, typ: EnumToken[this.config.separator.typ]}) : ','; + const separator: string = this.config.separator ? renderToken({ + ...this.config.separator, + typ: EnumToken[this.config.separator.typ as keyof typeof EnumToken] as EnumToken + } as Token) : ','; this.matchTypes(declaration); @@ -500,7 +436,7 @@ export class PropertyMap { continue; } - const config = declaration.nam == this.config.shorthand ? this.config : this.config.properties[declaration.nam] ?? this.config ; + const config = declaration.nam == this.config.shorthand ? this.config : this.config.properties[declaration.nam] ?? this.config; if (!('mapping' in config)) { @@ -663,7 +599,6 @@ export class PropertyMap { }, []); - // @todo remove renderToken call if (props.default.includes(curr[1][i].reduce((acc: string, curr: Token): string => acc + renderToken(curr) + ' ', '').trimEnd())) { if (!this.config.properties[curr[0]].required) { @@ -803,6 +738,7 @@ export class PropertyMap { // @ts-ignore if (values.length == 1 && + // @ts-ignore typeof (values[0]).val == 'string' && this.config.default.includes((values[0]).val.toLowerCase()) && this.config.default[0] != (values[0]).val.toLowerCase()) { @@ -819,15 +755,16 @@ export class PropertyMap { } } - const iterators = []>[]; + const iterators = []>[]; return { // @ts-ignore - next() { + next(): IteratorResult { - let v = iterable.next(); + let v: IteratorResult = iterable.next(); + // @ts-ignore while (v.done || v.value instanceof PropertySet) { if (v.value instanceof PropertySet) { @@ -857,11 +794,83 @@ export class PropertyMap { } } - return v; + return v as IteratorResult; } }; } + private matchTypes(declaration: AstDeclaration) { + + const patterns: string[] = this.pattern.slice(); + const values: Token[] = [...declaration.val]; + + let i: number; + let j: number; + + const map: Map = new Map; + + for (i = 0; i < patterns.length; i++) { + + for (j = 0; j < values.length; j++) { + + if (!map.has(patterns[i])) { + + // @ts-ignore + map.set(patterns[i], this.config.properties?.[patterns[i]]?.constraints?.mapping?.max ?? 1); + } + + let count: number = map.get(patterns[i]); + + if (count > 0 && matchType(values[j], this.config.properties[patterns[i]])) { + + Object.defineProperty(values[j], 'propertyName', { + enumerable: false, + writable: true, + value: patterns[i] + }); + + map.set(patterns[i], --count); + values.splice(j--, 1); + } + } + } + + if (this.config.set != null) { + + for (const [key, val] of Object.entries(this.config.set)) { + + if (map.has(key)) { + + for (const v of val) { + + // missing + if (map.get(v) == 1) { + + let i: number = declaration.val.length; + + while (i--) { + + // @ts-ignore + if (declaration.val[i].propertyName == key) { + + const val: Token = {...declaration.val[i]}; + + Object.defineProperty(val, 'propertyName', { + enumerable: false, + writable: true, + value: v + }); + + declaration.val.splice(i, 0, val, {typ: EnumToken.WhitespaceTokenType}); + } + } + } + } + } + } + } + } + private removeDefaults(map: Map, value: Token[]) { for (const [key, val] of map) { diff --git a/src/lib/parser/declaration/set.ts b/src/lib/parser/declaration/set.ts index e7cc5637..7cd2d1dd 100644 --- a/src/lib/parser/declaration/set.ts +++ b/src/lib/parser/declaration/set.ts @@ -1,8 +1,7 @@ import type { AstDeclaration, DimensionToken, - LiteralToken, - + LiteralToken, NumberToken, ShorthandPropertyType, Token, @@ -179,7 +178,7 @@ export class PropertySet { return this.config.properties.length == this.declarations.size; } - [Symbol.iterator]() { + [Symbol.iterator](): IterableIterator { let iterator: IterableIterator; const declarations: Map = this.declarations; @@ -272,22 +271,8 @@ export class PropertySet { return acc; }, []) }][Symbol.iterator](); - - // return { - // next() { - // - // return iterator.next(); - // } - // } } return iterator; - - // return { - // next() { - // - // return iterator.next(); - // } - // } } } \ No newline at end of file diff --git a/src/lib/parser/parse.ts b/src/lib/parser/parse.ts index bca3b2eb..1a3ea5c7 100644 --- a/src/lib/parser/parse.ts +++ b/src/lib/parser/parse.ts @@ -13,7 +13,8 @@ import { isPseudo, mathFuncs, mediaTypes, - parseDimension + parseDimension, + webkitPseudoAliasMap } from "../syntax"; import {parseDeclarationNode} from './utils'; import {renderToken} from "../renderer"; @@ -120,54 +121,16 @@ const enumTokenHints: Set = new Set([ EnumToken.EOFTokenType ]); -const webkitPseudoAliasMap: Record = { - '-webkit-autofill': 'autofill', - '-webkit-any': 'is', - '-moz-any': 'is', - '-webkit-border-after': 'border-block-end', - '-webkit-border-after-color': 'border-block-end-color', - '-webkit-border-after-style': 'border-block-end-style', - '-webkit-border-after-width': 'border-block-end-width', - '-webkit-border-before': 'border-block-start', - '-webkit-border-before-color': 'border-block-start-color', - '-webkit-border-before-style': 'border-block-start-style', - '-webkit-border-before-width': 'border-block-start-width', - '-webkit-border-end': 'border-inline-end', - '-webkit-border-end-color': 'border-inline-end-color', - '-webkit-border-end-style': 'border-inline-end-style', - '-webkit-border-end-width': 'border-inline-end-width', - '-webkit-border-start': 'border-inline-start', - '-webkit-border-start-color': 'border-inline-start-color', - '-webkit-border-start-style': 'border-inline-start-style', - '-webkit-border-start-width': 'border-inline-start-width', - '-webkit-box-align': 'align-items', - '-webkit-box-direction': 'flex-direction', - '-webkit-box-flex': 'flex-grow', - '-webkit-box-lines': 'flex-flow', - '-webkit-box-ordinal-group': 'order', - '-webkit-box-orient': 'flex-direction', - '-webkit-box-pack': 'justify-content', - '-webkit-column-break-after': 'break-after', - '-webkit-column-break-before': 'break-before', - '-webkit-column-break-inside': 'break-inside', - '-webkit-font-feature-settings': 'font-feature-settings', - '-webkit-hyphenate-character': 'hyphenate-character', - '-webkit-initial-letter': 'initial-letter', - '-webkit-margin-end': 'margin-block-end', - '-webkit-margin-start': 'margin-block-start', - '-webkit-padding-after': 'padding-block-end', - '-webkit-padding-before': 'padding-block-start', - '-webkit-padding-end': 'padding-inline-end', - '-webkit-padding-start': 'padding-inline-start', - '-webkit-min-device-pixel-ratio': 'min-resolution', - '-webkit-max-device-pixel-ratio': 'max-resolution' -} - function reject(reason?: any) { throw new Error(reason ?? 'Parsing aborted'); } +/** + * parse css string + * @param iterator + * @param options + */ export async function doParse(iterator: string, options: ParserOptions = {}): Promise { if (options.signal != null) { @@ -192,6 +155,7 @@ export async function doParse(iterator: string, options: ParserOptions = {}): Pr setParent: true, removePrefix: false, validation: true, + lenient: true, ...options }; @@ -239,12 +203,14 @@ export async function doParse(iterator: string, options: ParserOptions = {}): Pr const iter: Generator = tokenize(iterator); let item: TokenizeResult; + const rawTokens: TokenizeResult[] = []; while (item = iter.next().value) { stats.bytesIn = item.bytesIn; - // + rawTokens.push(item); + // doParse error if (item.hint != null && BadTokensTypes.includes(item.hint)) { @@ -259,7 +225,8 @@ export async function doParse(iterator: string, options: ParserOptions = {}): Pr if (item.token == ';' || item.token == '{') { - let node: AstAtRule | AstRule | AstKeyFrameRule | AstInvalidRule | null = await parseNode(tokens, context, stats, options, errors, src, map); + let node: AstAtRule | AstRule | AstKeyFrameRule | AstInvalidRule | null = await parseNode(tokens, context, stats, options, errors, src, map, rawTokens); + rawTokens.length = 0; if (node != null) { @@ -296,7 +263,9 @@ export async function doParse(iterator: string, options: ParserOptions = {}): Pr map = new Map; } else if (item.token == '}') { - await parseNode(tokens, context, stats, options, errors, src, map); + await parseNode(tokens, context, stats, options, errors, src, map, rawTokens); + rawTokens.length = 0; + const previousNode = stack.pop() as AstNode; // @ts-ignore @@ -328,7 +297,8 @@ export async function doParse(iterator: string, options: ParserOptions = {}): Pr if (tokens.length > 0) { - await parseNode(tokens, context, stats, options, errors, src, map); + await parseNode(tokens, context, stats, options, errors, src, map, rawTokens); + rawTokens.length = 0; if (context != null && context.typ == EnumToken.InvalidRuleTokenType) { @@ -347,6 +317,8 @@ export async function doParse(iterator: string, options: ParserOptions = {}): Pr // @ts-ignore context = stack[stack.length - 1] ?? ast; + + // remove empty nodes // @ts-ignore if (options.removeEmpty && previousNode != null && previousNode.chi.length == 0 && context.chi[context.chi.length - 1] == previousNode) { // @ts-ignore @@ -423,34 +395,6 @@ export async function doParse(iterator: string, options: ParserOptions = {}): Pr } } - // if (options.setParent) { - // - // const nodes: Array = [ast]; - // let node: AstNode; - // - // while ((node = nodes.shift()!)) { - // - // // @ts-ignore - // if (node.chi.length > 0) { - // - // // @ts-ignore - // for (const child of node.chi) { - // - // if (child.parent != node) { - // - // Object.defineProperty(child, 'parent', {...definedPropertySettings, value: node}); - // } - // - // if ('chi' in child && child.chi.length > 0) { - // - // // @ts-ignore - // nodes.push(child); - // } - // } - // } - // } - // } - const endTime: number = performance.now(); if (options.signal != null) { @@ -472,10 +416,27 @@ export async function doParse(iterator: string, options: ParserOptions = {}): Pr } } +function getLastNode(context: AstRuleList | AstInvalidRule | AstInvalidAtRule): AstNode | null { + + let i: number = (context.chi as AstNode[]).length; + + while (i--) { + + if ([EnumToken.CommentTokenType, EnumToken.CDOCOMMTokenType, EnumToken.WhitespaceTokenType].includes((context.chi as AstNode[])[i].typ)) { + + continue; + } + + return (context.chi as AstNode[])[i]; + } + + return null; +} + async function parseNode(results: TokenizeResult[], context: AstRuleList | AstInvalidRule | AstInvalidAtRule, stats: { bytesIn: number; importedBytesIn: number; -}, options: ParserOptions, errors: ErrorDescription[], src: string, map: Map): Promise { +}, options: ParserOptions, errors: ErrorDescription[], src: string, map: Map, rawTokens: TokenizeResult[]): Promise { let tokens: Token[] = []; @@ -555,25 +516,6 @@ async function parseNode(results: TokenizeResult[], context: AstRuleList | AstIn const atRule: AtRuleToken = tokens.shift(); const position: Position = map.get(atRule); - // if (atRule.val == 'charset') { - // - // if (context.typ != EnumToken.StyleSheetNodeType || context.chi.some(t => t.typ != EnumToken.CDOCOMMTokenType && t.typ != EnumToken.CommentNodeType)) { - // - // errors.push({ - // action: 'drop', - // message: 'doParse: invalid @charset', - // location: {src, ...position} - // }); - // - // return null; - // } - // - // if (options.removeCharset) { - // - // return null; - // } - // } - // @ts-ignore while ([EnumToken.WhitespaceTokenType].includes(tokens[0]?.typ)) { tokens.shift(); @@ -698,18 +640,65 @@ async function parseNode(results: TokenizeResult[], context: AstRuleList | AstIn // https://www.w3.org/TR/css-nesting-1/#conditionals // allowed nesting at-rules // there must be a top level rule in the stack + if (atRule.val == 'charset') { + let spaces: number = 0; - if (atRule.val == 'charset' && options.removeCharset) { + // https://developer.mozilla.org/en-US/docs/Web/CSS/@charset + for (let k = 1; k < rawTokens.length; k++) { - return null; + if (rawTokens[k].hint == EnumToken.WhitespaceTokenType) { + + spaces+= rawTokens[k].len; + continue; + } + + if (rawTokens[k].hint == EnumToken.CommentTokenType) { + + continue; + } + + if (rawTokens[k].hint == EnumToken.CDOCOMMTokenType) { + continue; + } + + if (spaces > 1) { + + errors.push({ + action: 'drop', + message: '@charset must have only one space', + // @ts-ignore + location: {src, ...(map.get(atRule) ?? position)} + }); + + return null; + } + + if (rawTokens[k].hint != EnumToken.StringTokenType || rawTokens[k].token[0] != '"') { + + errors.push({ + action: 'drop', + message: '@charset expects a ""', + // @ts-ignore + location: {src, ...(map.get(atRule) ?? position)} + }); + + return null; + } + + break; + } + + if (options.removeCharset) { + + return null; + } } - const t = parseAtRulePrelude(parseTokens(tokens, {minify: options.minify}), atRule) as Token[]; + const t: Token[] = parseAtRulePrelude(parseTokens(tokens, {minify: options.minify}), atRule) as Token[]; const raw: string[] = t.reduce((acc: string[], curr: Token) => { acc.push(renderToken(curr, {removeComments: true})); - return acc }, []); @@ -741,12 +730,47 @@ async function parseNode(results: TokenizeResult[], context: AstRuleList | AstIn if (options.validation) { - const valid: ValidationResult = validateAtRule(node, options, context); + let isValid: boolean = true; + + if (node.nam == 'else') { + + const prev = getLastNode(context); + + if (prev != null && prev.typ == EnumToken.AtRuleNodeType && ['when', 'else'].includes((prev).nam)) { + + if ((prev).nam == 'else') { + + isValid = Array.isArray((prev).tokens) && ((prev).tokens as Token[]).length > 0; + } + } else { + + isValid = false; + } + } + + const valid: ValidationResult = isValid ? validateAtRule(node, options, context) : { + valid: ValidationLevel.Drop, + node, + matches: [] as Token[], + syntax: '@' + node.nam, + error: '@' + node.nam + ' not allowed here', + tokens + } as ValidationResult; if (valid.valid == ValidationLevel.Drop) { + errors.push({ + action: 'drop', + message: valid.error + ' - "' + tokens.reduce((acc, curr) => acc + renderToken(curr, {minify: false}), '') + '"', + // @ts-ignore + location: {src, ...(map.get(valid.node) ?? position)} + }); + // @ts-ignore node.typ = EnumToken.InvalidAtRuleTokenType; + } else { + + node.val = (node.tokens as Token[]).reduce((acc, curr) => acc + renderToken(curr, {minify: false, removeComments: true}), ''); } } @@ -992,15 +1016,24 @@ async function parseNode(results: TokenizeResult[], context: AstRuleList | AstIn if (result != null) { - if (options.validation) { - - // const valid: ValidationResult = validateDeclaration(result, options, context); - // - // if (valid.valid == ValidationLevel.Drop) { - // - // return null; - // } - } + // if (options.validation) { + // + // const valid: ValidationResult = validateDeclaration(result, options, context); + // + // console.error({valid}); + // + // if (valid.valid == ValidationLevel.Drop) { + // + // errors.push({ + // action: 'drop', + // message: valid.error + ' - "' + tokens.reduce((acc, curr) => acc + renderToken(curr, {minify: false}), '') + '"', + // // @ts-ignore + // location: {src, ...(map.get(valid.node) ?? position)} + // }); + // + // return null; + // } + // } // @ts-ignore context.chi.push(result); @@ -1012,7 +1045,12 @@ async function parseNode(results: TokenizeResult[], context: AstRuleList | AstIn } } -export function parseAtRulePrelude(tokens: Token[], atRule: AtRuleToken): Token[] { +/** + * parse at-rule prelude + * @param tokens + * @param atRule + */ + function parseAtRulePrelude(tokens: Token[], atRule: AtRuleToken): Token[] { // @ts-ignore for (const {value, parent} of walkValues(tokens, null, null, true)) { @@ -1113,7 +1151,7 @@ export function parseAtRulePrelude(tokens: Token[], atRule: AtRuleToken): Token[ } } - if (value.typ == EnumToken.ParensTokenType) { + if (value.typ == EnumToken.ParensTokenType || (value.typ == EnumToken.FunctionTokenType && ['media', 'supports', 'style', 'scroll-state'].includes((value).val))) { // @todo parse range and declarations // parseDeclaration(parent.chi); @@ -1122,6 +1160,8 @@ export function parseAtRulePrelude(tokens: Token[], atRule: AtRuleToken): Token[ let nameIndex: number = -1; let valueIndex: number = -1; + const dashedIdent: boolean = value.typ == EnumToken.FunctionTokenType && value.val == 'style'; + for (let i = 0; i < value.chi.length; i++) { if (value.chi[i].typ == EnumToken.CommentTokenType || value.chi[i].typ == EnumToken.WhitespaceTokenType) { @@ -1129,7 +1169,7 @@ export function parseAtRulePrelude(tokens: Token[], atRule: AtRuleToken): Token[ continue; } - if (value.chi[i].typ == EnumToken.IdenTokenType) { + if ((dashedIdent && value.chi[i].typ == EnumToken.DashedIdenTokenType) || value.chi[i].typ == EnumToken.IdenTokenType || value.chi[i].typ == EnumToken.FunctionTokenType || value.chi[i].typ == EnumToken.ColorTokenType) { nameIndex = i; } @@ -1172,6 +1212,15 @@ export function parseAtRulePrelude(tokens: Token[], atRule: AtRuleToken): Token[ const val = value.chi.splice(valueIndex, 1)[0] as Token; const node = value.chi.splice(nameIndex, 1)[0] as IdentToken; + // 'background' + // @ts-ignore + if (node.typ == EnumToken.ColorTokenType && (node as ColorToken).kin == 'dpsys') { + + // @ts-ignore + delete node.kin; + node.typ = EnumToken.IdenTokenType; + } + while (value.chi[0]?.typ == EnumToken.WhitespaceTokenType) { value.chi.shift(); @@ -1194,6 +1243,10 @@ export function parseAtRulePrelude(tokens: Token[], atRule: AtRuleToken): Token[ return tokens; } +/** + * parse selector + * @param tokens + */ export function parseSelector(tokens: Token[]): Token[] { for (const {value, previousValue, nextValue, parent} of walkValues(tokens)) { @@ -1381,6 +1434,11 @@ export function parseSelector(tokens: Token[]): Token[] { // return doParse(`.x{${src}`, options).then((result: ParseResult) => (result.ast.chi[0]).chi.filter(t => t.typ == EnumToken.DeclarationNodeType)); // } +/** + * parse string + * @param src + * @param options + */ export function parseString(src: string, options: { location: boolean } = {location: false}): Token[] { return parseTokens([...tokenize(src)].map(t => { @@ -1498,7 +1556,7 @@ function getTokenType(val: string, hint?: EnumToken): Token { }; } - if (['linear-gradient', 'radial-gradient', 'repeating-linear-gradient', 'repeating-radial-gradient', 'conic-gradient', 'image', 'image-set', 'element', 'cross-fade'].includes(val)) { + if (['linear-gradient', 'radial-gradient', 'repeating-linear-gradient', 'repeating-radial-gradient', 'conic-gradient', 'image', 'image-set', 'element', 'cross-fade', 'paint'].includes(val)) { return { typ: EnumToken.ImageFunctionTokenType, val, @@ -1618,6 +1676,11 @@ function getTokenType(val: string, hint?: EnumToken): Token { }; } +/** + * parse token list + * @param tokens + * @param options + */ export function parseTokens(tokens: Token[], options: ParseTokenOptions = {}): Token[] { for (let i = 0; i < tokens.length; i++) { diff --git a/src/lib/parser/tokenize.ts b/src/lib/parser/tokenize.ts index 802876f6..d2624efd 100644 --- a/src/lib/parser/tokenize.ts +++ b/src/lib/parser/tokenize.ts @@ -24,10 +24,15 @@ function consumeWhiteSpace(parseInfo: ParseInfo): number { return count; } - function pushToken(token: string, parseInfo: ParseInfo, hint?: EnumToken): TokenizeResult { - const result = {token, hint, position: {...parseInfo.position}, bytesIn: parseInfo.currentPosition.ind + 1}; + const result = { + token, + len: parseInfo.currentPosition.ind - parseInfo.position.ind, + hint, + position: {...parseInfo.position}, + bytesIn: parseInfo.currentPosition.ind + 1 + }; parseInfo.position.ind = parseInfo.currentPosition.ind; parseInfo.position.lin = parseInfo.currentPosition.lin; @@ -200,7 +205,10 @@ function next(parseInfo: ParseInfo, count: number = 1): string { return char; } - +/** + * tokenize css string + * @param stream + */ export function* tokenize(stream: InputStream): Generator { const parseInfo: ParseInfo = { @@ -264,13 +272,17 @@ export function* tokenize(stream: InputStream): Generator { buffer = ''; break; } + } else { buffer += value; } } - yield pushToken(buffer, parseInfo, EnumToken.BadCommentTokenType); - buffer = ''; + if (buffer.length > 0) { + + yield pushToken(buffer, parseInfo, EnumToken.BadCommentTokenType); + buffer = ''; + } } break; @@ -326,7 +338,7 @@ export function* tokenize(stream: InputStream): Generator { } break; - + case '#': if (buffer.length > 0) { diff --git a/src/lib/renderer/render.ts b/src/lib/renderer/render.ts index 453ff2f1..33ce9c57 100644 --- a/src/lib/renderer/render.ts +++ b/src/lib/renderer/render.ts @@ -98,18 +98,30 @@ function update(position: Position, str: string) { } } +/** + * render ast + * @param data + * @param options + */ export function doRender(data: AstNode, options: RenderOptions = {}): RenderResult { + const minify: boolean = options.minify ?? true; + const beautify: boolean = options.beautify ?? !minify; options = { - ...(options.minify ?? true ? { + ...(beautify ? { + + indent: ' ', + newLine: '\n', + } : { + indent: '', newLine: '', + }), + ...(minify ? { removeEmpty: true, removeComments: true } : { - indent: ' ', - newLine: '\n', - compress: false, + removeEmpty: false, removeComments: false, }), sourcemap: false, convertColor: true, expandNestingRules: false, preserveLicense: false, ...options @@ -211,7 +223,18 @@ function updateSourceMap(node: AstRuleList | AstComment, options: RenderOptions, update(position, str); } -// @ts-ignore +/** + * render ast node + * @param data + * @param options + * @param sourcemap + * @param position + * @param errors + * @param reducer + * @param cache + * @param level + * @param indents + */ function renderAstNode(data: AstNode, options: RenderOptions, sourcemap: SourceMap | null, position: Position, errors: ErrorDescription[], reducer: (acc: string, curr: Token) => string, cache: { [key: string]: any }, level: number = 0, indents: string[] = []): string { @@ -356,10 +379,16 @@ function renderAstNode(data: AstNode, options: RenderOptions, sourcemap: SourceM // return renderToken(data as Token, options, cache, reducer, errors); throw new Error(`render: unexpected token ${JSON.stringify(data, null, 1)}`); } - - return ''; } +/** + * render ast token + * @param token + * @param options + * @param cache + * @param reducer + * @param errors + */ export function renderToken(token: Token, options: RenderOptions = {}, cache: { [key: string]: any } = Object.create(null), reducer?: (acc: string, curr: Token) => string, errors?: ErrorDescription[]): string { diff --git a/src/lib/syntax/syntax.ts b/src/lib/syntax/syntax.ts index 036e5bce..ded42dcf 100644 --- a/src/lib/syntax/syntax.ts +++ b/src/lib/syntax/syntax.ts @@ -36,6 +36,345 @@ export const mediaTypes: string[] = ['all', 'print', 'screen', // https://www.w3.org/TR/css-values-4/#math-function export const mathFuncs: string[] = ['calc', 'clamp', 'min', 'max', 'round', 'mod', 'rem', 'sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'atan2', 'pow', 'sqrt', 'hypot', 'log', 'exp', 'abs', 'sign']; +export const webkitPseudoAliasMap: Record = { + '-webkit-autofill': 'autofill', + '-webkit-any': 'is', + '-moz-any': 'is', + '-webkit-border-after': 'border-block-end', + '-webkit-border-after-color': 'border-block-end-color', + '-webkit-border-after-style': 'border-block-end-style', + '-webkit-border-after-width': 'border-block-end-width', + '-webkit-border-before': 'border-block-start', + '-webkit-border-before-color': 'border-block-start-color', + '-webkit-border-before-style': 'border-block-start-style', + '-webkit-border-before-width': 'border-block-start-width', + '-webkit-border-end': 'border-inline-end', + '-webkit-border-end-color': 'border-inline-end-color', + '-webkit-border-end-style': 'border-inline-end-style', + '-webkit-border-end-width': 'border-inline-end-width', + '-webkit-border-start': 'border-inline-start', + '-webkit-border-start-color': 'border-inline-start-color', + '-webkit-border-start-style': 'border-inline-start-style', + '-webkit-border-start-width': 'border-inline-start-width', + '-webkit-box-align': 'align-items', + '-webkit-box-direction': 'flex-direction', + '-webkit-box-flex': 'flex-grow', + '-webkit-box-lines': 'flex-flow', + '-webkit-box-ordinal-group': 'order', + '-webkit-box-orient': 'flex-direction', + '-webkit-box-pack': 'justify-content', + '-webkit-column-break-after': 'break-after', + '-webkit-column-break-before': 'break-before', + '-webkit-column-break-inside': 'break-inside', + '-webkit-font-feature-settings': 'font-feature-settings', + '-webkit-hyphenate-character': 'hyphenate-character', + '-webkit-initial-letter': 'initial-letter', + '-webkit-margin-end': 'margin-block-end', + '-webkit-margin-start': 'margin-block-start', + '-webkit-padding-after': 'padding-block-end', + '-webkit-padding-before': 'padding-block-start', + '-webkit-padding-end': 'padding-inline-end', + '-webkit-padding-start': 'padding-inline-start', + '-webkit-min-device-pixel-ratio': 'min-resolution', + '-webkit-max-device-pixel-ratio': 'max-resolution' +} + +// https://developer.mozilla.org/en-US/docs/Web/CSS/WebKit_Extensions +// https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-scrollbar +export const webkitExtensions = new Set([ + '-webkit-app-region', + '-webkit-border-horizontal-spacing', + '-webkit-border-vertical-spacing', + '-webkit-box-reflect', + '-webkit-column-axis', + '-webkit-column-progression', + '-webkit-cursor-visibility', + '-webkit-font-smoothing', + '-webkit-hyphenate-limit-after', + '-webkit-hyphenate-limit-before', + '-webkit-hyphenate-limit-lines', + '-webkit-line-align', + '-webkit-line-box-contain', + '-webkit-line-clamp', + '-webkit-line-grid', + '-webkit-line-snap', + '-webkit-locale', + '-webkit-logical-height', + '-webkit-logical-width', + '-webkit-margin-after', + '-webkit-margin-before', + '-webkit-mask-box-image-outset', + '-webkit-mask-box-image-repeat', + '-webkit-mask-box-image-slice', + '-webkit-mask-box-image-source', + '-webkit-mask-box-image-width', + '-webkit-mask-box-image', + '-webkit-mask-composite', + '-webkit-mask-position-x', + '-webkit-mask-position-y', + '-webkit-mask-repeat-x', + '-webkit-mask-repeat-y', + '-webkit-mask-source-type', + '-webkit-max-logical-height', + '-webkit-max-logical-width', + '-webkit-min-logical-height', + '-webkit-min-logical-width', + '-webkit-nbsp-mode', + '-webkit-perspective-origin-x', + '-webkit-perspective-origin-y', + '-webkit-rtl-ordering', + '-webkit-tap-highlight-color', + '-webkit-text-decoration-skip', + '-webkit-text-decorations-in-effect', + '-webkit-text-fill-color', + '-webkit-text-security', + '-webkit-text-stroke-color', + '-webkit-text-stroke-width', + '-webkit-text-stroke', + '-webkit-text-zoom', + '-webkit-touch-callout', + '-webkit-transform-origin-x', + '-webkit-transform-origin-y', + '-webkit-transform-origin-z', + '-webkit-user-drag', + '-webkit-user-modify', + '-webkit-border-after', + '-webkit-border-after-color', + '-webkit-border-after-style', + '-webkit-border-after-width', + '-webkit-border-before', + '-webkit-border-before-color', + '-webkit-border-before-style', + '-webkit-border-before-width', + '-webkit-border-end', + '-webkit-border-end-color', + '-webkit-border-end-style', + '-webkit-border-end-width', + '-webkit-border-start', + '-webkit-border-start-color', + '-webkit-border-start-style', + '-webkit-border-start-width', + '-webkit-box-align', + '-webkit-box-direction', + '-webkit-box-flex-group', + '-webkit-box-flex', + '-webkit-box-lines', + '-webkit-box-ordinal-group', + '-webkit-box-orient', + '-webkit-box-pack', + '-webkit-column-break-after', + '-webkit-column-break-before', + '-webkit-column-break-inside', + '-webkit-font-feature-settings', + '-webkit-hyphenate-character', + '-webkit-initial-letter', + '-webkit-margin-end', + '-webkit-margin-start', + '-webkit-padding-after', + '-webkit-padding-before', + '-webkit-padding-end', + '-webkit-padding-start', + '-webkit-fill-available', + ':-webkit-animating-full-screen-transition', + ':-webkit-any', + ':-webkit-any-link', + ':-webkit-autofill', + ':-webkit-autofill-strong-password', + ':-webkit-drag', + ':-webkit-full-page-media', + ':-webkit-full-screen*', + ':-webkit-full-screen-ancestor', + ':-webkit-full-screen-document', + ':-webkit-full-screen-controls-hidden', + '::-webkit-file-upload-button*', + '::-webkit-inner-spin-button', + '::-webkit-input-placeholder', + '::-webkit-meter-bar', + '::-webkit-meter-even-less-good-value', + '::-webkit-meter-inner-element', + '::-webkit-meter-optimum-value', + '::-webkit-meter-suboptimum-value', + '::-webkit-progress-bar', + '::-webkit-progress-inner-element', + '::-webkit-progress-value', + '::-webkit-search-cancel-button', + '::-webkit-search-results-button', + '::-webkit-slider-runnable-track', + '::-webkit-slider-thumb', + '-webkit-animation', + '-webkit-device-pixel-ratio', + '-webkit-transform-2d', + '-webkit-transform-3d', + '-webkit-transition', + '::-webkit-scrollbar', + '::-webkit-scrollbar-button', + '::-webkit-scrollbar', + '::-webkit-scrollbar-thumb', + '::-webkit-scrollbar-track', + '::-webkit-scrollbar-track-piece', + '::-webkit-scrollbar:vertical', + '::-webkit-scrollbar-corner ', + '::-webkit-resizer', + ':vertical', + ':horizontal', +]); + +// https://developer.mozilla.org/en-US/docs/Web/CSS/Mozilla_Extensions +export const mozExtensions = new Set([ + '-moz-box-align', + '-moz-box-direction', + '-moz-box-flex', + '-moz-box-ordinal-group', + '-moz-box-orient', + '-moz-box-pack', + '-moz-float-edge', + '-moz-force-broken-image-icon', + '-moz-image-region', + '-moz-orient', + '-moz-osx-font-smoothing', + '-moz-user-focus', + '-moz-user-input', + '-moz-user-modify', + '-moz-animation', + '-moz-animation-delay', + '-moz-animation-direction', + '-moz-animation-duration', + '-moz-animation-fill-mode', + '-moz-animation-iteration-count', + '-moz-animation-name', + '-moz-animation-play-state', + '-moz-animation-timing-function', + '-moz-appearance', + '-moz-backface-visibility', + '-moz-background-clip', + '-moz-background-origin', + '-moz-background-inline-policy', + '-moz-background-size', + '-moz-border-end', + '-moz-border-end-color', + '-moz-border-end-style', + '-moz-border-end-width', + '-moz-border-image', + '-moz-border-start', + '-moz-border-start-color', + '-moz-border-start-style', + '-moz-border-start-width', + '-moz-box-sizing', + 'clip-path', + '-moz-column-count', + '-moz-column-fill', + '-moz-column-gap', + '-moz-column-width', + '-moz-column-rule', + '-moz-column-rule-width', + '-moz-column-rule-style', + '-moz-column-rule-color', + 'filter', + '-moz-font-feature-settings', + '-moz-font-language-override', + '-moz-hyphens', + '-moz-margin-end', + '-moz-margin-start', + 'mask', + '-moz-opacity', + '-moz-outline', + '-moz-outline-color', + '-moz-outline-offset', + '-moz-outline-style', + '-moz-outline-width', + '-moz-padding-end', + '-moz-padding-start', + '-moz-perspective', + '-moz-perspective-origin', + 'pointer-events', + '-moz-tab-size', + '-moz-text-align-last', + '-moz-text-decoration-color', + '-moz-text-decoration-line', + '-moz-text-decoration-style', + '-moz-text-size-adjust', + '-moz-transform', + '-moz-transform-origin', + '-moz-transform-style', + '-moz-transition', + '-moz-transition-delay', + '-moz-transition-duration', + '-moz-transition-property', + '-moz-transition-timing-function', + '-moz-user-select', + '-moz-initial', + '-moz-appearance', + '-moz-linear-gradient', + '-moz-radial-gradient', + '-moz-element', + '-moz-image-rect', + '::-moz-anonymous-block', + '::-moz-anonymous-positioned-block', + ':-moz-any', + ':-moz-any-link', + ':-moz-broken', + '::-moz-canvas', + '::-moz-color-swatch', + '::-moz-cell-content', + ':-moz-drag-over', + ':-moz-first-node', + '::-moz-focus-inner', + '::-moz-focus-outer', + ':-moz-full-screen', + ':-moz-full-screen-ancestor', + ':-moz-handler-blocked', + ':-moz-handler-crashed', + ':-moz-handler-disabled', + '::-moz-inline-table', + ':-moz-last-node', + '::-moz-list-bullet', + '::-moz-list-number', + ':-moz-loading', + ':-moz-locale-dir', + ':-moz-locale-dir', + ':-moz-lwtheme', + ':-moz-lwtheme-brighttext', + ':-moz-lwtheme-darktext', + '::-moz-meter-bar', + ':-moz-native-anonymous', + ':-moz-only-whitespace', + '::-moz-pagebreak', + '::-moz-pagecontent', + ':-moz-placeholder', + '::-moz-placeholder', + '::-moz-progress-bar', + '::-moz-range-progress', + '::-moz-range-thumb', + '::-moz-range-track', + ':-moz-read-only', + ':-moz-read-write', + '::-moz-scrolled-canvas', + '::-moz-scrolled-content', + '::-moz-selection', + ':-moz-submit-invalid', + ':-moz-suppressed', + '::-moz-svg-foreign-content', + '::-moz-table', + '::-moz-table-cell', + '::-moz-table-column', + '::-moz-table-column-group', + '::-moz-table-outer', + '::-moz-table-row', + '::-moz-table-row-group', + ':-moz-ui-invalid', + ':-moz-ui-valid', + ':-moz-user-disabled', + '::-moz-viewport', + '::-moz-viewport-scroll', + ':-moz-window-inactive', + '-moz-device-pixel-ratio', + '-moz-os-version', + '-moz-touch-enabled', + '-moz-windows-glass', + '-moz-alt-content' + ] +); + export function isLength(dimension: DimensionToken): boolean { return 'unit' in dimension && dimensionUnits.has(dimension.unit.toLowerCase()); @@ -706,4 +1045,4 @@ export function isWhiteSpace(codepoint: number): boolean { return codepoint == 0x9 || codepoint == 0x20 || // isNewLine codepoint == 0xa || codepoint == 0xc || codepoint == 0xd; -} \ No newline at end of file +} diff --git a/src/lib/validation/at-rules/container.ts b/src/lib/validation/at-rules/container.ts new file mode 100644 index 00000000..00f63804 --- /dev/null +++ b/src/lib/validation/at-rules/container.ts @@ -0,0 +1,451 @@ +import type {AstAtRule, AstNode, MediaFeatureNotToken, Token, ValidationOptions} from "../../../@types"; +import type {ValidationSyntaxResult} from "../../../@types/validation"; +import {EnumToken, ValidationLevel} from "../../ast"; +import {consumeWhitespace, splitTokenList} from "../utils"; + +const validateContainerScrollStateFeature = validateContainerSizeFeature; + +export function validateAtRuleContainer(atRule: AstAtRule, options: ValidationOptions, root?: AstNode): ValidationSyntaxResult { + + // media-query-list + if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { + + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected supports query list', + tokens: [] + } as ValidationSyntaxResult; + } + + const result: ValidationSyntaxResult = validateAtRuleContainerQueryList(atRule.tokens, atRule); + + if (result.valid == ValidationLevel.Drop) { + + return result; + } + + if (!('chi' in atRule)) { + + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected at-rule body', + tokens: [] + } as ValidationSyntaxResult; + } + + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: '', + tokens: [] + } as ValidationSyntaxResult; +} + +function validateAtRuleContainerQueryList(tokens: Token[], atRule: AstAtRule): ValidationSyntaxResult { + + if (tokens.length == 0) { + + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query list', + tokens + } as ValidationSyntaxResult; + } + + let result: ValidationSyntaxResult | null = null; + let tokenType: EnumToken | null = null; + + for (const queries of splitTokenList(tokens)) { + + consumeWhitespace(queries); + + if (queries.length == 0) { + + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query list', + tokens + } + } + + result = null; + const match: Token[] = []; + let token: Token | null = null; + + tokenType = null; + + while (queries.length > 0) { + + if (queries.length == 0) { + + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query list', + tokens + } + } + + if (queries[0].typ == EnumToken.IdenTokenType) { + + match.push(queries.shift() as Token); + consumeWhitespace(queries); + } + + if (queries.length == 0) { + + break; + } + + token = queries[0]; + + if (token.typ == EnumToken.MediaFeatureNotTokenType) { + + token = token.val; + } + + if (token.typ != EnumToken.ParensTokenType && (token.typ != EnumToken.FunctionTokenType || !['scroll-state', 'style'].includes(token.val))) { + + return { + valid: ValidationLevel.Drop, + matches: [], + node: queries[0], + syntax: '@' + atRule.nam, + error: 'expected container query-in-parens', + tokens + } + } + + if (token.typ == EnumToken.ParensTokenType) { + + result = validateContainerSizeFeature(token.chi, atRule); + } else if (token.val == 'scroll-state') { + + result = validateContainerScrollStateFeature(token.chi, atRule); + } else { + + result = validateContainerStyleFeature(token.chi, atRule); + } + + if (result.valid == ValidationLevel.Drop) { + + return result; + } + + queries.shift(); + consumeWhitespace(queries); + + if (queries.length == 0) { + + break; + } + + token = queries[0]; + + if (token.typ != EnumToken.MediaFeatureAndTokenType && token.typ != EnumToken.MediaFeatureOrTokenType) { + + return { + valid: ValidationLevel.Drop, + matches: [], + node: queries[0], + syntax: '@' + atRule.nam, + error: 'expecting and/or container query token', + tokens + } + } + + if (tokenType == null) { + + tokenType = token.typ; + } + + if (tokenType != token.typ) { + + return { + valid: ValidationLevel.Drop, + matches: [], + node: queries[0], + syntax: '@' + atRule.nam, + error: 'mixing and/or not allowed at the same level', + tokens + } + } + + queries.shift(); + consumeWhitespace(queries); + + if (queries.length == 0) { + + return { + valid: ValidationLevel.Drop, + matches: [], + node: queries[0], + syntax: '@' + atRule.nam, + error: 'expected container query-in-parens', + tokens + } + } + } + } + + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: '', + tokens + } +} + +function validateContainerStyleFeature(tokens: Token[], atRule: AstAtRule): ValidationSyntaxResult { + + tokens = tokens.slice(); + + consumeWhitespace(tokens); + + if (tokens.length == 1) { + + if (tokens[0].typ == EnumToken.ParensTokenType) { + + return validateContainerStyleFeature(tokens[0].chi, atRule); + } + + if ([EnumToken.DashedIdenTokenType, EnumToken.IdenTokenType].includes(tokens[0].typ) || + ( tokens[0].typ == EnumToken.MediaQueryConditionTokenType &&tokens[0].op.typ == EnumToken.ColonTokenType)) { + + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: '', + tokens + } + } + } + + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query features', + tokens + } +} + +function validateContainerSizeFeature(tokens: Token[], atRule: AstAtRule): ValidationSyntaxResult { + + tokens = tokens.slice(); + consumeWhitespace(tokens); + + if (tokens.length == 0) { + + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query features', + tokens + } + } + + if (tokens.length == 1) { + + const token: Token = tokens[0]; + + if (token.typ == EnumToken.MediaFeatureNotTokenType) { + + return validateContainerSizeFeature([(token as MediaFeatureNotToken).val], atRule); + } + + if (token.typ == EnumToken.ParensTokenType) { + + return validateAtRuleContainerQueryStyleInParams(token.chi, atRule); + } + + if (![EnumToken.DashedIdenTokenType, EnumToken.MediaQueryConditionTokenType].includes(tokens[0].typ)) { + + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query features', + tokens + } + } + + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: '', + tokens + } + } + + return validateAtRuleContainerQueryStyleInParams(tokens, atRule); +} + +function validateAtRuleContainerQueryStyleInParams(tokens: Token[], atRule: AstAtRule): ValidationSyntaxResult { + + tokens = tokens.slice(); + consumeWhitespace(tokens); + + if (tokens.length == 0) { + + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query features', + tokens + } + } + + let token: Token = tokens[0]; + let tokenType: EnumToken | null = null; + let result: ValidationSyntaxResult | null = null; + + while (tokens.length > 0) { + + token = tokens[0]; + + if (token.typ == EnumToken.MediaFeatureNotTokenType) { + + token = token.val; + } + + if (tokens[0].typ != EnumToken.ParensTokenType) { + + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query-in-parens', + tokens + } + } + + const slices = tokens[0].chi.slice(); + + consumeWhitespace(slices); + + if (slices.length == 1) { + + if ([EnumToken.MediaFeatureNotTokenType, EnumToken.ParensTokenType].includes(slices[0].typ)) { + + result = validateAtRuleContainerQueryStyleInParams(slices, atRule); + + if (result.valid == ValidationLevel.Drop) { + + return result; + } + } else if (![EnumToken.DashedIdenTokenType, EnumToken.MediaQueryConditionTokenType].includes(slices[0].typ)) { + + result = { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: 'expected container query features', + tokens + } + } + } else { + + result = validateAtRuleContainerQueryStyleInParams(slices, atRule); + + if (result.valid == ValidationLevel.Drop) { + + return result; + } + } + + tokens.shift(); + consumeWhitespace(tokens); + + if (tokens.length == 0) { + + break; + } + + if (![EnumToken.MediaFeatureAndTokenType, EnumToken.MediaFeatureOrTokenType].includes(tokens[0].typ)) { + + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: '@' + atRule.nam, + error: 'expecting and/or container query token', + tokens + } + } + + if (tokenType == null) { + + tokenType = tokens[0].typ; + } + + if (tokenType != tokens[0].typ) { + + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: '@' + atRule.nam, + error: 'mixing and/or not allowed at the same level', + tokens + } + } + + tokens.shift(); + consumeWhitespace(tokens); + + if (tokens.length == 0) { + + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: '@' + atRule.nam, + error: 'expected container query-in-parens', + tokens + } + } + } + + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@' + atRule.nam, + error: '', + tokens + } +} \ No newline at end of file diff --git a/src/lib/validation/at-rules/counter-style.ts b/src/lib/validation/at-rules/counter-style.ts index dde7134a..a168f03e 100644 --- a/src/lib/validation/at-rules/counter-style.ts +++ b/src/lib/validation/at-rules/counter-style.ts @@ -13,7 +13,7 @@ export function validateAtRuleCounterStyle(atRule: AstAtRule, options: Validatio matches: [], node: atRule, syntax: '@counter-style', - error: 'expected media query list', + error: 'expected counter style name', tokens: [] } as ValidationSyntaxResult; } @@ -24,7 +24,7 @@ export function validateAtRuleCounterStyle(atRule: AstAtRule, options: Validatio // @ts-ignore return { - valid: ValidationLevel.Valid, + valid: ValidationLevel.Drop, matches: [], node: atRule, syntax: '@counter-style', diff --git a/src/lib/validation/at-rules/custom-media.ts b/src/lib/validation/at-rules/custom-media.ts new file mode 100644 index 00000000..bb9bffcf --- /dev/null +++ b/src/lib/validation/at-rules/custom-media.ts @@ -0,0 +1,58 @@ +import type {AstAtRule, AstNode, Token, ValidationOptions} from "../../../@types"; +import type {ValidationSyntaxResult} from "../../../@types/validation"; +import {EnumToken, ValidationLevel} from "../../ast"; +import {consumeWhitespace} from "../utils"; +import {validateAtRuleMediaQueryList} from "./media"; + +export function validateAtRuleCustomMedia(atRule: AstAtRule, options: ValidationOptions, root?: AstNode): ValidationSyntaxResult { + + // media-query-list + if (!Array.isArray(atRule.tokens) || atRule.tokens.length == 0) { + + // @ts-ignore + return { + valid: ValidationLevel.Valid, + matches: [], + node: null, + syntax: null, + error: '', + tokens: [] + } as ValidationSyntaxResult; + } + + const queries: Token[] = atRule.tokens.slice(); + + consumeWhitespace(queries); + + if (queries.length == 0 || queries[0].typ != EnumToken.DashedIdenTokenType) { + + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@custom-media', + error: 'expecting dashed identifier', + tokens: [] + } + } + + queries.shift(); + + const result: ValidationSyntaxResult = validateAtRuleMediaQueryList(queries, atRule); + + if (result.valid == ValidationLevel.Drop) { + + atRule.tokens = []; + + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@custom-media', + error: '', + tokens: [] + } + } + + return result; +} \ No newline at end of file diff --git a/src/lib/validation/at-rules/else.ts b/src/lib/validation/at-rules/else.ts new file mode 100644 index 00000000..8c15e8fb --- /dev/null +++ b/src/lib/validation/at-rules/else.ts @@ -0,0 +1,4 @@ +import {validateAtRuleWhen} from "./when"; + + +export const validateAtRuleElse = validateAtRuleWhen; \ No newline at end of file diff --git a/src/lib/validation/at-rules/index.ts b/src/lib/validation/at-rules/index.ts index fc42de2a..2637115c 100644 --- a/src/lib/validation/at-rules/index.ts +++ b/src/lib/validation/at-rules/index.ts @@ -8,4 +8,7 @@ export * from './layer'; export * from './font-feature-values'; export * from './namespace'; export * from './document'; -export * from './keyframes'; \ No newline at end of file +export * from './keyframes'; +export * from './when'; +export * from './else'; +export * from './container'; \ No newline at end of file diff --git a/src/lib/validation/at-rules/media.ts b/src/lib/validation/at-rules/media.ts index ccf9771a..b49ddddf 100644 --- a/src/lib/validation/at-rules/media.ts +++ b/src/lib/validation/at-rules/media.ts @@ -1,4 +1,12 @@ -import type {AstAtRule, AstNode, Token, ValidationOptions} from "../../../@types"; +import { + AstAtRule, + AstNode, + FunctionToken, + MediaFeatureToken, + ParensToken, + Token, + ValidationOptions +} from "../../../@types"; import type {ValidationSyntaxResult} from "../../../@types/validation.d.ts"; import {EnumToken, ValidationLevel} from "../../ast"; import {consumeWhitespace, splitTokenList} from "../utils"; @@ -10,16 +18,34 @@ export function validateAtRuleMedia(atRule: AstAtRule, options: ValidationOption // @ts-ignore return { - valid: ValidationLevel.Drop, + valid: ValidationLevel.Valid, + matches: [], + node: null, + syntax: null, + error: '', + tokens: [] + } as ValidationSyntaxResult; + } + + let result: ValidationSyntaxResult | null = null; + + const slice: Token[] = atRule.tokens.slice(); + + consumeWhitespace(slice); + + if (slice.length == 0) { + + return { + valid: ValidationLevel.Valid, matches: [], node: atRule, syntax: '@media', - error: 'expected media query list', + error: '', tokens: [] - } as ValidationSyntaxResult; + } } - const result: ValidationSyntaxResult = validateAtRuleMediaQueryList(atRule.tokens, atRule); + result = validateAtRuleMediaQueryList(atRule.tokens, atRule); if (result.valid == ValidationLevel.Drop) { @@ -52,12 +78,25 @@ export function validateAtRuleMedia(atRule: AstAtRule, options: ValidationOption export function validateAtRuleMediaQueryList(tokenList: Token[], atRule: AstAtRule): ValidationSyntaxResult { - for (const tokens of splitTokenList(tokenList)) { + const split: Token[][] = splitTokenList(tokenList); + const matched: Token[][] = []; + let result: ValidationSyntaxResult | null = null; + let previousToken: Token | null; + let mediaFeatureType: Token | null; + + for (let i = 0; i < split.length; i++) { + + const tokens: Token[] = split[i].slice(); + const match: Token[] = []; + + result = null; + mediaFeatureType = null; + previousToken = null; if (tokens.length == 0) { // @ts-ignore - return { + result = { valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? atRule, @@ -65,25 +104,42 @@ export function validateAtRuleMediaQueryList(tokenList: Token[], atRule: AstAtRu error: 'unexpected token', tokens: [] } as ValidationSyntaxResult; + continue; } - let previousToken: Token | null = null; - while (tokens.length > 0) { - // media-condition - if (validateMediaCondition(tokens[0])) { + previousToken = tokens[0]; - previousToken = tokens[0]; - tokens.shift(); - } - // media-type - else if (validateMediaFeature(tokens[0])) { + // media-condition | media-type | custom-media + if (!(validateMediaCondition(tokens[0], atRule) || validateMediaFeature(tokens[0]) || validateCustomMediaCondition(tokens[0], atRule))) { + + if (tokens[0].typ == EnumToken.ParensTokenType) { + + result = validateAtRuleMediaQueryList(tokens[0].chi, atRule); + } + else { + + result = { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0] ?? atRule, + syntax: '@media', + error: 'expecting media feature or media condition', + tokens: [] + } + } + + if (result.valid == ValidationLevel.Drop) { - previousToken = tokens[0]; - tokens.shift(); + break; + } + + result = null; } + match.push(tokens.shift() as Token); + if (tokens.length == 0) { break; @@ -94,7 +150,7 @@ export function validateAtRuleMediaQueryList(tokenList: Token[], atRule: AstAtRu if (previousToken?.typ != EnumToken.ParensTokenType) { // @ts-ignore - return { + result = { valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? atRule, @@ -102,13 +158,15 @@ export function validateAtRuleMediaQueryList(tokenList: Token[], atRule: AstAtRu error: 'expected media query list', tokens: [] } + + break; } } - if (![EnumToken.MediaFeatureOrTokenType, EnumToken.MediaFeatureAndTokenType].includes(tokens[0].typ)) { + else if (![EnumToken.MediaFeatureOrTokenType, EnumToken.MediaFeatureAndTokenType].includes(tokens[0].typ)) { // @ts-ignore - return { + result = { valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? atRule, @@ -116,38 +174,100 @@ export function validateAtRuleMediaQueryList(tokenList: Token[], atRule: AstAtRu error: 'expected and/or', tokens: [] } + + break; } - if (tokens.length == 1) { + if (mediaFeatureType == null) { + + mediaFeatureType = tokens[0]; + } + + if (mediaFeatureType.typ != tokens[0].typ) { // @ts-ignore - return { + result = { valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? atRule, syntax: '@media', - error: 'expected media-condition', + error: 'mixing and/or not allowed at the same level', tokens: [] } + + break; } - tokens.shift(); + match.push({typ: EnumToken.WhitespaceTokenType}, tokens.shift() as Token); - if (!consumeWhitespace(tokens)) { + consumeWhitespace(tokens); + + if (tokens.length == 0) { // @ts-ignore - return { + result = { valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? atRule, syntax: '@media', - error: 'expected whitespace', + error: 'expected media-condition', tokens: [] } + + break; } + + match.push({typ: EnumToken.WhitespaceTokenType}); + } + + if (result == null && match.length > 0) { + + matched.push(match); } } + if (result != null) { + + return result; + } + + if (matched.length == 0) { + + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@media', + error: 'expected media query list', + tokens: [] + }; + } + + tokenList.length = 0; + + let hasAll: boolean = false; + + for (let i = 0; i < matched.length; i++) { + + if (tokenList.length > 0) { + + tokenList.push({typ: EnumToken.CommaTokenType}); + } + + if (matched[i].length == 1 && matched.length > 1 && matched[i][0].typ == EnumToken.MediaFeatureTokenType && (matched[i][0] as MediaFeatureToken).val == 'all') { + + hasAll = true; + continue; + } + + tokenList.push(...matched[i]); + } + + if (hasAll && tokenList.length == 0) { + + tokenList.push({typ: EnumToken.MediaFeatureTokenType, val: 'all'}); + } + // @ts-ignore return { valid: ValidationLevel.Valid, @@ -159,11 +279,11 @@ export function validateAtRuleMediaQueryList(tokenList: Token[], atRule: AstAtRu } } -function validateMediaCondition(token: Token): boolean { +function validateCustomMediaCondition(token: Token, atRule: AstAtRule): boolean { if (token.typ == EnumToken.MediaFeatureNotTokenType) { - return validateMediaCondition(token.val); + return validateMediaCondition(token.val, atRule); } if (token.typ != EnumToken.ParensTokenType) { @@ -178,6 +298,28 @@ function validateMediaCondition(token: Token): boolean { return false; } + return chi[0].typ == EnumToken.DashedIdenTokenType; +} + +export function validateMediaCondition(token: Token, atRule: AstAtRule): boolean { + + if (token.typ == EnumToken.MediaFeatureNotTokenType) { + + return validateMediaCondition(token.val, atRule); + } + + if (token.typ != EnumToken.ParensTokenType && !(['when', 'else'].includes(atRule.nam) && token.typ == EnumToken.FunctionTokenType && ['media', 'supports'].includes(token.val)) ) { + + return false; + } + + const chi: Token[] = (token as ParensToken | FunctionToken).chi.filter((t: Token): boolean => t.typ != EnumToken.CommentTokenType && t.typ != EnumToken.WhitespaceTokenType); + + if (chi.length != 1) { + + return false; + } + if (chi[0].typ == EnumToken.IdenTokenType) { return true; @@ -185,7 +327,7 @@ function validateMediaCondition(token: Token): boolean { if (chi[0].typ == EnumToken.MediaFeatureNotTokenType) { - return validateMediaCondition(chi[0].val); + return validateMediaCondition(chi[0].val, atRule); } if (chi[0].typ == EnumToken.MediaQueryConditionTokenType) { @@ -196,7 +338,7 @@ function validateMediaCondition(token: Token): boolean { return false; } -function validateMediaFeature(token: Token): boolean { +export function validateMediaFeature(token: Token): boolean { let val: Token = token; diff --git a/src/lib/validation/at-rules/supports.ts b/src/lib/validation/at-rules/supports.ts index b8c69be5..aab4a503 100644 --- a/src/lib/validation/at-rules/supports.ts +++ b/src/lib/validation/at-rules/supports.ts @@ -17,7 +17,7 @@ export function validateAtRuleSupports(atRule: AstAtRule, options: ValidationOpt valid: ValidationLevel.Drop, matches: [], node: atRule, - syntax: '@supports', + syntax: '@' + atRule.nam, error: 'expected supports query list', tokens: [] } as ValidationSyntaxResult; @@ -42,7 +42,7 @@ export function validateAtRuleSupports(atRule: AstAtRule, options: ValidationOpt valid: ValidationLevel.Drop, matches: [], node: atRule, - syntax: '@supports', + syntax: '@' + atRule.nam, error: 'expected at-rule body', tokens: [] } @@ -53,7 +53,7 @@ export function validateAtRuleSupports(atRule: AstAtRule, options: ValidationOpt valid: ValidationLevel.Valid, matches: [], node: atRule, - syntax: '@supports', + syntax: '@' + atRule.nam, error: '', tokens: [] } @@ -70,7 +70,7 @@ export function validateAtRuleSupportsConditions(atRule: AstAtRule, tokenList: T valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? atRule, - syntax: '@supports', + syntax: '@' + atRule.nam, error: 'unexpected token', tokens: [] } as ValidationSyntaxResult; @@ -116,7 +116,7 @@ export function validateAtRuleSupportsConditions(atRule: AstAtRule, tokenList: T valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? previousToken ?? atRule, - syntax: '@supports', + syntax: '@' + atRule.nam, error: 'expected whitespace', tokens: [] } @@ -130,7 +130,7 @@ export function validateAtRuleSupportsConditions(atRule: AstAtRule, tokenList: T valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? atRule, - syntax: '@supports', + syntax: '@' + atRule.nam, error: 'expected and/or', tokens: [] } @@ -143,7 +143,7 @@ export function validateAtRuleSupportsConditions(atRule: AstAtRule, tokenList: T valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? atRule, - syntax: '@supports', + syntax: '@' + atRule.nam, error: 'expected supports-condition', tokens: [] } @@ -158,7 +158,7 @@ export function validateAtRuleSupportsConditions(atRule: AstAtRule, tokenList: T valid: ValidationLevel.Drop, matches: [], node: tokens[0] ?? atRule, - syntax: '@supports', + syntax: '@' + atRule.nam, error: 'expected whitespace', tokens: [] } @@ -176,14 +176,14 @@ export function validateSupportCondition(atRule: AstAtRule, token: Token): Valid return validateSupportCondition(atRule, token.val); } - if (token.typ != EnumToken.ParensTokenType) { + if (token.typ != EnumToken.ParensTokenType && !(['when', 'else'].includes(atRule.nam) && token.typ == EnumToken.FunctionTokenType && ['supports', 'font-format', 'font-tech'].includes(token.val))) { // @ts-ignore return { valid: ValidationLevel.Drop, matches: [], node: token, - syntax: '@supports', + syntax: '@' + atRule.nam, error: 'expected supports condition-in-parens', tokens: [] }; @@ -202,7 +202,7 @@ export function validateSupportCondition(atRule: AstAtRule, token: Token): Valid valid: ValidationLevel.Valid, matches: [], node: null, - syntax: '@supports', + syntax: '@' + atRule.nam, error: '', tokens: [] }; diff --git a/src/lib/validation/at-rules/when.ts b/src/lib/validation/at-rules/when.ts new file mode 100644 index 00000000..79d427d8 --- /dev/null +++ b/src/lib/validation/at-rules/when.ts @@ -0,0 +1,229 @@ +import {AstAtRule, type AstNode, FunctionToken, Token, type ValidationOptions} from "../../../@types"; +import type {ValidationSyntaxResult} from "../../../@types/validation"; +import {EnumToken, ValidationLevel} from "../../ast"; +import {consumeWhitespace, splitTokenList} from "../utils"; +import {validateMediaCondition, validateMediaFeature} from "./media"; +import {validateSupportCondition} from "./supports"; + +export function validateAtRuleWhen(atRule: AstAtRule, options: ValidationOptions, root?: AstNode): ValidationSyntaxResult { + + const slice: Token[] = Array.isArray(atRule.tokens) ? atRule.tokens.slice() : []; + + consumeWhitespace(slice); + + if (slice.length == 0) { + + // @ts-ignore + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@when', + error: '', + tokens: [] + } as ValidationSyntaxResult; + } + + const result: ValidationSyntaxResult = validateAtRuleWhenQueryList(atRule.tokens as Token[], atRule); + + if (result.valid == ValidationLevel.Drop) { + + return result; + } + + if (!('chi' in atRule)) { + + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: atRule, + syntax: '@when', + error: 'expected at-rule body', + tokens: [] + } as ValidationSyntaxResult; + } + + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@when', + error: '', + tokens: result.tokens + } +} + +// media() = media( [ | | ] ) +// supports() = supports( ) +export function validateAtRuleWhenQueryList(tokenList: Token[], atRule: AstAtRule): ValidationSyntaxResult { + + const matched: Token[][] = []; + + let result: ValidationSyntaxResult | null = null; + + for (const split of splitTokenList(tokenList)) { + + const match: Token[] = []; + result = null; + + consumeWhitespace(split); + + if (split.length == 0) { + + continue; + } + + while (split.length > 0) { + + if (split[0].typ != EnumToken.FunctionTokenType || !['media', 'supports', 'font-tech', 'font-format'].includes((split[0] as FunctionToken).val)) { + + result = { + valid: ValidationLevel.Drop, + matches: [], + node: split[0] ?? atRule, + syntax: '@when', + error: 'unexpected token', + tokens: [] + } as ValidationSyntaxResult; + + break; + } + + const chi: Token[] = split[0].chi.slice() as Token[]; + + consumeWhitespace(chi); + + if (split[0].val == 'media') { + + // result = valida + if (chi.length != 1 || !(validateMediaFeature(chi[0]) || validateMediaCondition(split[0], atRule))) { + + result = { + valid: ValidationLevel.Drop, + matches: [], + node: split[0] ?? atRule, + syntax: 'media( [ | | ] )', + error: 'unexpected token', + tokens: [] + } + + break; + } + + } else if (['supports', 'font-tech', 'font-format'].includes(split[0].val) ) { + + // result = valida + if (!validateSupportCondition(atRule, split[0])) { + + result = { + valid: ValidationLevel.Drop, + matches: [], + node: split[0] ?? atRule, + syntax: 'media( [ | | ] )', + error: 'unexpected token', + tokens: [] + } + + break; + } + } + + if (match.length > 0) { + + match.push({typ: EnumToken.WhitespaceTokenType}); + } + + match.push(split.shift() as Token); + consumeWhitespace(split); + + if (split.length == 0) { + + break; + } + + if (![EnumToken.MediaFeatureAndTokenType, EnumToken.MediaFeatureOrTokenType].includes(split[0].typ)) { + + result = { + valid: ValidationLevel.Drop, + matches: [], + node: split[0] ?? atRule, + syntax: '@when', + error: 'expecting and/or media-condition', + tokens: [] + } as ValidationSyntaxResult; + + break; + } + + if (match.length > 0) { + + match.push({typ: EnumToken.WhitespaceTokenType}); + } + + match.push(split.shift() as Token); + + consumeWhitespace(split); + + if (split.length == 0) { + + result = { + valid: ValidationLevel.Drop, + matches: [], + node: split[0] ?? atRule, + syntax: '@when', + error: 'expecting media-condition', + tokens: [] + } as ValidationSyntaxResult; + + break; + } + } + + if (result == null && match.length > 0) { + + matched.push(match); + } + } + + if (result != null) { + + return result; + } + + if (matched.length == 0) { + + return { + valid: ValidationLevel.Drop, + matches: [], + // @ts-ignore + node: result?.node ?? atRule, + syntax: '@when', + error: 'invalid at-rule body', + tokens: [] + } + } + + tokenList.length = 0; + + for (const match of matched) { + + if (tokenList.length > 0) { + + tokenList.push({ + typ: EnumToken.CommaTokenType + }); + } + + tokenList.push(...match); + } + + return { + valid: ValidationLevel.Valid, + matches: [], + node: atRule, + syntax: '@when', + error: '', + tokens: tokenList + } +} \ No newline at end of file diff --git a/src/lib/validation/atrule.ts b/src/lib/validation/atrule.ts index a99347c8..a7544b27 100644 --- a/src/lib/validation/atrule.ts +++ b/src/lib/validation/atrule.ts @@ -4,8 +4,10 @@ import {EnumToken, ValidationLevel} from "../ast"; import {getParsedSyntax, getSyntaxConfig} from "./config"; import {ValidationSyntaxGroupEnum, ValidationToken} from "./parser"; import { + validateAtRuleContainer, validateAtRuleCounterStyle, validateAtRuleDocument, + validateAtRuleElse, validateAtRuleFontFeatureValues, validateAtRuleImport, validateAtRuleKeyframes, @@ -14,8 +16,10 @@ import { validateAtRuleNamespace, validateAtRulePage, validateAtRulePageMarginBox, - validateAtRuleSupports + validateAtRuleSupports, + validateAtRuleWhen } from "./at-rules"; +import {validateAtRuleCustomMedia} from "./at-rules/custom-media"; export function validateAtRule(atRule: AstAtRule, options: ValidationOptions, root?: AstNode): ValidationResult { @@ -82,11 +86,31 @@ export function validateAtRule(atRule: AstAtRule, options: ValidationOptions, ro return validateAtRuleNamespace(atRule, options, root); } + if (atRule.nam == 'when') { + + return validateAtRuleWhen(atRule, options, root); + } + + if (atRule.nam == 'else') { + + return validateAtRuleElse(atRule, options, root); + } + + if (atRule.nam == 'container') { + + return validateAtRuleContainer(atRule, options, root); + } + if (atRule.nam == 'document') { return validateAtRuleDocument(atRule, options, root); } + if (atRule.nam == 'custom-media') { + + return validateAtRuleCustomMedia(atRule, options, root); + } + if (['position-try', 'property', 'font-palette-values'].includes(atRule.nam)) { if (!('tokens' in atRule)) { @@ -142,7 +166,6 @@ export function validateAtRule(atRule: AstAtRule, options: ValidationOptions, ro } // scope - if (atRule.nam == 'page') { return validateAtRulePage(atRule, options, root); @@ -181,15 +204,15 @@ export function validateAtRule(atRule: AstAtRule, options: ValidationOptions, ro if (!(name in config.atRules)) { - // if (root?.typ == EnumToken.AtRuleNodeType) { - // - // const syntaxes: ValidationToken = (getParsedSyntax(ValidationSyntaxGroupEnum.AtRules, '@' + (root as AstAtRule).nam) as ValidationToken[])?.[0]; - // - // if ('chi' in syntaxes) { - // - // return validateSyntax(syntaxes.chi as ValidationToken[], [atRule], root, options); - // } - // } + if (options.lenient) { + + return { + valid: ValidationLevel.Lenient, + node: atRule, + syntax: null, + error: '' + } + } return { diff --git a/src/lib/validation/config.json b/src/lib/validation/config.json index 36a6b0f0..7191701b 100644 --- a/src/lib/validation/config.json +++ b/src/lib/validation/config.json @@ -996,9 +996,6 @@ "inline-size": { "syntax": "<'width'>" }, - "input-security": { - "syntax": "auto | none" - }, "inset": { "syntax": "<'top'>{1,4}" }, @@ -1557,6 +1554,9 @@ "shape-rendering": { "syntax": "auto | optimizeSpeed | crispEdges | geometricPrecision" }, + "speak-as": { + "syntax": "normal | spell-out || digits || [ literal-punctuation | no-punctuation ]" + }, "stop-color": { "syntax": "<'color'>" }, @@ -1669,7 +1669,7 @@ "syntax": "none | auto | " }, "text-spacing-trim": { - "syntax": "space-all | normal | space-first | trim-start | trim-both | trim-all | auto" + "syntax": "space-all | normal | space-first | trim-start" }, "text-transform": { "syntax": "none | capitalize | uppercase | lowercase | full-width | full-size-kana" @@ -1765,7 +1765,7 @@ "syntax": "normal | pre | nowrap | pre-wrap | pre-line | break-spaces | [ <'white-space-collapse'> || <'text-wrap'> ]" }, "white-space-collapse": { - "syntax": "collapse | discard | preserve | preserve-breaks | preserve-spaces | break-spaces" + "syntax": "collapse | preserve | preserve-breaks | preserve-spaces | break-spaces" }, "widows": { "syntax": "" @@ -1827,10 +1827,10 @@ "syntax": "attr( ? [, ]? )" }, "blur": { - "syntax": "blur( )" + "syntax": "blur( ? )" }, "brightness": { - "syntax": "brightness( )" + "syntax": "brightness( [ | ]? )" }, "calc": { "syntax": "calc( )" @@ -1854,7 +1854,7 @@ "syntax": "conic-gradient( [ from ]? [ at ]?, )" }, "contrast": { - "syntax": "contrast( [ ] )" + "syntax": "contrast( [ | ]? )" }, "cos": { "syntax": "cos( )" @@ -1869,7 +1869,7 @@ "syntax": "cross-fade( , ? )" }, "drop-shadow": { - "syntax": "drop-shadow( {2,3} ? )" + "syntax": "drop-shadow( [ ? && {2,3} ] )" }, "element": { "syntax": "element( )" @@ -1887,7 +1887,7 @@ "syntax": "fit-content( )" }, "grayscale": { - "syntax": "grayscale( )" + "syntax": "grayscale( [ | ]? )" }, "hsl": { "syntax": "hsl( [ / ]? ) | hsl( , , , ? )" @@ -1896,7 +1896,7 @@ "syntax": "hsla( [ / ]? ) | hsla( , , , ? )" }, "hue-rotate": { - "syntax": "hue-rotate( )" + "syntax": "hue-rotate( [ | ]? )" }, "hwb": { "syntax": "hwb( [ | none] [ | none] [ | none] [ / [ | none] ]? )" @@ -1914,7 +1914,7 @@ "syntax": "inset( {1,4} [ round <'border-radius'> ]? )" }, "invert": { - "syntax": "invert( )" + "syntax": "invert( [ | ]? )" }, "lab": { "syntax": "lab( [ | | none] [ | | none] [ | | none] [ / [ | none] ]? )" @@ -1962,7 +1962,7 @@ "syntax": "oklch( [ | | none] [ | | none] [ | none] [ / [ | none] ]? )" }, "opacity": { - "syntax": "opacity( [ ] )" + "syntax": "opacity( [ | ]? )" }, "paint": { "syntax": "paint( , ? )" @@ -1971,13 +1971,13 @@ "syntax": "palette-mix( , [ [normal | light | dark | | ] && ? ]#{2})" }, "path": { - "syntax": "path( [ , ]? )" + "syntax": "path( <'fill-rule'>? , )" }, "perspective": { "syntax": "perspective( [ | none ] )" }, "polygon": { - "syntax": "polygon( ? , [ ]# )" + "syntax": "polygon( <'fill-rule'>? , [ ]# )" }, "pow": { "syntax": "pow( , )" @@ -2025,7 +2025,7 @@ "syntax": "round( ?, , )" }, "saturate": { - "syntax": "saturate( )" + "syntax": "saturate( [ | ]? )" }, "scale": { "syntax": "scale( [ | ]#{1,2} )" @@ -2046,7 +2046,7 @@ "syntax": "scroll( [ || ]? )" }, "sepia": { - "syntax": "sepia( )" + "syntax": "sepia( [ | ]? )" }, "sign": { "syntax": "sign( )" @@ -2201,10 +2201,10 @@ "syntax": "normal | multiply | screen | overlay | darken | lighten | color-dodge | color-burn | hard-light | soft-light | difference | exclusion | hue | saturation | color | luminosity" }, "blur()": { - "syntax": "blur( )" + "syntax": "blur( ? )" }, "brightness()": { - "syntax": "brightness( )" + "syntax": "brightness( [ | ]? )" }, "calc()": { "syntax": "calc( )" @@ -2246,7 +2246,13 @@ "syntax": "" }, "color": { - "syntax": " | | | | | | | | | | | | | | currentcolor | transparent" + "syntax": " | currentColor | | | " + }, + "color-base": { + "syntax": " | | | | transparent" + }, + "color-function": { + "syntax": " | | | | | | | | | " }, "color()": { "syntax": "color( [from ]? [ / [ | none ] ]? )" @@ -2318,7 +2324,7 @@ "syntax": "[ contextual | no-contextual ]" }, "contrast()": { - "syntax": "contrast( [ ] )" + "syntax": "contrast( [ | ]? )" }, "coord-box": { "syntax": " | view-box" @@ -2360,7 +2366,7 @@ "syntax": "[ [ | ]+ ]#" }, "deprecated-system-color": { - "syntax": "ActiveBorder | ActiveCaption | AppWorkspace | Background | ButtonFace | ButtonHighlight | ButtonShadow | ButtonText | CaptionText | GrayText | Highlight | HighlightText | InactiveBorder | InactiveCaption | InactiveCaptionText | InfoBackground | InfoText | Menu | MenuText | Scrollbar | ThreeDDarkShadow | ThreeDFace | ThreeDHighlight | ThreeDLightShadow | ThreeDShadow | Window | WindowFrame | WindowText" + "syntax": "ActiveBorder | ActiveCaption | AppWorkspace | Background | ButtonHighlight | ButtonShadow | CaptionText | InactiveBorder | InactiveCaption | InactiveCaptionText | InfoBackground | InfoText | Menu | MenuText | Scrollbar | ThreeDDarkShadow | ThreeDFace | ThreeDHighlight | ThreeDLightShadow | ThreeDShadow | Window | WindowFrame | WindowText" }, "discretionary-lig-values": { "syntax": "[ discretionary-ligatures | no-discretionary-ligatures ]" @@ -2384,7 +2390,7 @@ "syntax": "block | inline | run-in" }, "drop-shadow()": { - "syntax": "drop-shadow( {2,3} ? )" + "syntax": "drop-shadow( [ ? && {2,3} ] )" }, "easing-function": { "syntax": "linear | | " @@ -2437,9 +2443,6 @@ "feature-value-name": { "syntax": "" }, - "fill-rule": { - "syntax": "nonzero | evenodd" - }, "filter-function": { "syntax": " | | | | | | | | | " }, @@ -2489,7 +2492,7 @@ "syntax": " | | | | | " }, "grayscale()": { - "syntax": "grayscale( )" + "syntax": "grayscale( [ | ]? )" }, "grid-line": { "syntax": "auto | | [ && ? ] | [ span && [ || ] ]" @@ -2510,7 +2513,7 @@ "syntax": "[ shorter | longer | increasing | decreasing ] hue" }, "hue-rotate()": { - "syntax": "hue-rotate( )" + "syntax": "hue-rotate( [ | ]? )" }, "hwb()": { "syntax": "hwb( [ | none] [ | none] [ | none] [ / [ | none] ]? )" @@ -2531,7 +2534,7 @@ "syntax": "image-set( # )" }, "image-set-option": { - "syntax": "[ | ]x [ || type() ]" + "syntax": "[ | ] [ || type() ]" }, "image-src": { "syntax": " | " @@ -2546,7 +2549,7 @@ "syntax": "inset( {1,4} [ round <'border-radius'> ]? )" }, "invert()": { - "syntax": "invert( )" + "syntax": "invert( [ | ]? )" }, "keyframe-block": { "syntax": "# {\n \n}" @@ -2690,7 +2693,7 @@ "syntax": "repeat( [ | auto-fill ], + )" }, "named-color": { - "syntax": "transparent | aliceblue | antiquewhite | aqua | aquamarine | azure | beige | bisque | black | blanchedalmond | blue | blueviolet | brown | burlywood | cadetblue | chartreuse | chocolate | coral | cornflowerblue | cornsilk | crimson | cyan | darkblue | darkcyan | darkgoldenrod | darkgray | darkgreen | darkgrey | darkkhaki | darkmagenta | darkolivegreen | darkorange | darkorchid | darkred | darksalmon | darkseagreen | darkslateblue | darkslategray | darkslategrey | darkturquoise | darkviolet | deeppink | deepskyblue | dimgray | dimgrey | dodgerblue | firebrick | floralwhite | forestgreen | fuchsia | gainsboro | ghostwhite | gold | goldenrod | gray | green | greenyellow | grey | honeydew | hotpink | indianred | indigo | ivory | khaki | lavender | lavenderblush | lawngreen | lemonchiffon | lightblue | lightcoral | lightcyan | lightgoldenrodyellow | lightgray | lightgreen | lightgrey | lightpink | lightsalmon | lightseagreen | lightskyblue | lightslategray | lightslategrey | lightsteelblue | lightyellow | lime | limegreen | linen | magenta | maroon | mediumaquamarine | mediumblue | mediumorchid | mediumpurple | mediumseagreen | mediumslateblue | mediumspringgreen | mediumturquoise | mediumvioletred | midnightblue | mintcream | mistyrose | moccasin | navajowhite | navy | oldlace | olive | olivedrab | orange | orangered | orchid | palegoldenrod | palegreen | paleturquoise | palevioletred | papayawhip | peachpuff | peru | pink | plum | powderblue | purple | rebeccapurple | red | rosybrown | royalblue | saddlebrown | salmon | sandybrown | seagreen | seashell | sienna | silver | skyblue | slateblue | slategray | slategrey | snow | springgreen | steelblue | tan | teal | thistle | tomato | turquoise | violet | wheat | white | whitesmoke | yellow | yellowgreen" + "syntax": "aliceblue | antiquewhite | aqua | aquamarine | azure | beige | bisque | black | blanchedalmond | blue | blueviolet | brown | burlywood | cadetblue | chartreuse | chocolate | coral | cornflowerblue | cornsilk | crimson | cyan | darkblue | darkcyan | darkgoldenrod | darkgray | darkgreen | darkgrey | darkkhaki | darkmagenta | darkolivegreen | darkorange | darkorchid | darkred | darksalmon | darkseagreen | darkslateblue | darkslategray | darkslategrey | darkturquoise | darkviolet | deeppink | deepskyblue | dimgray | dimgrey | dodgerblue | firebrick | floralwhite | forestgreen | fuchsia | gainsboro | ghostwhite | gold | goldenrod | gray | green | greenyellow | grey | honeydew | hotpink | indianred | indigo | ivory | khaki | lavender | lavenderblush | lawngreen | lemonchiffon | lightblue | lightcoral | lightcyan | lightgoldenrodyellow | lightgray | lightgreen | lightgrey | lightpink | lightsalmon | lightseagreen | lightskyblue | lightslategray | lightslategrey | lightsteelblue | lightyellow | lime | limegreen | linen | magenta | maroon | mediumaquamarine | mediumblue | mediumorchid | mediumpurple | mediumseagreen | mediumslateblue | mediumspringgreen | mediumturquoise | mediumvioletred | midnightblue | mintcream | mistyrose | moccasin | navajowhite | navy | oldlace | olive | olivedrab | orange | orangered | orchid | palegoldenrod | palegreen | paleturquoise | palevioletred | papayawhip | peachpuff | peru | pink | plum | powderblue | purple | rebeccapurple | red | rosybrown | royalblue | saddlebrown | salmon | sandybrown | seagreen | seashell | sienna | silver | skyblue | slateblue | slategray | slategrey | snow | springgreen | steelblue | tan | teal | thistle | tomato | turquoise | violet | wheat | white | whitesmoke | yellow | yellowgreen" }, "namespace-prefix": { "syntax": "" @@ -2723,7 +2726,7 @@ "syntax": "oklch( [ | | none] [ | | none] [ | none] [ / [ | none] ]? )" }, "opacity()": { - "syntax": "opacity( [ ] )" + "syntax": "opacity( [ | ]? )" }, "opacity-value": { "syntax": " | " @@ -2771,7 +2774,7 @@ "syntax": "palette-mix( , [ [normal | light | dark | | ] && ? ]#{2})" }, "path()": { - "syntax": "path( [ , ]? )" + "syntax": "path( <'fill-rule'>? , )" }, "perspective()": { "syntax": "perspective( [ | none ] )" @@ -2780,7 +2783,7 @@ "syntax": "hsl | hwb | lch | oklch" }, "polygon()": { - "syntax": "polygon( ? , [ ]# )" + "syntax": "polygon( <'fill-rule'>? , [ ]# )" }, "position": { "syntax": "[ [ left | center | right ] || [ top | center | bottom ] | [ left | center | right | ] [ top | center | bottom | ]? | [ [ left | right ] ] && [ [ top | bottom ] ] ]" @@ -2879,7 +2882,7 @@ "syntax": "nearest | up | down | to-zero" }, "saturate()": { - "syntax": "saturate( )" + "syntax": "saturate( [ | ]? )" }, "scale()": { "syntax": "scale( [ | ]#{1,2} )" @@ -2915,7 +2918,7 @@ "syntax": "center | start | end | self-start | self-end | flex-start | flex-end" }, "sepia()": { - "syntax": "sepia( )" + "syntax": "sepia( [ | ]? )" }, "shadow": { "syntax": "inset? && {2,4} && ?" @@ -3480,19 +3483,103 @@ "syntax": "@charset \"\";" }, "@counter-style": { - "syntax": "@counter-style {\n [ system: ; ] ||\n [ symbols: ; ] ||\n [ additive-symbols: ; ] ||\n [ negative: ; ] ||\n [ prefix: ; ] ||\n [ suffix: ; ] ||\n [ range: ; ] ||\n [ pad: ; ] ||\n [ speak-as: ; ] ||\n [ fallback: ; ]\n}" + "syntax": "@counter-style {\n [ system: ; ] ||\n [ symbols: ; ] ||\n [ additive-symbols: ; ] ||\n [ negative: ; ] ||\n [ prefix: ; ] ||\n [ suffix: ; ] ||\n [ range: ; ] ||\n [ pad: ; ] ||\n [ speak-as: ; ] ||\n [ fallback: ; ]\n}", + "descriptors": { + "additive-symbols": { + "syntax": "[ && ]#" + }, + "fallback": { + "syntax": "" + }, + "negative": { + "syntax": " ?" + }, + "pad": { + "syntax": " && " + }, + "prefix": { + "syntax": "" + }, + "range": { + "syntax": "[ [ | infinite ]{2} ]# | auto" + }, + "speak-as": { + "syntax": "auto | bullets | numbers | words | spell-out | " + }, + "suffix": { + "syntax": "" + }, + "symbols": { + "syntax": "+" + }, + "system": { + "syntax": "cyclic | numeric | alphabetic | symbolic | additive | [ fixed ? ] | [ extends ]" + } + } }, "@document": { "syntax": "@document [ | url-prefix() | domain() | media-document() | regexp() ]# {\n \n}" }, "@font-face": { - "syntax": "@font-face {\n [ font-family: ; ] ||\n [ src: ; ] ||\n [ unicode-range: ; ] ||\n [ font-variant: ; ] ||\n [ font-feature-settings: ; ] ||\n [ font-variation-settings: ; ] ||\n [ font-stretch: ; ] ||\n [ font-weight: ; ] ||\n [ font-style: ; ] ||\n [ size-adjust: ; ] ||\n [ ascent-override: ; ] ||\n [ descent-override: ; ] ||\n [ line-gap-override: ; ]\n}" + "syntax": "@font-face {\n [ font-family: ; ] ||\n [ src: ; ] ||\n [ unicode-range: ; ] ||\n [ font-variant: ; ] ||\n [ font-feature-settings: ; ] ||\n [ font-variation-settings: ; ] ||\n [ font-stretch: ; ] ||\n [ font-weight: ; ] ||\n [ font-style: ; ] ||\n [ size-adjust: ; ] ||\n [ ascent-override: ; ] ||\n [ descent-override: ; ] ||\n [ line-gap-override: ; ]\n}", + "descriptors": { + "ascent-override": { + "syntax": "normal | " + }, + "descent-override": { + "syntax": "normal | " + }, + "font-display": { + "syntax": "[ auto | block | swap | fallback | optional ]" + }, + "font-family": { + "syntax": "" + }, + "font-feature-settings": { + "syntax": "normal | #" + }, + "font-stretch": { + "syntax": "{1,2}" + }, + "font-style": { + "syntax": "normal | italic | oblique {0,2}" + }, + "font-variation-settings": { + "syntax": "normal | [ ]#" + }, + "font-weight": { + "syntax": "{1,2}" + }, + "line-gap-override": { + "syntax": "normal | " + }, + "size-adjust": { + "syntax": "" + }, + "src": { + "syntax": "[ [ format( # ) ]? | local( ) ]#" + }, + "unicode-range": { + "syntax": "#" + } + } }, "@font-feature-values": { "syntax": "@font-feature-values # {\n \n}" }, "@font-palette-values": { - "syntax": "@font-palette-values {\n \n}" + "syntax": "@font-palette-values {\n \n}", + "descriptors": { + "base-palette": { + "syntax": "light | dark | " + }, + "font-family": { + "syntax": "#" + }, + "override-colors": { + "syntax": "[ ]#" + } + } }, "@import": { "syntax": "@import [ | ]\n [ layer | layer() ]?\n [ supports( [ | ] ) ]?\n ? ;" @@ -3510,13 +3597,38 @@ "syntax": "@namespace ? [ | ];" }, "@page": { - "syntax": "@page {\n \n}" + "syntax": "@page {\n \n}", + "descriptors": { + "bleed": { + "syntax": "auto | " + }, + "marks": { + "syntax": "none | [ crop || cross ]" + }, + "page-orientation": { + "syntax": "upright | rotate-left | rotate-right " + }, + "size": { + "syntax": "{1,2} | auto | [ || [ portrait | landscape ] ]" + } + } }, "@position-try": { "syntax": "@position-try {\n \n}" }, "@property": { - "syntax": "@property {\n \n}" + "syntax": "@property {\n \n}", + "descriptors": { + "inherits": { + "syntax": "true | false" + }, + "initial-value": { + "syntax": "?" + }, + "syntax": { + "syntax": "" + } + } }, "@scope": { "syntax": "@scope [()]? [to ()]? {\n \n}" @@ -3528,7 +3640,15 @@ "syntax": "@supports {\n \n}" }, "@view-transition": { - "syntax": "@view-transition {\n \n}" + "syntax": "@view-transition {\n \n}", + "descriptors": { + "navigation": { + "syntax": "auto | none" + }, + "types": { + "syntax": "none | +" + } + } } } } \ No newline at end of file diff --git a/src/lib/validation/config.ts b/src/lib/validation/config.ts index e04ca9df..9100334b 100644 --- a/src/lib/validation/config.ts +++ b/src/lib/validation/config.ts @@ -1,6 +1,6 @@ import config from './config.json' with {type: 'json'}; -import type {ValidationConfiguration} from "../../@types/validation"; -import {parseSyntax, renderSyntax, ValidationSyntaxGroupEnum, ValidationToken, walkValidationToken} from "./parser"; +import type {ValidationConfiguration, ValidationSyntaxNode} from "../../@types/validation"; +import {parseSyntax, ValidationSyntaxGroupEnum, ValidationToken} from "./parser"; const parsedSyntaxes = new Map(); @@ -11,38 +11,46 @@ export function getSyntaxConfig(): ValidationConfiguration { return config as ValidationConfiguration; } -export function getParsedSyntax(group: ValidationSyntaxGroupEnum, key: string): null | ValidationToken[] { +export function getParsedSyntax(group: ValidationSyntaxGroupEnum, key: string | string[]): null | ValidationToken[] { - if (!(key in config[group])) { + // @ts-ignore + let obj = config[group] as Record; - const matches: RegExpMatchArray = key.match(/(@?)(-[a-zA-Z]+)-(.*?)$/) as RegExpMatchArray; + const keys: string[] = Array.isArray(key) ? key : [key]; - if (matches != null) { + for (let i = 0; i < keys.length; i++) { - key = matches[1] + matches[3]; - } + key = keys[i]; + + if (!(key in obj)) { + + if ((i == 0 && key.charAt(0) == '@') || key.charAt(0) == '-') { + + const matches: RegExpMatchArray = key.match(/^(@?)(-[a-zA-Z]+)-(.*?)$/) as RegExpMatchArray; + + if (matches != null) { + + key = matches[1] + matches[3]; + } + } - if (!(key in config[group])) { + if (!(key in obj)) { - return null; + return null; + } } + + // @ts-ignore + obj = obj[key]; } - const index: string = group + '.' + key; + const index: string = group + '.' + keys.join('.'); // @ts-ignore if (!parsedSyntaxes.has(index)) { // @ts-ignore - const syntax = parseSyntax(config[group][key].syntax); - - for (const node of syntax.chi as ValidationToken[]) { - - for (const {token, parent} of walkValidationToken(node)) { - - token.text = renderSyntax(token, parent); - } - } + const syntax: ValidationRootToken = parseSyntax(obj.syntax); // @ts-ignore parsedSyntaxes.set(index, syntax.chi); diff --git a/src/lib/validation/declaration.ts b/src/lib/validation/declaration.ts index e4c52b80..b7a88862 100644 --- a/src/lib/validation/declaration.ts +++ b/src/lib/validation/declaration.ts @@ -1,9 +1,9 @@ import type {AstAtRule, AstDeclaration, AstNode, ValidationOptions} from "../../@types"; -import type {ValidationConfiguration, ValidationResult, ValidationSyntaxResult} from "../../@types/validation"; +import type {ValidationConfiguration, ValidationResult} from "../../@types/validation"; import {EnumToken, ValidationLevel} from "../ast"; import {getParsedSyntax, getSyntaxConfig} from "./config"; +import {ParsedSyntax, ValidationSyntaxGroupEnum, ValidationToken} from "./parser"; import {validateSyntax} from "./syntax"; -import {ValidationAtRuleDefinitionToken, ValidationSyntaxGroupEnum, ValidationToken} from "./parser"; export function validateDeclaration(declaration: AstDeclaration, options: ValidationOptions, root?: AstNode): ValidationResult { @@ -15,7 +15,7 @@ export function validateDeclaration(declaration: AstDeclaration, options: Valida if (name[0] == '-') { - const match = /^-([a-z]+)-(.*)$/.exec(name); + const match: RegExpExecArray | null = /^-([a-z]+)-(.*)$/.exec(name); if (match != null) { @@ -26,12 +26,12 @@ export function validateDeclaration(declaration: AstDeclaration, options: Valida // root is at-rule - check if declaration allowed if (root?.typ == EnumToken.AtRuleNodeType) { - + // const syntax: ValidationToken = (getParsedSyntax(ValidationSyntaxGroupEnum.AtRules, '@' + (root as AstAtRule).nam) as ValidationToken[])?.[0]; if (syntax != null) { - if(!('chi' in syntax)) { + if (!('chi' in syntax)) { return { valid: ValidationLevel.Drop, @@ -52,18 +52,45 @@ export function validateDeclaration(declaration: AstDeclaration, options: Valida } } - if (!(name in config.declarations) && !(name in config.syntaxes)) { + // console.error({syntax}); - return { + const config: ValidationConfiguration = getSyntaxConfig(); - valid: ValidationLevel.Drop, - node: declaration, - syntax: null, - error: `unknown declaration "${declaration.nam}"` + // @ts-ignore + const obj = config[ValidationSyntaxGroupEnum.AtRules]['@' + (root as AstAtRule).nam] as ParsedSyntax; + + if ('descriptors' in obj) { + + const descriptors = obj.descriptors as Record; + + if (!(name in descriptors)) { + + return { + valid: ValidationLevel.Drop, + node: declaration, + syntax: `<${declaration.nam}>`, + error: `declaration <${declaration.nam}> is not allowed in <@${(root as AstAtRule).nam}>` + } } + + const syntax = getParsedSyntax(ValidationSyntaxGroupEnum.AtRules, ['@' + (root as AstAtRule).nam, 'descriptors', name]) as ValidationToken[]; + return validateSyntax(syntax, declaration.val, root, options); } - return validateSyntax((syntax as ValidationAtRuleDefinitionToken).chi as ValidationToken[], [declaration], root, options); + // console.error({name}); + + // if (!(name in config.declarations) && !(name in config.syntaxes)) { + // + // return { + // + // valid: ValidationLevel.Drop, + // node: declaration, + // syntax: null, + // error: `unknown declaration "${declaration.nam}"` + // } + // } + // + // return validateSyntax((syntax as ValidationAtRuleDefinitionToken).chi as ValidationToken[], [declaration], root, options); } } @@ -84,7 +111,7 @@ export function validateDeclaration(declaration: AstDeclaration, options: Valida valid: ValidationLevel.Drop, node: declaration, - syntax: null, + syntax: `<${declaration.nam}>`, error: `unknown declaration "${declaration.nam}"` } } diff --git a/src/lib/validation/parser/parse.ts b/src/lib/validation/parser/parse.ts index 97326f18..632cfd27 100644 --- a/src/lib/validation/parser/parse.ts +++ b/src/lib/validation/parser/parse.ts @@ -23,6 +23,7 @@ import { ValidationBlockToken, ValidationBracketToken, ValidationCharacterToken, + ValidationColumnArrayToken, ValidationColumnToken, ValidationCommaToken, ValidationDeclarationDefinitionToken, @@ -45,6 +46,22 @@ import { } from "./index"; import {isIdent, isPseudo} from '../../syntax'; +export enum WalkValidationTokenEnum { + + IgnoreChildren, + IgnoreNode, + IgnoreAll, +} + +export enum WalkValidationTokenKeyTypeEnum { + + Array = 'array', + Children = 'chi', + Left = 'l', + Right = 'r', + Prelude = 'prelude', +} + declare type ValidationContext = ValidationRootToken | ValidationBracketToken @@ -288,6 +305,88 @@ function* tokenize(syntax: string, position: Position = {ind: 0, lin: 1, col: 0} } } +function columnCallback(token: ValidationToken, parent: ValidationToken, key: WalkValidationTokenKeyTypeEnum) { + + if (key == WalkValidationTokenKeyTypeEnum.Prelude) { + + return WalkValidationTokenEnum.IgnoreAll; + } + + if (token.typ == ValidationTokenEnum.ColumnToken || token.typ == ValidationTokenEnum.Whitespace) { + + return WalkValidationTokenEnum.IgnoreNode + } + + return WalkValidationTokenEnum.IgnoreChildren +} + +function toColumnArray(ast: ValidationColumnToken, parent?: ValidationToken): ValidationToken { + + const result = new Map; + + // @ts-ignore + for (const {token} of walkValidationToken(ast, null, columnCallback)) { + + result.set(JSON.stringify(token), token); + } + + const node = { + typ: ValidationTokenEnum.ColumnArrayToken, + chi: [...result.values()] + } as ValidationColumnArrayToken; + + if (parent != null) { + + replaceNode(parent, ast, node); + } + + return node; +} + +function replaceNode(parent: ValidationToken, target: ValidationToken, node: ValidationToken) { + + if ('l' in parent && parent.l == target) { + + parent.l = node; + } + + if ('r' in parent && parent.r == target) { + + parent.r = node; + } + + if ('chi' in parent) { + + // @ts-ignore + for (let i = 0; i < parent.chi.length; i++) { + + // @ts-ignore + if (parent.chi[i] == target) { + + // @ts-ignore + parent.chi[i] = node; + break; + } + } + } +} + +function transform(context: ValidationContext): ValidationToken { + + for (const {token, parent} of walkValidationToken(context)) { + + switch (token.typ) { + + case ValidationTokenEnum.ColumnToken: + + toColumnArray(token as ValidationColumnToken, parent as ValidationToken); + break + } + } + + return context; +} + export function parseSyntax(syntax: string): ValidationRootToken { const root: ValidationRootToken = Object.defineProperty({ @@ -296,7 +395,7 @@ export function parseSyntax(syntax: string): ValidationRootToken { }, 'pos', {...objectProperties, value: {ind: 0, lin: 1, col: 0}}) as ValidationRootToken; // return minify(doParseSyntax(syntaxes, tokenize(syntaxes), root)) as ValidationRootToken; - return minify(doParseSyntax(syntax, tokenize(syntax), root)) as ValidationRootToken; + return minify(transform(doParseSyntax(syntax, tokenize(syntax), root))) as ValidationRootToken; } function matchParens(syntax: string, iterator: Iterator | Generator): ValidationToken[] { @@ -1420,6 +1519,13 @@ export function renderSyntax(token: ValidationToken, parent?: ValidationToken): return '{' + (token as ValidationBlockToken).chi.reduce((acc, t) => acc + renderSyntax(t), '') + '}'; + case ValidationTokenEnum.DeclarationDefinitionToken: + + return (token as ValidationDeclarationDefinitionToken).nam + ': ' + renderSyntax((token as ValidationDeclarationDefinitionToken).val); + + case ValidationTokenEnum.ColumnArrayToken: + + return (token as ValidationColumnArrayToken).chi.reduce((acc: string, curr: ValidationToken) => acc + (acc.trim().length > 0 ? '||' : '') + renderSyntax(curr), ''); default: @@ -1565,61 +1671,66 @@ function minify(ast: ValidationToken | ValidationToken[]): ValidationToken | Val return ast; } -export function* walkValidationToken(token: ValidationToken | ValidationToken[], parent?: ValidationToken, callback?: (token: ValidationToken, parent?: ValidationToken) => void): Generator<{ +export function* walkValidationToken(token: ValidationToken | ValidationToken[], parent?: ValidationToken | null, callback?: (token: ValidationToken, parent?: ValidationToken | null, key?: WalkValidationTokenKeyTypeEnum | null) => WalkValidationTokenEnum | null, key?: WalkValidationTokenKeyTypeEnum): Generator<{ token: ValidationToken, - parent?: ValidationToken + parent?: ValidationToken | null }> { if (Array.isArray(token)) { for (const child of token) { - yield* walkValidationToken(child, parent, callback); + yield* walkValidationToken(child, parent, callback, WalkValidationTokenKeyTypeEnum.Array); } return; } + let action: WalkValidationTokenEnum | null = null; if (callback != null) { - callback(token, parent); + // @ts-ignore + action = callback(token, parent, key); } - yield {token, parent}; + if (action != WalkValidationTokenEnum.IgnoreNode && action != WalkValidationTokenEnum.IgnoreAll) { + + yield {token, parent}; + } - if ('prelude' in token) { + if (action != WalkValidationTokenEnum.IgnoreChildren && action != WalkValidationTokenEnum.IgnoreAll && 'prelude' in token) { for (const child of (token as ValidationAtRuleDefinitionToken).prelude as ValidationToken[]) { - yield* walkValidationToken(child, token, callback); + yield* walkValidationToken(child, token, callback, WalkValidationTokenKeyTypeEnum.Prelude); } } - if ('chi' in token) { + if (action != WalkValidationTokenEnum.IgnoreChildren && 'chi' in token) { // @ts-ignore for (const child of (token as ValidationFunctionToken | ValidationBracketToken | ValidationPipeToken).chi) { - yield* walkValidationToken(child as ValidationToken, token, callback); + yield* walkValidationToken(child as ValidationToken, token, callback, WalkValidationTokenKeyTypeEnum.Children); } } - if ('l' in token) { + if (action != WalkValidationTokenEnum.IgnoreChildren && action != WalkValidationTokenEnum.IgnoreAll && 'l' in token) { // @ts-ignore for (const child of (token as ValidationColumnToken | ValidationAmpersandToken).l) { - yield* walkValidationToken(child as ValidationToken, token, callback); + yield* walkValidationToken(child as ValidationToken, token, callback, WalkValidationTokenKeyTypeEnum.Left); } } - if ('r' in token) { + if (action != WalkValidationTokenEnum.IgnoreChildren && action != WalkValidationTokenEnum.IgnoreAll && 'r' in token) { // @ts-ignore for (const child of (token as ValidationColumnToken | ValidationAmpersandToken).r) { - yield* walkValidationToken(child as ValidationToken, token, callback); + yield* walkValidationToken(child as ValidationToken, token, callback, WalkValidationTokenKeyTypeEnum.Right); } } } @@ -1629,9 +1740,10 @@ export function cleanup(ast: { [key: string]: any }) { return ast; } -interface ParsedSyntax { - syntax: string - ast?: ValidationToken[] +export interface ParsedSyntax { + syntax: string; + ast?: ValidationToken[]; + descriptors?: Record; } type ParsedSyntaxes = Record @@ -1748,6 +1860,13 @@ export async function parseAtRulesSyntax(): Promise { // ast: parseSyntax(values.syntaxes).chi, // mdn_url: values.mdn_url }; + + if ('descriptors' in values) { + + json[key].descriptors = Object.entries(values.descriptors as Record).reduce((acc, [k, v]) => Object.assign(acc, {[k]: {syntax: v.syntax} as ParsedSyntax}), {} as Record); + } } return cleanup(json) as ParsedSyntaxes; diff --git a/src/lib/validation/parser/types.ts b/src/lib/validation/parser/types.ts index d7477f65..f999f2d1 100644 --- a/src/lib/validation/parser/types.ts +++ b/src/lib/validation/parser/types.ts @@ -1,5 +1,5 @@ +export const specialValues: string[] = ['inherit', 'initial', 'unset', 'revert', 'revert-layer']; -export const specialValues: string[] = ['inherit', 'initial', 'unset', 'revert', 'revert-layer']; export enum ValidationTokenEnum { Root, @@ -41,7 +41,8 @@ export enum ValidationTokenEnum { DeclarationNameToken, DeclarationDefinitionToken, SemiColon, - Character + Character, + ColumnArrayToken } export const enum ValidationSyntaxGroupEnum { @@ -234,6 +235,12 @@ export interface ValidationColumnToken extends ValidationToken { r: ValidationToken[]; } +export interface ValidationColumnArrayToken extends ValidationToken { + + typ: ValidationTokenEnum.ColumnArrayToken, + chi: ValidationToken[]; +} + export interface ValidationDeclarationToken extends ValidationToken { typ: ValidationTokenEnum.DeclarationType, diff --git a/src/lib/validation/selector.ts b/src/lib/validation/selector.ts index cb6e5d38..742ca1e4 100644 --- a/src/lib/validation/selector.ts +++ b/src/lib/validation/selector.ts @@ -1,24 +1,23 @@ import type {AstAtRule, AstRule, AstRuleStyleSheet, Token, ValidationOptions} from "../../@types"; import {EnumToken} from "../ast"; -import type {ValidationResult} from "../../@types/validation"; import {validateKeyframeBlockList, validateRelativeSelectorList} from "./syntaxes"; +import type {ValidationResult} from "../../@types/validation"; import {validateSelectorList} from "./syntaxes/selector-list"; export function validateSelector(selector: Token[], options: ValidationOptions, root?: AstAtRule | AstRule | AstRuleStyleSheet): ValidationResult { if (root == null) { - return validateRelativeSelectorList(selector, root); + return validateSelectorList(selector, root, options); } // @ts-ignore if (root.typ == EnumToken.AtRuleNodeType && root.nam.match(/^(-[a-z]+-)?keyframes$/)) { - return validateKeyframeBlockList(selector, root); + return validateKeyframeBlockList(selector, root, options); } let isNested: number = root.typ == EnumToken.RuleNodeType ? 1 : 0; - let currentRoot = root.parent; while (currentRoot != null && isNested == 0) { @@ -30,7 +29,7 @@ export function validateSelector(selector: Token[], options: ValidationOptions, if (isNested > 0) { // @ts-ignore - return validateRelativeSelectorList(selector, root, {nestedSelector: true}); + return validateRelativeSelectorList(selector, root, {...(options ?? {}), nestedSelector: true}); } } @@ -40,5 +39,5 @@ export function validateSelector(selector: Token[], options: ValidationOptions, const nestedSelector: boolean = isNested > 0; // @ts-ignore - return nestedSelector ? validateRelativeSelectorList(selector, root, {nestedSelector}) : validateSelectorList(selector, root, {nestedSelector}); + return nestedSelector ? validateRelativeSelectorList(selector, root, {...(options ?? {}), nestedSelector}) : validateSelectorList(selector, root, {...(options ?? {}), nestedSelector}); } diff --git a/src/lib/validation/syntax.ts b/src/lib/validation/syntax.ts index ec43b793..d27e417f 100644 --- a/src/lib/validation/syntax.ts +++ b/src/lib/validation/syntax.ts @@ -1,4 +1,5 @@ import { + renderSyntax, specialValues, ValidationAmpersandToken, ValidationAtRule, @@ -6,6 +7,7 @@ import { ValidationAtRuleToken, ValidationBracketToken, ValidationColumnToken, + ValidationDeclarationDefinitionToken, ValidationDeclarationToken, ValidationFunctionDefinitionToken, ValidationFunctionToken, @@ -42,6 +44,7 @@ import {getParsedSyntax, getSyntaxConfig} from "./config"; import type {ValidationConfiguration, ValidationSyntaxResult} from "../../@types/validation"; import {isLength} from "../syntax"; import {validateSelector} from "./selector"; +import {validateImage} from "./syntaxes"; const config: ValidationConfiguration = getSyntaxConfig(); @@ -82,6 +85,12 @@ export interface ValidationContext { export function validateSyntax(syntaxes: ValidationToken[], tokens: Token[] | AstNode[], root?: AstNode, options?: ValidationOptions, context: ValidationContext = {level: 0}): ValidationSyntaxResult { + console.error(JSON.stringify({ + syntax: syntaxes.reduce((acc, curr) => acc + renderSyntax(curr), ''), + syntaxes, + tokens, + s: new Error('bar').stack + }, null, 1)); if (syntaxes == null) { // @ts-ignore @@ -751,7 +760,7 @@ export function validateSyntax(syntaxes: ValidationToken[], tokens: Token[] | As function isOptionalSyntax(syntaxes: ValidationToken[]): boolean { - return syntaxes.every(t => t.typ == ValidationTokenEnum.Whitespace || t.isOptional || t.isRepeatable || (t.typ == ValidationTokenEnum.PropertyType && isOptionalSyntax(getParsedSyntax(ValidationSyntaxGroupEnum.Syntaxes, (t as ValidationPropertyToken).val) ?? getParsedSyntax(ValidationSyntaxGroupEnum.Declarations, (t as ValidationPropertyToken).val) as ValidationToken[]))) + return syntaxes.length > 0 && syntaxes.every(t => t.typ == ValidationTokenEnum.Whitespace || t.isOptional || t.isRepeatable || (t.typ == ValidationTokenEnum.PropertyType && isOptionalSyntax(getParsedSyntax(ValidationSyntaxGroupEnum.Syntaxes, (t as ValidationPropertyToken).val) ?? getParsedSyntax(ValidationSyntaxGroupEnum.Declarations, (t as ValidationPropertyToken).val) as ValidationToken[] ?? []))); } function doValidateSyntax(syntax: ValidationToken, token: Token | AstNode, tokens: Token[] | AstNode[], root: AstNode | null, options: ValidationOptions, context: ValidationContext): ValidationSyntaxResult { @@ -1033,7 +1042,25 @@ function doValidateSyntax(syntax: ValidationToken, token: Token | AstNode, token // - if (['media-feature', 'mf-plain'].includes((syntax as ValidationPropertyToken).val)) { + if ('image' == (syntax as ValidationPropertyToken).val) { + + valid = token.typ == EnumToken.UrlFunctionTokenType || token.typ == EnumToken.ImageFunctionTokenType; + + if (!valid) { + + return { + valid: ValidationLevel.Drop, + matches: [], + node: token, + syntax, + error: 'unexpected ', + tokens + } + } + + result = validateImage(token as Token); + + } else if (['media-feature', 'mf-plain'].includes((syntax as ValidationPropertyToken).val)) { valid = token.typ == EnumToken.DeclarationNodeType; @@ -1089,6 +1116,24 @@ function doValidateSyntax(syntax: ValidationToken, token: Token | AstNode, token valid = [EnumToken.AtRuleNodeType, EnumToken.RuleNodeType].includes(token.typ); + if (!valid && token.typ == EnumToken.DeclarationNodeType && root?.typ == EnumToken.AtRuleNodeType && (root as AstAtRule).nam == 'media') { + + // allowed only if nested rule + let parent = root; + + while (parent != null) { + + if (parent.typ == EnumToken.RuleNodeType) { + + valid = true; + break; + } + + // @ts-ignore + parent = parent.parent; + } + } + // @ts-ignore result = { valid: valid ? ValidationLevel.Valid : ValidationLevel.Drop, @@ -1965,6 +2010,22 @@ function doValidateSyntax(syntax: ValidationToken, token: Token | AstNode, token break; + case ValidationTokenEnum.DeclarationDefinitionToken: + + if (token.typ != EnumToken.DeclarationNodeType || token.nam != (syntax as ValidationDeclarationDefinitionToken).nam) { + + return { + valid: ValidationLevel.Drop, + matches: [], + node: token, + syntax, + error: '', + tokens + } + } + + return validateSyntax([(syntax as ValidationDeclarationDefinitionToken).val], token.val, root as AstNode, options, context); + default: throw new Error('not implemented: ' + JSON.stringify({syntax, token, tokens}, null, 1)); diff --git a/src/lib/validation/syntaxes/complex-selector-list.ts b/src/lib/validation/syntaxes/complex-selector-list.ts index c694bde1..a4226548 100644 --- a/src/lib/validation/syntaxes/complex-selector-list.ts +++ b/src/lib/validation/syntaxes/complex-selector-list.ts @@ -1,8 +1,8 @@ import type {AstAtRule, AstRule, Token} from "../../../@types"; import type {ValidationSelectorOptions, ValidationSyntaxResult} from "../../../@types/validation.d.ts"; -import {EnumToken, ValidationLevel} from "../../ast"; +import {ValidationLevel} from "../../ast"; import {validateSelector} from "./selector"; -import {consumeWhitespace} from "../utils"; +import {consumeWhitespace, splitTokenList} from "../utils"; export function validateComplexSelectorList(tokens: Token[], root?: AstAtRule | AstRule, options?: ValidationSelectorOptions): ValidationSyntaxResult { @@ -24,25 +24,26 @@ export function validateComplexSelectorList(tokens: Token[], root?: AstAtRule | } } - let i: number = -1; - let j: number = 0; let result: ValidationSyntaxResult | null = null; - while (i + 1 < tokens.length) { + for (const t of splitTokenList(tokens)) { - if (tokens[++i].typ == EnumToken.CommaTokenType) { - - result = validateSelector(tokens.slice(j, i), root, options); + result = validateSelector(t, root, options); if (result.valid == ValidationLevel.Drop) { return result; } - - j = i + 1; - i = j; - } } - return validateSelector(i == j ? tokens.slice(i) : tokens.slice(j, i + 1), root, options); + // @ts-ignore + return result ?? { + valid: ValidationLevel.Drop, + matches: [], + // @ts-ignore + node: root, + syntax: null, + error: 'expecting complex selector list', + tokens + }; } \ No newline at end of file diff --git a/src/lib/validation/syntaxes/complex-selector.ts b/src/lib/validation/syntaxes/complex-selector.ts index 3b2c4ed8..a33f43d2 100644 --- a/src/lib/validation/syntaxes/complex-selector.ts +++ b/src/lib/validation/syntaxes/complex-selector.ts @@ -1,10 +1,12 @@ import type {AstAtRule, AstRule, Token} from "../../../@types"; import type {ValidationSelectorOptions, ValidationSyntaxResult} from "../../../@types/validation.d.ts"; -import {consumeWhitespace} from "../utils"; +import {consumeWhitespace, splitTokenList} from "../utils"; import {EnumToken, ValidationLevel} from "../../ast"; +import {validateCompoundSelector} from "./compound-selector"; export const combinatorsTokens: EnumToken[] = [EnumToken.ChildCombinatorTokenType, EnumToken.ColumnCombinatorTokenType, - EnumToken.DescendantCombinatorTokenType, EnumToken.NextSiblingCombinatorTokenType, EnumToken.SubsequentSiblingCombinatorTokenType]; + // EnumToken.DescendantCombinatorTokenType, + EnumToken.NextSiblingCombinatorTokenType, EnumToken.SubsequentSiblingCombinatorTokenType]; // [ ? ]* export function validateComplexSelector(tokens: Token[], root?: AstAtRule | AstRule, options?: ValidationSelectorOptions): ValidationSyntaxResult { @@ -26,313 +28,31 @@ export function validateComplexSelector(tokens: Token[], root?: AstAtRule | AstR } } - while (tokens.length > 0) { + // const config = getSyntaxConfig(); + // + // let match: number = 0; - if (combinatorsTokens.includes(tokens[0].typ)) { + let result: ValidationSyntaxResult | null = null; - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - // @ts-ignore - node: tokens[0], - syntax: null, - error: 'unexpected combinator', - tokens - } - } - - if (tokens[0].typ == EnumToken.NestingSelectorTokenType) { - - if (!options?.nestedSelector) { - - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - // @ts-ignore - node: tokens[0], - syntax: null, - error: 'nested selector not allowed', - tokens - } - } - - while (tokens.length > 0 && tokens[0].typ == EnumToken.NestingSelectorTokenType) { - - tokens.shift(); - consumeWhitespace(tokens); - } - - if (tokens.length == 0) { - - break; - } - } - - if (EnumToken.IdenTokenType == tokens[0].typ) { - - tokens.shift(); - consumeWhitespace(tokens); - - if (tokens.length == 0) { - - break; - } - } - - if (EnumToken.UniversalSelectorTokenType == tokens[0].typ) { - - tokens.shift(); - consumeWhitespace(tokens); - } - - while (tokens.length > 0) { - - if (tokens[0].typ == EnumToken.PseudoClassFuncTokenType) { - - if (tokens[0].val.startsWith(':-webkit-')) { - - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - // @ts-ignore - node: tokens[0], - syntax: null, - error: 'invalid pseudo-class', - tokens - } - } - } - - if ([ - EnumToken.ClassSelectorTokenType, - EnumToken.HashTokenType, - EnumToken.PseudoClassTokenType, - EnumToken.PseudoClassFuncTokenType].includes(tokens[0].typ)) { - - tokens.shift(); - consumeWhitespace(tokens); - continue - } - - if (tokens[0].typ == EnumToken.NestingSelectorTokenType) { - - if (!options?.nestedSelector) { - - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - // @ts-ignore - node: tokens[0], - syntax: null, - error: 'nested selector not allowed', - tokens - } - } - - tokens.shift(); - consumeWhitespace(tokens); - continue - } - - // validate namespace - if (tokens[0].typ == EnumToken.NameSpaceAttributeTokenType) { - - if (!((tokens[0].l == null || tokens[0].l.typ == EnumToken.IdenTokenType || (tokens[0].l.typ == EnumToken.LiteralTokenType && tokens[0].l.val == '*')) && - tokens[0].r.typ == EnumToken.IdenTokenType)) { - - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0], - syntax: null, - error: 'expecting wq-name', - tokens - } - } - - tokens.shift(); - consumeWhitespace(tokens); - continue; - } - // validate attribute - else if (tokens[0].typ == EnumToken.AttrTokenType) { + // const combinators: EnumToken[] = combinatorsTokens.filter((t: EnumToken) => t != EnumToken.DescendantCombinatorTokenType); - const children: Token[] = tokens[0].chi.slice() as Token[]; + for (const t of splitTokenList(tokens, combinatorsTokens)) { - consumeWhitespace(children); + result = validateCompoundSelector(t, root, options); - if (children.length == 0) { + if (result.valid == ValidationLevel.Drop) { - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0], - syntax: null, - error: 'invalid attribute selector', - tokens - } - } - - if (![ - EnumToken.IdenTokenType, - EnumToken.NameSpaceAttributeTokenType, - EnumToken.MatchExpressionTokenType].includes(children[0].typ)) { - - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0], - syntax: null, - error: 'invalid attribute selector', - tokens - } - } - - if (children[0].typ == EnumToken.MatchExpressionTokenType) { - - if (![EnumToken.IdenTokenType, - EnumToken.NameSpaceAttributeTokenType].includes(children[0].l.typ) || - ![ - EnumToken.EqualMatchTokenType, EnumToken.DashMatchTokenType, - EnumToken.StartMatchTokenType, EnumToken.ContainMatchTokenType, - EnumToken.EndMatchTokenType, EnumToken.IncludeMatchTokenType].includes(children[0].op.typ) || - ![EnumToken.StringTokenType, - EnumToken.IdenTokenType].includes(children[0].r.typ)) { - - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0], - syntax: null, - error: 'invalid attribute selector', - tokens - } - } - - if (children[0].attr != null && !['i', 's'].includes(children[0].attr)) { - - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0], - syntax: null, - error: 'invalid attribute selector', - tokens - } - } - } - - children.shift(); - consumeWhitespace(children); - - if (children.length > 0) { - - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: children[0], - syntax: null, - error: 'unexpected token', - tokens - } - } - - tokens.shift(); - consumeWhitespace(tokens); - continue; - } - - break; - } - - if (tokens.length == 0) { - - break - } - - // combinator - if (!combinatorsTokens.includes(tokens[0].typ)) { - - if (tokens[0].typ == EnumToken.NestingSelectorTokenType) { - - if (!options?.nestedSelector) { - - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - // @ts-ignore - node: tokens[0], - syntax: null, - error: 'nested selector not allowed', - tokens - } - } - - tokens.shift(); - consumeWhitespace(tokens); - continue - } - - - if (tokens.length > 0 && - [ - EnumToken.IdenTokenType, - EnumToken.AttrTokenType, - EnumToken.NameSpaceAttributeTokenType, - EnumToken.ClassSelectorTokenType, - EnumToken.HashTokenType, - EnumToken.PseudoClassTokenType, - EnumToken.PseudoClassFuncTokenType].includes(tokens[0].typ)) { - - continue - } - - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: tokens[0], - syntax: null, - error: 'expecting combinator or subclass-selector', - tokens - } - } - - const token = tokens.shift() as Token; - consumeWhitespace(tokens); - - if (tokens.length == 0) { - - // @ts-ignore - return { - valid: ValidationLevel.Drop, - matches: [], - node: token, - syntax: null, - error: 'expected compound-selector', - tokens - } + return result; } } // @ts-ignore - return { - valid: ValidationLevel.Valid, + return result ?? { + valid: ValidationLevel.Drop, matches: [], - node: null, + node: root, syntax: null, - error: '', + error: 'expecting compound-selector', tokens } } \ No newline at end of file diff --git a/src/lib/validation/syntaxes/compound-selector.ts b/src/lib/validation/syntaxes/compound-selector.ts new file mode 100644 index 00000000..008739e1 --- /dev/null +++ b/src/lib/validation/syntaxes/compound-selector.ts @@ -0,0 +1,268 @@ +import type {AstAtRule, AstRule, Token} from "../../../@types"; +import type { + ValidationConfiguration, + ValidationSelectorOptions, + ValidationSyntaxResult +} from "../../../@types/validation"; +import {EnumToken, ValidationLevel} from "../../ast"; +import {consumeWhitespace} from "../utils"; +import {mozExtensions, webkitExtensions} from "../../syntax"; +import {getSyntaxConfig} from "../config"; + +export function validateCompoundSelector(tokens: Token[], root?: AstAtRule | AstRule, options?: ValidationSelectorOptions): ValidationSyntaxResult { + + if (tokens.length == 0) { + + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + // @ts-ignore + node: root, + // @ts-ignore + syntax: null, + error: 'expected selector', + tokens + } + } + + tokens = tokens.slice(); + consumeWhitespace(tokens); + + const config: ValidationConfiguration = getSyntaxConfig(); + + let match: number = 0; + let length: number = tokens.length; + + while (tokens.length > 0) { + + while (tokens.length > 0 && tokens[0].typ == EnumToken.NestingSelectorTokenType) { + + if (!options?.nestedSelector) { + + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + // @ts-ignore + node: tokens[0], + syntax: null, + error: 'nested selector not allowed', + tokens + } + } + + match++; + tokens.shift(); + consumeWhitespace(tokens); + } + + // + while (tokens.length > 0 && + [ + EnumToken.IdenTokenType, + EnumToken.NameSpaceAttributeTokenType, + EnumToken.ClassSelectorTokenType, + EnumToken.HashTokenType, + EnumToken.UniversalSelectorTokenType + ].includes(tokens[0].typ)) { + + match++; + tokens.shift(); + consumeWhitespace(tokens); + } + + while (tokens.length > 0 && tokens[0].typ == EnumToken.PseudoClassFuncTokenType) { + + if ( + !mozExtensions.has(tokens[0].val + '()') && + !webkitExtensions.has(tokens[0].val + '()') && + !((tokens[0].val + '()') in config.selectors) + ) { + + if (!options?.lenient || /^(:?)-webkit-/.test(tokens[0].val)) { + + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + // @ts-ignore + node: tokens[0], + syntax: null, + error: 'unknown pseudo-class: ' + tokens[0].val + '()', + tokens + } + } + } + + match++; + tokens.shift(); + consumeWhitespace(tokens); + } + + while (tokens.length > 0 && tokens[0].typ == EnumToken.PseudoClassTokenType) { + + const isPseudoElement: boolean = tokens[0].val.startsWith('::'); + + if ( + // https://developer.mozilla.org/en-US/docs/Web/CSS/WebKit_Extensions#pseudo-elements + !(isPseudoElement && tokens[0].val.startsWith('::-webkit-')) && + !mozExtensions.has(tokens[0].val) && + !webkitExtensions.has(tokens[0].val) && + !(tokens[0].val in config.selectors) && + !(!isPseudoElement && + (':' + tokens[0].val) in config.selectors) + ) { + + if (!options?.lenient || /^(:?)-webkit-/.test(tokens[0].val)) { + + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + // @ts-ignore + node: tokens[0], + syntax: null, + error: 'unknown pseudo-class: ' + tokens[0].val, + tokens + } + } + } + + match++; + tokens.shift(); + consumeWhitespace(tokens); + } + + while (tokens.length > 0 && tokens[0].typ == EnumToken.AttrTokenType) { + + const children: Token[] = tokens[0].chi.slice() as Token[]; + + consumeWhitespace(children); + + if (children.length == 0) { + + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: null, + error: 'invalid attribute selector', + tokens + } + } + + if (![ + EnumToken.IdenTokenType, + EnumToken.NameSpaceAttributeTokenType, + EnumToken.MatchExpressionTokenType + ].includes(children[0].typ)) { + + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: null, + error: 'invalid attribute selector', + tokens + } + } + + if (children[0].typ == EnumToken.MatchExpressionTokenType) { + + if (children.length != 1) { + + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: null, + error: 'invalid ', + tokens + } + } + + if (![ + EnumToken.IdenTokenType, + EnumToken.NameSpaceAttributeTokenType + ].includes(children[0].l.typ) || + ![ + EnumToken.EqualMatchTokenType, EnumToken.DashMatchTokenType, + EnumToken.StartMatchTokenType, EnumToken.ContainMatchTokenType, + EnumToken.EndMatchTokenType, EnumToken.IncludeMatchTokenType].includes(children[0].op.typ) || + ![ + EnumToken.StringTokenType, + EnumToken.IdenTokenType + ].includes(children[0].r.typ)) { + + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: null, + error: 'invalid attribute selector', + tokens + } + } + + if (children[0].attr != null && !['i', 's'].includes(children[0].attr)) { + + // @ts-ignore + return { + valid: ValidationLevel.Drop, + matches: [], + node: tokens[0], + syntax: null, + error: 'invalid attribute selector', + tokens + } + } + } + + match++; + tokens.shift(); + consumeWhitespace(tokens); + } + + if (length == tokens.length) { + + return { + valid: ValidationLevel.Drop, + matches: [], + // @ts-ignore + node: tokens[0], + // @ts-ignore + syntax: null, + error: 'expected compound selector', + tokens + } + } + + length = tokens.length; + } + + return match == 0 ? { + valid: ValidationLevel.Drop, + matches: [], + // @ts-ignore + node: root, + // @ts-ignore + syntax: null, + error: 'expected compound selector', + tokens + } as ValidationSyntaxResult : + // @ts-ignore + { + valid: ValidationLevel.Valid, + matches: [] as Token[], + // @ts-ignore + node: root as Token, + // @ts-ignore + syntax: null, + error: null, + tokens + } as ValidationSyntaxResult +} diff --git a/src/lib/validation/syntaxes/image.ts b/src/lib/validation/syntaxes/image.ts new file mode 100644 index 00000000..ec8e0d95 --- /dev/null +++ b/src/lib/validation/syntaxes/image.ts @@ -0,0 +1,29 @@ +import {ValidationSyntaxResult} from "../../../@types/validation"; +import {type FunctionImageToken, Token} from "../../../@types"; +import {EnumToken, ValidationLevel} from "../../ast"; +import {validateSyntax} from "../syntax"; +import {getParsedSyntax} from "../config"; +import {ValidationSyntaxGroupEnum, ValidationToken} from "../parser"; +import {validateURL} from "./url"; + +export function validateImage(token: Token): ValidationSyntaxResult { + + if (token.typ == EnumToken.UrlFunctionTokenType) { + + return validateURL(token); + } + + if (token.typ == EnumToken.ImageFunctionTokenType) { + + return validateSyntax(getParsedSyntax(ValidationSyntaxGroupEnum.Syntaxes, (token as FunctionImageToken).val + '()') as ValidationToken[], (token as FunctionImageToken).chi); + } + + return { + valid: ValidationLevel.Drop, + matches: [], + node: token, + syntax: 'image()', + error: 'expected or ', + tokens: [] + } +} \ No newline at end of file diff --git a/src/lib/validation/syntaxes/index.ts b/src/lib/validation/syntaxes/index.ts index 7041ed52..8c157b29 100644 --- a/src/lib/validation/syntaxes/index.ts +++ b/src/lib/validation/syntaxes/index.ts @@ -6,4 +6,5 @@ export * from './relative-selector'; export * from './complex-selector-list'; export * from './selector'; export * from './keyframe-block-list'; -export * from './keyframe-selector'; \ No newline at end of file +export * from './keyframe-selector'; +export * from './image'; \ No newline at end of file diff --git a/src/lib/validation/syntaxes/keyframe-block-list.ts b/src/lib/validation/syntaxes/keyframe-block-list.ts index ee3f5a85..91e17e33 100644 --- a/src/lib/validation/syntaxes/keyframe-block-list.ts +++ b/src/lib/validation/syntaxes/keyframe-block-list.ts @@ -1,10 +1,10 @@ -import type {AstAtRule, Token} from "../../../@types"; +import type {AstAtRule, Token, ValidationOptions} from "../../../@types"; import type {ValidationSyntaxResult} from "../../../@types/validation.d.ts"; import {EnumToken, ValidationLevel} from "../../ast"; import {validateKeyframeSelector} from "./keyframe-selector"; -export function validateKeyframeBlockList(tokens: Token[], atRule: AstAtRule): ValidationSyntaxResult { +export function validateKeyframeBlockList(tokens: Token[], atRule: AstAtRule, options: ValidationOptions): ValidationSyntaxResult { let i: number = 0; let j: number = 0; @@ -14,7 +14,7 @@ export function validateKeyframeBlockList(tokens: Token[], atRule: AstAtRule): V if (tokens[++i].typ == EnumToken.CommaTokenType) { - result = validateKeyframeSelector(tokens.slice(j, i), atRule); + result = validateKeyframeSelector(tokens.slice(j, i), atRule, options); if (result.valid == ValidationLevel.Drop) { @@ -26,5 +26,5 @@ export function validateKeyframeBlockList(tokens: Token[], atRule: AstAtRule): V } } - return validateKeyframeSelector(i == j ? tokens.slice(i) : tokens.slice(j, i + 1), atRule); + return validateKeyframeSelector(i == j ? tokens.slice(i) : tokens.slice(j, i + 1), atRule, options); } \ No newline at end of file diff --git a/src/lib/validation/syntaxes/keyframe-selector.ts b/src/lib/validation/syntaxes/keyframe-selector.ts index 712e3061..dd1b3d07 100644 --- a/src/lib/validation/syntaxes/keyframe-selector.ts +++ b/src/lib/validation/syntaxes/keyframe-selector.ts @@ -1,9 +1,9 @@ -import type {AstNode, Token} from "../../../@types"; +import type {AstNode, Token, ValidationOptions} from "../../../@types"; import type {ValidationSyntaxResult} from "../../../@types/validation.d.ts"; import {consumeWhitespace} from "../utils"; import {EnumToken, ValidationLevel} from "../../ast"; -export function validateKeyframeSelector(tokens: Token[], atRule: AstNode): ValidationSyntaxResult { +export function validateKeyframeSelector(tokens: Token[], atRule: AstNode, options: ValidationOptions): ValidationSyntaxResult { consumeWhitespace(tokens); diff --git a/src/lib/validation/syntaxes/relative-selector-list.ts b/src/lib/validation/syntaxes/relative-selector-list.ts index 596cb4d2..e00122af 100644 --- a/src/lib/validation/syntaxes/relative-selector-list.ts +++ b/src/lib/validation/syntaxes/relative-selector-list.ts @@ -1,29 +1,64 @@ import type {AstAtRule, AstRule, Token} from "../../../@types"; import type {ValidationSelectorOptions, ValidationSyntaxResult} from "../../../@types/validation.d.ts"; -import {EnumToken, ValidationLevel} from "../../ast"; +import {ValidationLevel} from "../../ast"; import {validateRelativeSelector} from "./relative-selector"; +import {consumeWhitespace, splitTokenList} from "../utils"; export function validateRelativeSelectorList(tokens: Token[], root?: AstAtRule | AstRule, options?: ValidationSelectorOptions): ValidationSyntaxResult { - let i: number = 0; + let i: number = -1; let j: number = 0; let result: ValidationSyntaxResult | null = null; - while (i + 1 < tokens.length) { + tokens = tokens.slice(); - if (tokens[++i].typ == EnumToken.CommaTokenType) { + consumeWhitespace(tokens); - result = validateRelativeSelector(tokens.slice(j, i), root, options); + if (tokens.length == 0) { - if (result.valid == ValidationLevel.Drop) { + return { + valid: ValidationLevel.Drop, + matches: [], + // @ts-ignore + node: root, + // @ts-ignore + syntax: null, + error: 'expecting relative selector list', + tokens + } + } + + for (const t of splitTokenList(tokens)) { + + if (t.length == 0) { - return result; + return { + valid: ValidationLevel.Drop, + matches: [], + // @ts-ignore + node: root, + // @ts-ignore + syntax: null, + error: 'unexpected comma', + tokens } + } + + const result: ValidationSyntaxResult = validateRelativeSelector(t, root, options); - j = i + 1; - i = j; + if (result.valid == ValidationLevel.Drop) { + return result; } } - return validateRelativeSelector(i == j ? tokens.slice(i) : tokens.slice(j, i + 1), root, options); + return { + valid: ValidationLevel.Valid, + matches: [], + // @ts-ignore + node: root, + // @ts-ignore + syntax: null, + error: '', + tokens + } } \ No newline at end of file diff --git a/src/lib/validation/utils/list.ts b/src/lib/validation/utils/list.ts index 9885c875..578f6faa 100644 --- a/src/lib/validation/utils/list.ts +++ b/src/lib/validation/utils/list.ts @@ -1,16 +1,16 @@ import {EnumToken} from "../../ast"; import type {Token} from "../../../@types"; -export function splitTokenList(tokenList: Token[]): Token[][] { +export function splitTokenList(tokenList: Token[], split: EnumToken[] = [EnumToken.CommaTokenType]): Token[][] { - return tokenList.reduce( (acc: Token[][], curr: Token): Token[][] => { + return tokenList.reduce((acc: Token[][], curr: Token): Token[][] => { if (curr.typ == EnumToken.CommentTokenType) { return acc; } - if (curr.typ == EnumToken.CommaTokenType) { + if (split.includes(curr.typ)) { acc.push([]); } else { diff --git a/test/inspect.js b/test/inspect.js index 1164cb6f..181eafe6 100644 --- a/test/inspect.js +++ b/test/inspect.js @@ -7,4 +7,4 @@ const css = readFileSync(dirname(new URL(import.meta.url).pathname) + '/files/cs const {code, stats} = await transform(css); console.log(code); -console.log({stats}); \ No newline at end of file +console.error({stats}); \ No newline at end of file diff --git a/test/specs/code/at-rules.js b/test/specs/code/at-rules.js new file mode 100644 index 00000000..65dd1e70 --- /dev/null +++ b/test/specs/code/at-rules.js @@ -0,0 +1,371 @@ +export function run(describe, expect, transform, parse, render, dirname) { + + describe('media queries level 5', function () { + it('empty query #1', function () { + return transform(` +@media { + + p .a { color: red; } + p .b { color: red; } + } +} + +`).then((result) => expect(result.code).equals(`p .a,p .b{color:red}`)); + }); + + it('error handling #2', function () { + return transform(` + @media &test, all, (example, all,), speech { + + p .a { color: red; } + p .b { color: red; } + } + } + +`).then((result) => expect(result.code).equals(`@media speech{p .a,p .b{color:red}}`)); + }); + + it('error handling #3', function () { + return transform(` + @media (hover) and (width > 1024px) or (--modern), (color) { + .a { + color: green; } + + } +`).then((result) => expect(result.code).equals(`@media (color){.a{color:green}}`)); + }); + + it('error handling #4', function () { + return transform(` + @media (hover) and ((width > 1024px) and (--modern) and (color)), (color) { + .a { + color: green; } + + } +`).then((result) => expect(result.code).equals(`@media (hover) and ((width>1024px) and (--modern) and (color)),(color){.a{color:green}}`)); + }); + + it('error handling #5', function () { + return transform(` + @media (hover) and ((width > 1024px) and (--modern) or (color)), (color) { + .a { + color: green; } + + } +`).then((result) => expect(result.code).equals(`@media (color){.a{color:green}}`)); + }); + + it('error handling #6', function () { + return transform(` +@when media(width >= 400px) and media(pointer: fine) and supports(display: flex) { + +.a { + color: green; } +} +@else { + +.a { + color: red; } +} +@else supports(caret-color: pink) and supports(background: double-rainbow()){ + +.a { + color: green; } +} +`, {beautify: true}).then((result) => expect(result.code).equals(`@when media(width>=400px) and media(pointer:fine) and supports(display:flex) { + .a { + color: green + } +} +@else { + .a { + color: red + } +}`)); + }); + + it('support font-tech, font-format #7', function () { + return transform(` + +@when font-tech(color-COLRv1) and font-tech(variations) { + @font-face { font-family: icons; src: url(icons-gradient-var.woff2); } +} +@else font-tech(color-SVG) { + @font-face { font-family: icons; src: url(icons-gradient.woff2); } +} +@else font-tech(color-COLRv0) { + @font-face { font-family: icons; src: url(icons-flat.woff2); } +} +@else { + @font-face { font-family: icons; src: url(icons-fallback.woff2); } +} + +`, {beautify: true}).then((result) => expect(result.code).equals(`@when font-tech(color-COLRv1) and font-tech(variations) { + @font-face { + font-family: icons; + src: url(icons-gradient-var.woff2) + } +} +@else font-tech(color-SVG) { + @font-face { + font-family: icons; + src: url(icons-gradient.woff2) + } +} +@else font-tech(color-COLRv0) { + @font-face { + font-family: icons; + src: url(icons-flat.woff2) + } +} +@else { + @font-face { + font-family: icons; + src: url(icons-fallback.woff2) + } +}`)); + }); + + it('font-face #8', function () { + + return transform(` + +@font-face { font-family: icons; src: url(icons-fallback.woff2); +@supports font-tech(color-COLRv1) { + @font-face { font-family: icons; src: url(icons-gradient-var.woff2); } +} + +`, {beautify: true}).then((result) => expect(result.code).equals(`@font-face { + font-family: icons; + src: url(icons-fallback.woff2); + @supports font-tech(color-COLRv1) { + @font-face { + font-family: icons; + src: url(icons-gradient-var.woff2) + } + } +}`)); + }); + + it('container #9', function () { + + return transform(` + +/* condition list */ +@container card scroll-state(stuck: top) and + style(--themeBackground), + not style(background-color: red), + style(color: green) and style(background-color: transparent), + style(--themeColor: blue) or style(--themeColor: purple){ + h2 { + font-size: 1.5em; + } +} + +`, {beautify: true}).then((result) => expect(result.code).equals(`@container card scroll-state(stuck:top) and style(--themeBackground),not style(background-color:red),style(color:green) and style(background-color:transparent),style(--themeColor:blue) or style(--themeColor:purple) { + h2 { + font-size: 1.5em + } +}`)); + }); + + it('container #10', function () { + + return transform(` + +/* condition list */ +@container { + h2 { + font-size: 1.5em; + } +} + +`, {beautify: true}).then((result) => expect(result.code).equals(``)); + }); + + it('container #11', function () { + + return transform(` + +/* condition list */ +@container card { + h2 { + font-size: 1.5em; + } +} + +`, {beautify: true}).then((result) => expect(result.code).equals(`@container card { + h2 { + font-size: 1.5em + } +}`)); + }); + + it('container #12', function () { + + return transform(` + +/* condition list */ +@container card card{ + h2 { + font-size: 1.5em; + } +} +@container card style() { + h2 { + font-size: 1.5em; + } +} +@container card (()) { + h2 { + font-size: 1.5em; + } +} + +@container card ((--themeBackground) and (--themeColor),) { + h2 { + font-size: 1.5em; + } +} + +@container card ((--themeBackground) not (--themeColor)) { + h2 { + font-size: 1.5em; + } +} + +`, {beautify: true}).then((result) => expect(result.code).equals(``)); + }); + + it('container #13', function () { + + return transform(` + +/* condition list */ +@container card ; + +`, {beautify: true}).then((result) => expect(result.code).equals(``)); + }); + + it('custom-media #14', function () { + + return transform(` + /* --modern targets modern devices that support color or hover */ +@custom-media --modern (color), (hover); + +@media (--modern) and (width > 1024px) { + .a { color: green; } +} + +`).then((result) => expect(result.code).equals(`@custom-media --modern (color),(hover);@media (--modern) and (width>1024px){.a{color:green}}`)); + }); + + it('when-else #15', function () { + + return transform(` +@when media(width >= 400px) and media(pointer: fine) and supports(display: flex) { + +.a { + color: green; } +} +@else supports(caret-color: pink) and supports(background: double-rainbow()) { + +.a { + color: green; } +} +@else { + +.a { + color: green; } +} +`, {beautify: true}).then((result) => expect(result.code).equals(`@when media(width>=400px) and media(pointer:fine) and supports(display:flex) { + .a { + color: green + } +} +@else supports(caret-color:pink) and supports(background:double-rainbow()) { + .a { + color: green + } +} +@else { + .a { + color: green + } +}`)); + }); + + it('counter-syle #16', function () { + + return transform(` + +/* condition list */@counter-style thumbs { + system: cyclic; + symbols: "\\1F44D"; + suffix: " "; +} + +`, {beautify: true}).then((result) => expect(result.code).equals(`@counter-style thumbs { + system: cyclic; + symbols: "👍"; + suffix: " " +}`)); + }); + + it('counter-style #17', function () { + + return transform(` + +/* condition list */ +@counter-style { + system: cyclic; + symbols: "\\1F44D"; + suffix: " "; +} + +@counter-style thumbs; +@counter-style thumbs thumbs; +@counter-style /* thumbs thumbs */; +@counter-style var(); + +`, {beautify: true}).then((result) => expect(result.code).equals(``)); + }); + + it('at-rule #18', function () { + + return transform(`@charset "UTF-8" +`, {beautify: true, removeCharset: false}).then((result) => expect(result.code).equals(``)); + }); + + it('at-rule #19', function () { + + return transform(`@charset "UTF-8" +`, {beautify: true, removeCharset: false}).then((result) => expect(result.code).equals(`@charset "UTF-8";`)); + }); + + it('at-rule #20', function () { + + return transform(`@charset 'UTF-8' +`, {beautify: true, removeCharset: false}).then((result) => expect(result.code).equals(``)); + }); + + it('at-rule #21', function () { + + return transform(`@charset /* erw */ 'UTF-8'; +`, {beautify: true, removeCharset: false}).then((result) => expect(result.code).equals(``)); + }); + + it('at-rule #22', function () { + + return transform(`@charset /* erw */ "UTF-8"; +`, {beautify: true, removeCharset: false}).then((result) => expect(result.code).equals(``)); + }); + + it('at-rule #23', function () { + + return transform(`@charset /* erw */"UTF-8"; +`, {beautify: true, removeCharset: false}).then((result) => expect(result.code).equals(`@charset "UTF-8";`)); + }); + }); + +} diff --git a/test/specs/code/block.js b/test/specs/code/block.js index cd59a239..c26cc1a1 100644 --- a/test/specs/code/block.js +++ b/test/specs/code/block.js @@ -708,6 +708,18 @@ content: '\\21 now\\21'; .foo-bar,div#flavor { width: 12px; height: 25% +}`)); + }); + + it('compound selector #34', function () { + const file = ` + +::selection { + color: red; +} +`; + return parse(file, {inlineCssVariables: true}).then(result => expect(render(result.ast, {minify: false}).code).equals(`::selection { + color: red }`)); }); } diff --git a/test/specs/code/color.js b/test/specs/code/color.js index a291a406..ac8a7fcc 100644 --- a/test/specs/code/color.js +++ b/test/specs/code/color.js @@ -222,7 +222,7 @@ color: hsl(from green calc(h * 2) s l / calc(alpha / 2)) }`)); }); - it('relative color calc( and var() #25', function () { + it('relative color calc() and var() #25', function () { return parse(` :root { --color: green; @@ -242,7 +242,7 @@ color: hsl(from green calc(h * 2) s l / calc(alpha / 2)) }`)); }); - it('relative color calc( and var() #26', function () { + it('relative color calc() and var() #26', function () { return parse(` :root { --color: 255 0 0; diff --git a/test/specs/code/expand.js b/test/specs/code/expand.js index 3938a5ea..7018092a 100644 --- a/test/specs/code/expand.js +++ b/test/specs/code/expand.js @@ -247,6 +247,28 @@ html { :scope>header { border-block-end: 1px solid #fff } +}`)); + }); + + it('expand rule #11', function () { + + return transform(` +.parent { + color: blue; + + @scope (& > .scope) to (& .limit) { + & .content { + color: red; + } + } +} +`, {beautify: true, expandNestingRules: true}).then((result) => expect(result.code).equals(`.parent { + color: blue +} +@scope (.parent >.scope) to (.parent .limit) { + .parent .content { + color: red + } }`)); }); }); diff --git a/test/specs/code/index.js b/test/specs/code/index.js index eda15fd0..f4f87cd5 100644 --- a/test/specs/code/index.js +++ b/test/specs/code/index.js @@ -16,4 +16,6 @@ export * as sourceMap from './sourcemaps.js'; export * as vars from './vars.js'; export * as visitors from './visitors.js'; export * as walk from './walk.js'; -export * as validation from './validation.js'; \ No newline at end of file +export * as validation from './validation.js'; +export * as atRules from './at-rules.js'; +export * as lenient from './lenient.js' \ No newline at end of file diff --git a/test/specs/code/lenient.js b/test/specs/code/lenient.js new file mode 100644 index 00000000..750b3b7f --- /dev/null +++ b/test/specs/code/lenient.js @@ -0,0 +1,50 @@ +export function run(describe, expect, transform, parse, render, dirname, readFile) { + + describe('Lenient validation', function () { + it('lenient at-rules and pseudo classes #1', function () { + const css = ` + +@unknown { + + height: 100px; + width: 100px; +} + +@unknown :unknown { + + height: 100px; + width: 100px; +} + + :unknown { + + height: 100px; + width: 100px; +} + + +@media screen { + + .foo:-webkit-any-link():not(:hover) { + height: calc(100px * 2/ 15); + } +} +`; + return transform(css, { + beautify: true + }).then((result) => expect(result.code).equals(`@unknown { + height: 100px; + width: 100px +} +@unknown :unknown { + height: 100px; + width: 100px +} +:unknown { + height: 100px; + width: 100px +}`)); + }); + }); + +} \ No newline at end of file diff --git a/test/specs/code/prefix.js b/test/specs/code/prefix.js index 62059ba3..f41e188e 100644 --- a/test/specs/code/prefix.js +++ b/test/specs/code/prefix.js @@ -41,18 +41,17 @@ export function run(describe, expect, transform, parse, render, dirname, readFil @media screen { - .foo:-webkit-autofiller:not(:hover) { + .foo:-webkit-autofill:not(:hover) { height: calc(100px * 2/ 15); } } `, {removePrefix: true}).then(result => expect(render(result.ast, {minify: false}).code).equals(`@media screen { - .foo:-webkit-autofiller:not(:hover) { + .foo:autofill:not(:hover) { height: calc(40px/3) } }`)); }); - it('selector invalid prefix #4', function () { return transform(` @@ -65,5 +64,32 @@ export function run(describe, expect, transform, parse, render, dirname, readFil `, {removePrefix: true}).then(result => expect(render(result.ast, {minify: false}).code).equals(``)); }); + it('prefixed properties #4', function () { + return transform(` + +@media screen { + +:root { + + --color: red; + } + .foo:-webkit-any-link { + height: calc(100px * 2/ 15); + -webkit-appearance: none;; + -moz-window-shadow: menu; + } +} +`, {removePrefix: true}).then(result => expect(render(result.ast, {minify: false}).code).equals(`@media screen { + :root { + --color: red + } + .foo:-webkit-any-link { + height: calc(40px/3); + appearance: none; + -moz-window-shadow: menu + } +}`)); + }); + }); } \ No newline at end of file