Add components

This commit is contained in:
2025-09-29 11:24:36 +02:00
parent 6d1050c6cb
commit 620d4144b5
3748 changed files with 902194 additions and 0 deletions

22
slider/node_modules/@stylistic/eslint-plugin/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,22 @@
MIT License
Copyright OpenJS Foundation and other contributors, <www.openjsf.org>
Copyright (c) 2023-PRESENT ESLint Stylistic contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,551 @@
import { createAllConfigs, warnDeprecation } from "./utils.js";
import { array_bracket_newline_default } from "./rules/array-bracket-newline.js";
import { array_bracket_spacing_default } from "./rules/array-bracket-spacing.js";
import { array_element_newline_default } from "./rules/array-element-newline.js";
import { arrow_parens_default } from "./rules/arrow-parens.js";
import { arrow_spacing_default } from "./rules/arrow-spacing.js";
import { block_spacing_default } from "./rules/block-spacing.js";
import { brace_style_default } from "./rules/brace-style.js";
import { comma_dangle_default } from "./rules/comma-dangle.js";
import { comma_spacing_default } from "./rules/comma-spacing.js";
import { comma_style_default } from "./rules/comma-style.js";
import { computed_property_spacing_default } from "./rules/computed-property-spacing.js";
import { curly_newline_default } from "./rules/curly-newline.js";
import { dot_location_default } from "./rules/dot-location.js";
import { eol_last_default } from "./rules/eol-last.js";
import { function_call_argument_newline_default } from "./rules/function-call-argument-newline.js";
import { function_call_spacing_default } from "./rules/function-call-spacing.js";
import { function_paren_newline_default } from "./rules/function-paren-newline.js";
import { generator_star_spacing_default } from "./rules/generator-star-spacing.js";
import { implicit_arrow_linebreak_default } from "./rules/implicit-arrow-linebreak.js";
import { indent_binary_ops_default } from "./rules/indent-binary-ops.js";
import { indent_default } from "./rules/indent.js";
import { jsx_child_element_spacing_default } from "./rules/jsx-child-element-spacing.js";
import { jsx_closing_bracket_location_default } from "./rules/jsx-closing-bracket-location.js";
import { jsx_closing_tag_location_default } from "./rules/jsx-closing-tag-location.js";
import { jsx_curly_brace_presence_default } from "./rules/jsx-curly-brace-presence.js";
import { jsx_curly_newline_default } from "./rules/jsx-curly-newline.js";
import { jsx_curly_spacing_default } from "./rules/jsx-curly-spacing.js";
import { jsx_equals_spacing_default } from "./rules/jsx-equals-spacing.js";
import { jsx_first_prop_new_line_default } from "./rules/jsx-first-prop-new-line.js";
import { jsx_function_call_newline_default } from "./rules/jsx-function-call-newline.js";
import { jsx_indent_props_default } from "./rules/jsx-indent-props.js";
import { jsx_indent_default } from "./rules/jsx-indent.js";
import { jsx_max_props_per_line_default } from "./rules/jsx-max-props-per-line.js";
import { jsx_newline_default } from "./rules/jsx-newline.js";
import { jsx_one_expression_per_line_default } from "./rules/jsx-one-expression-per-line.js";
import { jsx_pascal_case_default } from "./rules/jsx-pascal-case.js";
import { jsx_props_no_multi_spaces_default } from "./rules/jsx-props-no-multi-spaces.js";
import { jsx_quotes_default } from "./rules/jsx-quotes.js";
import { jsx_self_closing_comp_default } from "./rules/jsx-self-closing-comp.js";
import { jsx_sort_props_default } from "./rules/jsx-sort-props.js";
import { jsx_tag_spacing_default } from "./rules/jsx-tag-spacing.js";
import { jsx_wrap_multilines_default } from "./rules/jsx-wrap-multilines.js";
import { key_spacing_default } from "./rules/key-spacing.js";
import { keyword_spacing_default } from "./rules/keyword-spacing.js";
import { line_comment_position_default } from "./rules/line-comment-position.js";
import { linebreak_style_default } from "./rules/linebreak-style.js";
import { lines_around_comment_default } from "./rules/lines-around-comment.js";
import { lines_between_class_members_default } from "./rules/lines-between-class-members.js";
import { max_len_default } from "./rules/max-len.js";
import { max_statements_per_line_default } from "./rules/max-statements-per-line.js";
import { member_delimiter_style_default } from "./rules/member-delimiter-style.js";
import { multiline_comment_style_default } from "./rules/multiline-comment-style.js";
import { multiline_ternary_default } from "./rules/multiline-ternary.js";
import { new_parens_default } from "./rules/new-parens.js";
import { newline_per_chained_call_default } from "./rules/newline-per-chained-call.js";
import { no_confusing_arrow_default } from "./rules/no-confusing-arrow.js";
import { no_extra_parens_default } from "./rules/no-extra-parens.js";
import { no_extra_semi_default } from "./rules/no-extra-semi.js";
import { no_floating_decimal_default } from "./rules/no-floating-decimal.js";
import { no_mixed_operators_default } from "./rules/no-mixed-operators.js";
import { no_mixed_spaces_and_tabs_default } from "./rules/no-mixed-spaces-and-tabs.js";
import { no_multi_spaces_default } from "./rules/no-multi-spaces.js";
import { no_multiple_empty_lines_default } from "./rules/no-multiple-empty-lines.js";
import { no_tabs_default } from "./rules/no-tabs.js";
import { no_trailing_spaces_default } from "./rules/no-trailing-spaces.js";
import { no_whitespace_before_property_default } from "./rules/no-whitespace-before-property.js";
import { nonblock_statement_body_position_default } from "./rules/nonblock-statement-body-position.js";
import { object_curly_newline_default } from "./rules/object-curly-newline.js";
import { object_curly_spacing_default } from "./rules/object-curly-spacing.js";
import { object_property_newline_default } from "./rules/object-property-newline.js";
import { one_var_declaration_per_line_default } from "./rules/one-var-declaration-per-line.js";
import { operator_linebreak_default } from "./rules/operator-linebreak.js";
import { padded_blocks_default } from "./rules/padded-blocks.js";
import { padding_line_between_statements_default } from "./rules/padding-line-between-statements.js";
import { quote_props_default } from "./rules/quote-props.js";
import { quotes_default } from "./rules/quotes.js";
import { rest_spread_spacing_default } from "./rules/rest-spread-spacing.js";
import { semi_spacing_default } from "./rules/semi-spacing.js";
import { semi_style_default } from "./rules/semi-style.js";
import { semi_default } from "./rules/semi.js";
import { space_before_blocks_default } from "./rules/space-before-blocks.js";
import { space_before_function_paren_default } from "./rules/space-before-function-paren.js";
import { space_in_parens_default } from "./rules/space-in-parens.js";
import { space_infix_ops_default } from "./rules/space-infix-ops.js";
import { space_unary_ops_default } from "./rules/space-unary-ops.js";
import { spaced_comment_default } from "./rules/spaced-comment.js";
import { switch_colon_spacing_default } from "./rules/switch-colon-spacing.js";
import { template_curly_spacing_default } from "./rules/template-curly-spacing.js";
import { template_tag_spacing_default } from "./rules/template-tag-spacing.js";
import { type_annotation_spacing_default } from "./rules/type-annotation-spacing.js";
import { type_generic_spacing_default } from "./rules/type-generic-spacing.js";
import { type_named_tuple_spacing_default } from "./rules/type-named-tuple-spacing.js";
import { wrap_iife_default } from "./rules/wrap-iife.js";
import { wrap_regex_default } from "./rules/wrap-regex.js";
import { yield_star_spacing_default } from "./rules/yield-star-spacing.js";
var rules_default = {
"array-bracket-newline": array_bracket_newline_default,
"array-bracket-spacing": array_bracket_spacing_default,
"array-element-newline": array_element_newline_default,
"arrow-parens": arrow_parens_default,
"arrow-spacing": arrow_spacing_default,
"block-spacing": block_spacing_default,
"brace-style": brace_style_default,
"comma-dangle": comma_dangle_default,
"comma-spacing": comma_spacing_default,
"comma-style": comma_style_default,
"computed-property-spacing": computed_property_spacing_default,
"curly-newline": curly_newline_default,
"dot-location": dot_location_default,
"eol-last": eol_last_default,
"function-call-argument-newline": function_call_argument_newline_default,
"function-call-spacing": function_call_spacing_default,
"function-paren-newline": function_paren_newline_default,
"generator-star-spacing": generator_star_spacing_default,
"implicit-arrow-linebreak": implicit_arrow_linebreak_default,
"indent": indent_default,
"indent-binary-ops": indent_binary_ops_default,
"jsx-child-element-spacing": jsx_child_element_spacing_default,
"jsx-closing-bracket-location": jsx_closing_bracket_location_default,
"jsx-closing-tag-location": jsx_closing_tag_location_default,
"jsx-curly-brace-presence": jsx_curly_brace_presence_default,
"jsx-curly-newline": jsx_curly_newline_default,
"jsx-curly-spacing": jsx_curly_spacing_default,
"jsx-equals-spacing": jsx_equals_spacing_default,
"jsx-first-prop-new-line": jsx_first_prop_new_line_default,
"jsx-function-call-newline": jsx_function_call_newline_default,
"jsx-indent": jsx_indent_default,
"jsx-indent-props": jsx_indent_props_default,
"jsx-max-props-per-line": jsx_max_props_per_line_default,
"jsx-newline": jsx_newline_default,
"jsx-one-expression-per-line": jsx_one_expression_per_line_default,
"jsx-pascal-case": jsx_pascal_case_default,
"jsx-props-no-multi-spaces": jsx_props_no_multi_spaces_default,
"jsx-quotes": jsx_quotes_default,
"jsx-self-closing-comp": jsx_self_closing_comp_default,
"jsx-sort-props": jsx_sort_props_default,
"jsx-tag-spacing": jsx_tag_spacing_default,
"jsx-wrap-multilines": jsx_wrap_multilines_default,
"key-spacing": key_spacing_default,
"keyword-spacing": keyword_spacing_default,
"line-comment-position": line_comment_position_default,
"linebreak-style": linebreak_style_default,
"lines-around-comment": lines_around_comment_default,
"lines-between-class-members": lines_between_class_members_default,
"max-len": max_len_default,
"max-statements-per-line": max_statements_per_line_default,
"member-delimiter-style": member_delimiter_style_default,
"multiline-comment-style": multiline_comment_style_default,
"multiline-ternary": multiline_ternary_default,
"new-parens": new_parens_default,
"newline-per-chained-call": newline_per_chained_call_default,
"no-confusing-arrow": no_confusing_arrow_default,
"no-extra-parens": no_extra_parens_default,
"no-extra-semi": no_extra_semi_default,
"no-floating-decimal": no_floating_decimal_default,
"no-mixed-operators": no_mixed_operators_default,
"no-mixed-spaces-and-tabs": no_mixed_spaces_and_tabs_default,
"no-multi-spaces": no_multi_spaces_default,
"no-multiple-empty-lines": no_multiple_empty_lines_default,
"no-tabs": no_tabs_default,
"no-trailing-spaces": no_trailing_spaces_default,
"no-whitespace-before-property": no_whitespace_before_property_default,
"nonblock-statement-body-position": nonblock_statement_body_position_default,
"object-curly-newline": object_curly_newline_default,
"object-curly-spacing": object_curly_spacing_default,
"object-property-newline": object_property_newline_default,
"one-var-declaration-per-line": one_var_declaration_per_line_default,
"operator-linebreak": operator_linebreak_default,
"padded-blocks": padded_blocks_default,
"padding-line-between-statements": padding_line_between_statements_default,
"quote-props": quote_props_default,
"quotes": quotes_default,
"rest-spread-spacing": rest_spread_spacing_default,
"semi": semi_default,
"semi-spacing": semi_spacing_default,
"semi-style": semi_style_default,
"space-before-blocks": space_before_blocks_default,
"space-before-function-paren": space_before_function_paren_default,
"space-in-parens": space_in_parens_default,
"space-infix-ops": space_infix_ops_default,
"space-unary-ops": space_unary_ops_default,
"spaced-comment": spaced_comment_default,
"switch-colon-spacing": switch_colon_spacing_default,
"template-curly-spacing": template_curly_spacing_default,
"template-tag-spacing": template_tag_spacing_default,
"type-annotation-spacing": type_annotation_spacing_default,
"type-generic-spacing": type_generic_spacing_default,
"type-named-tuple-spacing": type_named_tuple_spacing_default,
"wrap-iife": wrap_iife_default,
"wrap-regex": wrap_regex_default,
"yield-star-spacing": yield_star_spacing_default
};
const plugin = { rules: rules_default };
var plugin_default = plugin;
function customize(options = {}) {
const { arrowParens = false, blockSpacing = true, braceStyle = "stroustrup", commaDangle = "always-multiline", experimental: enableExperimentalRules = false, indent = 2, jsx = true, pluginName = "@stylistic", quoteProps = "consistent-as-needed", quotes = "single", semi = false, severity = "error" } = options;
let rules = {
"@stylistic/array-bracket-spacing": [severity, "never"],
"@stylistic/arrow-parens": [
severity,
arrowParens ? "always" : "as-needed",
{ requireForBlockBody: true }
],
"@stylistic/arrow-spacing": [severity, {
after: true,
before: true
}],
"@stylistic/block-spacing": [severity, blockSpacing ? "always" : "never"],
"@stylistic/brace-style": [
severity,
braceStyle,
{ allowSingleLine: true }
],
"@stylistic/comma-dangle": [severity, commaDangle],
"@stylistic/comma-spacing": [severity, {
after: true,
before: false
}],
"@stylistic/comma-style": [severity, "last"],
"@stylistic/computed-property-spacing": [
severity,
"never",
{ enforceForClassMembers: true }
],
"@stylistic/dot-location": [severity, "property"],
"@stylistic/eol-last": severity,
"@stylistic/generator-star-spacing": [severity, {
after: true,
before: false
}],
"@stylistic/indent": [
severity,
indent,
{
ArrayExpression: 1,
CallExpression: { arguments: 1 },
flatTernaryExpressions: false,
FunctionDeclaration: {
body: 1,
parameters: 1,
returnType: 1
},
FunctionExpression: {
body: 1,
parameters: 1,
returnType: 1
},
ignoreComments: false,
ignoredNodes: ["TSUnionType", "TSIntersectionType"],
ImportDeclaration: 1,
MemberExpression: 1,
ObjectExpression: 1,
offsetTernaryExpressions: true,
outerIIFEBody: 1,
SwitchCase: 1,
tabLength: indent === "tab" ? 4 : indent,
VariableDeclarator: 1
}
],
"@stylistic/indent-binary-ops": [severity, indent],
"@stylistic/key-spacing": [severity, {
afterColon: true,
beforeColon: false
}],
"@stylistic/keyword-spacing": [severity, {
after: true,
before: true
}],
"@stylistic/lines-between-class-members": [
severity,
"always",
{ exceptAfterSingleLine: true }
],
"@stylistic/max-statements-per-line": [severity, { max: 1 }],
"@stylistic/member-delimiter-style": [severity, {
multiline: {
delimiter: semi ? "semi" : "none",
requireLast: semi
},
multilineDetection: "brackets",
overrides: { interface: { multiline: {
delimiter: semi ? "semi" : "none",
requireLast: semi
} } },
singleline: { delimiter: semi ? "semi" : "comma" }
}],
"@stylistic/multiline-ternary": [severity, "always-multiline"],
"@stylistic/new-parens": severity,
"@stylistic/no-extra-parens": [severity, "functions"],
"@stylistic/no-floating-decimal": severity,
"@stylistic/no-mixed-operators": [severity, {
allowSamePrecedence: true,
groups: [
[
"==",
"!=",
"===",
"!==",
">",
">=",
"<",
"<="
],
["&&", "||"],
["in", "instanceof"]
]
}],
"@stylistic/no-mixed-spaces-and-tabs": severity,
"@stylistic/no-multi-spaces": severity,
"@stylistic/no-multiple-empty-lines": [severity, {
max: 1,
maxBOF: 0,
maxEOF: 0
}],
"@stylistic/no-tabs": indent === "tab" ? "off" : severity,
"@stylistic/no-trailing-spaces": severity,
"@stylistic/no-whitespace-before-property": severity,
"@stylistic/object-curly-spacing": [severity, "always"],
"@stylistic/operator-linebreak": [severity, "before"],
"@stylistic/padded-blocks": [severity, {
blocks: "never",
classes: "never",
switches: "never"
}],
"@stylistic/quote-props": [severity, quoteProps],
"@stylistic/quotes": [
severity,
quotes,
{
allowTemplateLiterals: "always",
avoidEscape: false
}
],
"@stylistic/rest-spread-spacing": [severity, "never"],
"@stylistic/semi": [severity, semi ? "always" : "never"],
"@stylistic/semi-spacing": [severity, {
after: true,
before: false
}],
"@stylistic/space-before-blocks": [severity, "always"],
"@stylistic/space-before-function-paren": [severity, {
anonymous: "always",
asyncArrow: "always",
named: "never"
}],
"@stylistic/space-in-parens": [severity, "never"],
"@stylistic/space-infix-ops": severity,
"@stylistic/space-unary-ops": [severity, {
nonwords: false,
words: true
}],
"@stylistic/spaced-comment": [
severity,
"always",
{
block: {
balanced: true,
exceptions: ["*"],
markers: ["!"]
},
line: {
exceptions: ["/", "#"],
markers: ["/"]
}
}
],
"@stylistic/template-curly-spacing": severity,
"@stylistic/template-tag-spacing": [severity, "never"],
"@stylistic/type-annotation-spacing": [severity, {}],
"@stylistic/type-generic-spacing": severity,
"@stylistic/type-named-tuple-spacing": severity,
"@stylistic/wrap-iife": [
severity,
"any",
{ functionPrototypeMethods: true }
],
"@stylistic/yield-star-spacing": [severity, {
after: true,
before: false
}],
...jsx ? {
"@stylistic/jsx-closing-bracket-location": severity,
"@stylistic/jsx-closing-tag-location": severity,
"@stylistic/jsx-curly-brace-presence": [severity, { propElementValues: "always" }],
"@stylistic/jsx-curly-newline": severity,
"@stylistic/jsx-curly-spacing": [severity, "never"],
"@stylistic/jsx-equals-spacing": severity,
"@stylistic/jsx-first-prop-new-line": severity,
"@stylistic/jsx-function-call-newline": [severity, "multiline"],
"@stylistic/jsx-indent-props": [severity, indent],
"@stylistic/jsx-max-props-per-line": [severity, {
maximum: 1,
when: "multiline"
}],
"@stylistic/jsx-one-expression-per-line": [severity, { allow: "single-child" }],
"@stylistic/jsx-quotes": severity,
"@stylistic/jsx-tag-spacing": [severity, {
afterOpening: "never",
beforeClosing: "never",
beforeSelfClosing: "always",
closingSlash: "never"
}],
"@stylistic/jsx-wrap-multilines": [severity, {
arrow: "parens-new-line",
assignment: "parens-new-line",
condition: "parens-new-line",
declaration: "parens-new-line",
logical: "parens-new-line",
prop: "parens-new-line",
propertyValue: "parens-new-line",
return: "parens-new-line"
}]
} : {}
};
if (enableExperimentalRules) {}
if (pluginName !== "@stylistic") {
const regex = /^@stylistic\//;
rules = Object.fromEntries(Object.entries(rules).map(([ruleName, ruleConfig]) => [ruleName.replace(regex, `${pluginName}/`), ruleConfig]));
}
return {
plugins: { [pluginName]: plugin_default },
rules
};
}
const config = { rules: {
"array-bracket-newline": 0,
"array-bracket-spacing": 0,
"array-element-newline": 0,
"arrow-parens": 0,
"arrow-spacing": 0,
"block-spacing": 0,
"brace-style": 0,
"comma-dangle": 0,
"comma-spacing": 0,
"comma-style": 0,
"computed-property-spacing": 0,
"dot-location": 0,
"eol-last": 0,
"func-call-spacing": 0,
"function-call-argument-newline": 0,
"function-paren-newline": 0,
"generator-star-spacing": 0,
"implicit-arrow-linebreak": 0,
"indent": 0,
"jsx-quotes": 0,
"key-spacing": 0,
"keyword-spacing": 0,
"linebreak-style": 0,
"lines-around-comment": 0,
"lines-between-class-members": 0,
"max-len": 0,
"max-statements-per-line": 0,
"multiline-ternary": 0,
"new-parens": 0,
"newline-per-chained-call": 0,
"no-confusing-arrow": 0,
"no-extra-parens": 0,
"no-extra-semi": 0,
"no-floating-decimal": 0,
"no-mixed-operators": 0,
"no-mixed-spaces-and-tabs": 0,
"no-multi-spaces": 0,
"no-multiple-empty-lines": 0,
"no-tabs": 0,
"no-trailing-spaces": 0,
"no-whitespace-before-property": 0,
"nonblock-statement-body-position": 0,
"object-curly-newline": 0,
"object-curly-spacing": 0,
"object-property-newline": 0,
"one-var-declaration-per-line": 0,
"operator-linebreak": 0,
"padded-blocks": 0,
"padding-line-between-statements": 0,
"quote-props": 0,
"quotes": 0,
"rest-spread-spacing": 0,
"semi": 0,
"semi-spacing": 0,
"semi-style": 0,
"space-before-blocks": 0,
"space-before-function-paren": 0,
"space-in-parens": 0,
"space-infix-ops": 0,
"space-unary-ops": 0,
"spaced-comment": 0,
"switch-colon-spacing": 0,
"template-curly-spacing": 0,
"template-tag-spacing": 0,
"wrap-iife": 0,
"wrap-regex": 0,
"yield-star-spacing": 0,
"@typescript-eslint/block-spacing": 0,
"@typescript-eslint/brace-style": 0,
"@typescript-eslint/comma-dangle": 0,
"@typescript-eslint/comma-spacing": 0,
"@typescript-eslint/func-call-spacing": 0,
"@typescript-eslint/indent": 0,
"@typescript-eslint/key-spacing": 0,
"@typescript-eslint/keyword-spacing": 0,
"@typescript-eslint/lines-around-comment": 0,
"@typescript-eslint/lines-between-class-members": 0,
"@typescript-eslint/member-delimiter-style": 0,
"@typescript-eslint/no-extra-parens": 0,
"@typescript-eslint/no-extra-semi": 0,
"@typescript-eslint/object-curly-spacing": 0,
"@typescript-eslint/padding-line-between-statements": 0,
"@typescript-eslint/quotes": 0,
"@typescript-eslint/semi": 0,
"@typescript-eslint/space-before-blocks": 0,
"@typescript-eslint/space-before-function-paren": 0,
"@typescript-eslint/space-infix-ops": 0,
"@typescript-eslint/type-annotation-spacing": 0,
"react/jsx-child-element-spacing": 0,
"react/jsx-closing-bracket-location": 0,
"react/jsx-closing-tag-location": 0,
"react/jsx-curly-brace-presence": 0,
"react/jsx-curly-newline": 0,
"react/jsx-curly-spacing": 0,
"react/jsx-equals-spacing": 0,
"react/jsx-first-prop-new-line": 0,
"react/jsx-indent": 0,
"react/jsx-indent-props": 0,
"react/jsx-max-props-per-line": 0,
"react/jsx-newline": 0,
"react/jsx-one-expression-per-line": 0,
"react/jsx-pascal-case": 0,
"react/jsx-props-no-multi-spaces": 0,
"react/self-closing-comp": 0,
"react/jsx-sort-props": 0,
"react/jsx-tag-spacing": 0,
"react/jsx-wrap-multilines": 0
} };
var disable_legacy_default = config;
const allConfigsIgnore = [/^jsx-/, /^curly-newline$/];
const all = /* @__PURE__ */ createAllConfigs(plugin_default, "@stylistic", (name) => !allConfigsIgnore.some((re) => re.test(name)));
const recommended = /* @__PURE__ */ customize();
const configs = new Proxy({
"disable-legacy": disable_legacy_default,
"customize": customize,
"recommended": recommended,
"recommended-flat": recommended,
"all": all,
"all-flat": all
}, { get(target, p, receiver) {
const prop = p.toString();
if (prop.endsWith("-flat")) warnDeprecation(`config("${prop}")`, `"${prop.replace("-flat", "")}"`);
return Reflect.get(target, p, receiver);
} });
export { configs, plugin_default };

View File

@@ -0,0 +1,7 @@
import { RuleOptions } from "./rule-options2.js";
//#region dts/define-config-support.d.ts
declare module 'eslint-define-config' {
export interface CustomRuleOptions extends RuleOptions {}
}

View File

@@ -0,0 +1,130 @@
import { RuleOptions, UnprefixedRuleOptions } from "./rule-options2.js";
import { ESLint, Linter, Rule } from "eslint";
//#region dts/options.d.ts
interface StylisticCustomizeOptions {
/**
* The name of the registered plugin, used to prefix rule IDs
* @default '@stylistic'
*/
pluginName?: string;
/**
* Indentation level
* Similar to the `tabWidth` and `useTabs` options in Prettier
*
* @default 2
*/
indent?: number | 'tab';
/**
* Quote style
* Similar to `singleQuote` option in Prettier
*
* @default 'single'
*/
quotes?: 'single' | 'double' | 'backtick';
/**
* Whether to enable semicolons
* Similar to `semi` option in Prettier
*
* @default false
*/
semi?: boolean;
/**
* Enable JSX support
* @default true
*/
jsx?: boolean;
/**
* When to enable arrow parenthesis
* Similar to `arrowParens` option in Prettier
*
* @default false
*/
arrowParens?: boolean;
/**
* Which brace style to use
* @default 'stroustrup'
*/
braceStyle?: '1tbs' | 'stroustrup' | 'allman';
/**
* Whether to require spaces around braces
* Similar to `bracketSpacing` option in Prettier
*
* @default true
*/
blockSpacing?: boolean;
/**
* When to enable prop quoting
* Similar to `quoteProps` option in Prettier
*
* @default 'consistent-as-needed'
*/
quoteProps?: 'always' | 'as-needed' | 'consistent' | 'consistent-as-needed';
/**
* When to enable comma dangles
* Similar to `trailingComma` option in Prettier
*
* @default 'always-multiline'
*/
commaDangle?: 'never' | 'always' | 'always-multiline' | 'only-multiline';
/**
* Severity level of the rules
* Determines how violations are reported.
*
* @default 'error'
*/
severity?: 'error' | 'warn';
/**
* Enable the experimental rules
*
* @default false
*/
experimental?: boolean;
}
//#endregion
//#region dts/configs.d.ts
declare function customize(options?: StylisticCustomizeOptions): Linter.Config;
declare const configs: {
/**
* Disable all legacy rules from `eslint`, `@typescript-eslint` and `eslint-plugin-react`
*
* This config works for both flat and legacy config format
*/
'disable-legacy': Linter.Config;
/**
* A factory function to customize the recommended config
*/
'customize': typeof customize;
/**
* The default recommended config in Flat Config Format
*/
'recommended': Linter.Config;
/**
* The default recommended config in Flat Config Format
*
* @deprecated use `recommended` instead.
*/
'recommended-flat': Linter.Config;
/**
* Enable all rules, in Flat Config Format
*/
'all': Linter.Config;
/**
* Enable all rules, in Flat Config Format
*
* @deprecated use `all` instead.
*/
'all-flat': Linter.Config;
};
type Configs = typeof configs;
//#endregion
//#region dts/rules.d.ts
type Rules = { [K in keyof UnprefixedRuleOptions]: Rule.RuleModule };
//#endregion
//#region dts/index.d.ts
declare const plugin: {
rules: Rules;
configs: ESLint.Plugin['configs'] & Configs;
};
//#endregion
export { type Configs, type RuleOptions, type Rules, type StylisticCustomizeOptions, type UnprefixedRuleOptions, plugin as default };

View File

@@ -0,0 +1,2 @@
import { RuleOptions, UnprefixedRuleOptions } from "./rule-options2.js";
export { RuleOptions, UnprefixedRuleOptions };

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,100 @@
import "./utils.js";
import "./vendor.js";
import "./rules/array-bracket-newline.js";
import "./rules/array-bracket-spacing.js";
import "./rules/array-element-newline.js";
import "./rules/arrow-parens.js";
import "./rules/arrow-spacing.js";
import "./rules/block-spacing.js";
import "./rules/brace-style.js";
import "./rules/comma-dangle.js";
import "./rules/comma-spacing.js";
import "./rules/comma-style.js";
import "./rules/computed-property-spacing.js";
import "./rules/curly-newline.js";
import "./rules/dot-location.js";
import "./rules/eol-last.js";
import "./rules/function-call-argument-newline.js";
import "./rules/function-call-spacing.js";
import "./rules/function-paren-newline.js";
import "./rules/generator-star-spacing.js";
import "./rules/implicit-arrow-linebreak.js";
import "./rules/indent-binary-ops.js";
import "./rules/indent.js";
import "./rules/jsx-child-element-spacing.js";
import "./rules/jsx-closing-bracket-location.js";
import "./rules/jsx-closing-tag-location.js";
import "./rules/jsx-curly-brace-presence.js";
import "./rules/jsx-curly-newline.js";
import "./rules/jsx-curly-spacing.js";
import "./rules/jsx-equals-spacing.js";
import "./rules/jsx-first-prop-new-line.js";
import "./rules/jsx-function-call-newline.js";
import "./rules/jsx-indent-props.js";
import "./rules/jsx-indent.js";
import "./rules/jsx-max-props-per-line.js";
import "./rules/jsx-newline.js";
import "./rules/jsx-one-expression-per-line.js";
import "./rules/jsx-pascal-case.js";
import "./rules/jsx-props-no-multi-spaces.js";
import "./rules/jsx-quotes.js";
import "./rules/jsx-self-closing-comp.js";
import "./rules/jsx-sort-props.js";
import "./rules/jsx-tag-spacing.js";
import "./rules/jsx-wrap-multilines.js";
import "./rules/key-spacing.js";
import "./rules/keyword-spacing.js";
import "./rules/line-comment-position.js";
import "./rules/linebreak-style.js";
import "./rules/lines-around-comment.js";
import "./rules/lines-between-class-members.js";
import "./rules/max-len.js";
import "./rules/max-statements-per-line.js";
import "./rules/member-delimiter-style.js";
import "./rules/multiline-comment-style.js";
import "./rules/multiline-ternary.js";
import "./rules/new-parens.js";
import "./rules/newline-per-chained-call.js";
import "./rules/no-confusing-arrow.js";
import "./rules/no-extra-parens.js";
import "./rules/no-extra-semi.js";
import "./rules/no-floating-decimal.js";
import "./rules/no-mixed-operators.js";
import "./rules/no-mixed-spaces-and-tabs.js";
import "./rules/no-multi-spaces.js";
import "./rules/no-multiple-empty-lines.js";
import "./rules/no-tabs.js";
import "./rules/no-trailing-spaces.js";
import "./rules/no-whitespace-before-property.js";
import "./rules/nonblock-statement-body-position.js";
import "./rules/object-curly-newline.js";
import "./rules/object-curly-spacing.js";
import "./rules/object-property-newline.js";
import "./rules/one-var-declaration-per-line.js";
import "./rules/operator-linebreak.js";
import "./rules/padded-blocks.js";
import "./rules/padding-line-between-statements.js";
import "./rules/quote-props.js";
import "./rules/quotes.js";
import "./rules/rest-spread-spacing.js";
import "./rules/semi-spacing.js";
import "./rules/semi-style.js";
import "./rules/semi.js";
import "./rules/space-before-blocks.js";
import "./rules/space-before-function-paren.js";
import "./rules/space-in-parens.js";
import "./rules/space-infix-ops.js";
import "./rules/space-unary-ops.js";
import "./rules/spaced-comment.js";
import "./rules/switch-colon-spacing.js";
import "./rules/template-curly-spacing.js";
import "./rules/template-tag-spacing.js";
import "./rules/type-annotation-spacing.js";
import "./rules/type-generic-spacing.js";
import "./rules/type-named-tuple-spacing.js";
import "./rules/wrap-iife.js";
import "./rules/wrap-regex.js";
import "./rules/yield-star-spacing.js";
import { configs, plugin_default } from "./configs.js";
const index = Object.assign(plugin_default, { configs });
export { index as default, index as "module.exports" };

View File

@@ -0,0 +1,31 @@
import { createRequire } from "node:module";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
var __export = (target, all) => {
for (var name in all) __defProp(target, name, {
get: all[name],
enumerable: true
});
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
return to;
};
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
var __require = /* @__PURE__ */ createRequire(import.meta.url);
export { __commonJSMin, __export, __reExport, __require, __toESM };

View File

@@ -0,0 +1,135 @@
import { ast_exports, createRule } from "../utils.js";
var array_bracket_newline_default = createRule({
name: "array-bracket-newline",
meta: {
type: "layout",
docs: { description: "Enforce linebreaks after opening and before closing array brackets" },
fixable: "whitespace",
schema: [{ oneOf: [{
type: "string",
enum: [
"always",
"never",
"consistent"
]
}, {
type: "object",
properties: {
multiline: { type: "boolean" },
minItems: {
type: ["integer", "null"],
minimum: 0
}
},
additionalProperties: false
}] }],
messages: {
unexpectedOpeningLinebreak: "There should be no linebreak after '['.",
unexpectedClosingLinebreak: "There should be no linebreak before ']'.",
missingOpeningLinebreak: "A linebreak is required after '['.",
missingClosingLinebreak: "A linebreak is required before ']'."
}
},
create(context) {
const sourceCode = context.sourceCode;
function normalizeOptionValue(option) {
let consistent = false;
let multiline = false;
let minItems = 0;
if (option) if (option === "consistent") {
consistent = true;
minItems = Number.POSITIVE_INFINITY;
} else if (option === "always" || typeof option !== "string" && option.minItems === 0) minItems = 0;
else if (option === "never") minItems = Number.POSITIVE_INFINITY;
else {
multiline = Boolean(option.multiline);
minItems = option.minItems || Number.POSITIVE_INFINITY;
}
else {
consistent = false;
multiline = true;
minItems = Number.POSITIVE_INFINITY;
}
return {
consistent,
multiline,
minItems
};
}
function normalizeOptions(options) {
const value = normalizeOptionValue(options);
return {
ArrayExpression: value,
ArrayPattern: value
};
}
function reportNoBeginningLinebreak(node, token) {
context.report({
node,
loc: token.loc,
messageId: "unexpectedOpeningLinebreak",
fix(fixer) {
const nextToken = sourceCode.getTokenAfter(token, { includeComments: true });
if (!nextToken || (0, ast_exports.isCommentToken)(nextToken)) return null;
return fixer.removeRange([token.range[1], nextToken.range[0]]);
}
});
}
function reportNoEndingLinebreak(node, token) {
context.report({
node,
loc: token.loc,
messageId: "unexpectedClosingLinebreak",
fix(fixer) {
const previousToken = sourceCode.getTokenBefore(token, { includeComments: true });
if (!previousToken || (0, ast_exports.isCommentToken)(previousToken)) return null;
return fixer.removeRange([previousToken.range[1], token.range[0]]);
}
});
}
function reportRequiredBeginningLinebreak(node, token) {
context.report({
node,
loc: token.loc,
messageId: "missingOpeningLinebreak",
fix(fixer) {
return fixer.insertTextAfter(token, "\n");
}
});
}
function reportRequiredEndingLinebreak(node, token) {
context.report({
node,
loc: token.loc,
messageId: "missingClosingLinebreak",
fix(fixer) {
return fixer.insertTextBefore(token, "\n");
}
});
}
function check(node) {
const elements = node.elements;
const normalizedOptions = normalizeOptions(context.options[0]);
const options = normalizedOptions[node.type];
const openBracket = sourceCode.getFirstToken(node);
const closeBracket = sourceCode.getLastToken(node);
const firstIncComment = sourceCode.getTokenAfter(openBracket, { includeComments: true });
const lastIncComment = sourceCode.getTokenBefore(closeBracket, { includeComments: true });
const first = sourceCode.getTokenAfter(openBracket);
const last = sourceCode.getTokenBefore(closeBracket);
const needsLinebreaks = elements.length >= options.minItems || options.multiline && elements.length > 0 && !(0, ast_exports.isTokenOnSameLine)(lastIncComment, firstIncComment) || elements.length === 0 && firstIncComment.type === "Block" && !(0, ast_exports.isTokenOnSameLine)(lastIncComment, firstIncComment) && firstIncComment === lastIncComment || options.consistent && !(0, ast_exports.isTokenOnSameLine)(openBracket, first);
if (needsLinebreaks) {
if ((0, ast_exports.isTokenOnSameLine)(openBracket, first)) reportRequiredBeginningLinebreak(node, openBracket);
if ((0, ast_exports.isTokenOnSameLine)(last, closeBracket)) reportRequiredEndingLinebreak(node, closeBracket);
} else {
if (!(0, ast_exports.isTokenOnSameLine)(openBracket, first)) reportNoBeginningLinebreak(node, openBracket);
if (!(0, ast_exports.isTokenOnSameLine)(last, closeBracket)) reportNoEndingLinebreak(node, closeBracket);
}
}
return {
ArrayPattern: check,
ArrayExpression: check
};
}
});
export { array_bracket_newline_default };

View File

@@ -0,0 +1,122 @@
import { ast_exports, createRule } from "../utils.js";
var array_bracket_spacing_default = createRule({
name: "array-bracket-spacing",
meta: {
type: "layout",
docs: { description: "Enforce consistent spacing inside array brackets" },
fixable: "whitespace",
schema: [{
type: "string",
enum: ["always", "never"]
}, {
type: "object",
properties: {
singleValue: { type: "boolean" },
objectsInArrays: { type: "boolean" },
arraysInArrays: { type: "boolean" }
},
additionalProperties: false
}],
messages: {
unexpectedSpaceAfter: "There should be no space after '{{tokenValue}}'.",
unexpectedSpaceBefore: "There should be no space before '{{tokenValue}}'.",
missingSpaceAfter: "A space is required after '{{tokenValue}}'.",
missingSpaceBefore: "A space is required before '{{tokenValue}}'."
}
},
create(context) {
const spaced = context.options[0] === "always";
const sourceCode = context.sourceCode;
function isOptionSet(option) {
return context.options[1] ? context.options[1][option] === !spaced : false;
}
const options = {
spaced,
singleElementException: isOptionSet("singleValue"),
objectsInArraysException: isOptionSet("objectsInArrays"),
arraysInArraysException: isOptionSet("arraysInArrays")
};
function reportNoBeginningSpace(node, token) {
const nextToken = sourceCode.getTokenAfter(token);
context.report({
node,
loc: {
start: token.loc.end,
end: nextToken.loc.start
},
messageId: "unexpectedSpaceAfter",
data: { tokenValue: token.value },
fix(fixer) {
return fixer.removeRange([token.range[1], nextToken.range[0]]);
}
});
}
function reportNoEndingSpace(node, token) {
const previousToken = sourceCode.getTokenBefore(token);
context.report({
node,
loc: {
start: previousToken.loc.end,
end: token.loc.start
},
messageId: "unexpectedSpaceBefore",
data: { tokenValue: token.value },
fix(fixer) {
return fixer.removeRange([previousToken.range[1], token.range[0]]);
}
});
}
function reportRequiredBeginningSpace(node, token) {
context.report({
node,
loc: token.loc,
messageId: "missingSpaceAfter",
data: { tokenValue: token.value },
fix(fixer) {
return fixer.insertTextAfter(token, " ");
}
});
}
function reportRequiredEndingSpace(node, token) {
context.report({
node,
loc: token.loc,
messageId: "missingSpaceBefore",
data: { tokenValue: token.value },
fix(fixer) {
return fixer.insertTextBefore(token, " ");
}
});
}
function isObjectType(node) {
return node && (node.type === "ObjectExpression" || node.type === "ObjectPattern");
}
function isArrayType(node) {
return node && (node.type === "ArrayExpression" || node.type === "ArrayPattern");
}
function validateArraySpacing(node) {
if (options.spaced && node.elements.length === 0) return;
const first = sourceCode.getFirstToken(node);
const second = sourceCode.getFirstToken(node, 1);
const last = node.type === "ArrayPattern" && node.typeAnnotation ? sourceCode.getTokenBefore(node.typeAnnotation) : sourceCode.getLastToken(node);
const penultimate = sourceCode.getTokenBefore(last);
const firstElement = node.elements[0];
const lastElement = node.elements[node.elements.length - 1];
const openingBracketMustBeSpaced = firstElement && options.objectsInArraysException && isObjectType(firstElement) || firstElement && options.arraysInArraysException && isArrayType(firstElement) || options.singleElementException && node.elements.length === 1 ? !options.spaced : options.spaced;
const closingBracketMustBeSpaced = lastElement && options.objectsInArraysException && isObjectType(lastElement) || lastElement && options.arraysInArraysException && isArrayType(lastElement) || options.singleElementException && node.elements.length === 1 ? !options.spaced : options.spaced;
if ((0, ast_exports.isTokenOnSameLine)(first, second)) {
if (openingBracketMustBeSpaced && !sourceCode.isSpaceBetween(first, second)) reportRequiredBeginningSpace(node, first);
if (!openingBracketMustBeSpaced && sourceCode.isSpaceBetween(first, second)) reportNoBeginningSpace(node, first);
}
if (first !== penultimate && (0, ast_exports.isTokenOnSameLine)(penultimate, last)) {
if (closingBracketMustBeSpaced && !sourceCode.isSpaceBetween(penultimate, last)) reportRequiredEndingSpace(node, last);
if (!closingBracketMustBeSpaced && sourceCode.isSpaceBetween(penultimate, last)) reportNoEndingSpace(node, last);
}
}
return {
ArrayPattern: validateArraySpacing,
ArrayExpression: validateArraySpacing
};
}
});
export { array_bracket_spacing_default };

View File

@@ -0,0 +1,148 @@
import { ast_exports, createRule, isSingleLine } from "../utils.js";
var array_element_newline_default = createRule({
name: "array-element-newline",
meta: {
type: "layout",
docs: { description: "Enforce line breaks after each array element" },
fixable: "whitespace",
schema: {
definitions: { basicConfig: { oneOf: [{
type: "string",
enum: [
"always",
"never",
"consistent"
]
}, {
type: "object",
properties: {
consistent: { type: "boolean" },
multiline: { type: "boolean" },
minItems: {
type: ["integer", "null"],
minimum: 0
}
},
additionalProperties: false
}] } },
type: "array",
items: [{ oneOf: [{ $ref: "#/definitions/basicConfig" }, {
type: "object",
properties: {
ArrayExpression: { $ref: "#/definitions/basicConfig" },
ArrayPattern: { $ref: "#/definitions/basicConfig" }
},
additionalProperties: false,
minProperties: 1
}] }]
},
messages: {
unexpectedLineBreak: "There should be no linebreak here.",
missingLineBreak: "There should be a linebreak after this element."
}
},
create(context) {
const sourceCode = context.sourceCode;
function normalizeOptionValue(providedOption) {
let consistent = false;
let multiline = false;
let minItems;
const option = providedOption || "always";
if (!option || option === "always" || typeof option === "object" && option.minItems === 0) minItems = 0;
else if (option === "never") minItems = Number.POSITIVE_INFINITY;
else if (option === "consistent") {
consistent = true;
minItems = Number.POSITIVE_INFINITY;
} else {
consistent = Boolean(option.consistent);
multiline = Boolean(option.multiline);
minItems = option.minItems || Number.POSITIVE_INFINITY;
}
return {
consistent,
multiline,
minItems
};
}
function normalizeOptions(options) {
if (options && (options.ArrayExpression || options.ArrayPattern)) {
let expressionOptions, patternOptions;
if (options.ArrayExpression) expressionOptions = normalizeOptionValue(options.ArrayExpression);
if (options.ArrayPattern) patternOptions = normalizeOptionValue(options.ArrayPattern);
return {
ArrayExpression: expressionOptions,
ArrayPattern: patternOptions
};
}
const value = normalizeOptionValue(options);
return {
ArrayExpression: value,
ArrayPattern: value
};
}
function reportNoLineBreak(token) {
const tokenBefore = sourceCode.getTokenBefore(token, { includeComments: true });
context.report({
loc: {
start: tokenBefore.loc.end,
end: token.loc.start
},
messageId: "unexpectedLineBreak",
fix(fixer) {
if ((0, ast_exports.isCommentToken)(tokenBefore)) return null;
if (!(0, ast_exports.isTokenOnSameLine)(tokenBefore, token)) return fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], " ");
const twoTokensBefore = sourceCode.getTokenBefore(tokenBefore, { includeComments: true });
if ((0, ast_exports.isCommentToken)(twoTokensBefore)) return null;
return fixer.replaceTextRange([twoTokensBefore.range[1], tokenBefore.range[0]], "");
}
});
}
function reportRequiredLineBreak(token) {
const tokenBefore = sourceCode.getTokenBefore(token, { includeComments: true });
context.report({
loc: {
start: tokenBefore.loc.end,
end: token.loc.start
},
messageId: "missingLineBreak",
fix(fixer) {
return fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], "\n");
}
});
}
function check(node) {
const elements = node.elements;
const normalizedOptions = normalizeOptions(context.options[0]);
const options = normalizedOptions[node.type];
if (!options) return;
let elementBreak = false;
if (options.multiline) elementBreak = elements.some((element) => element !== null && !isSingleLine(element));
let linebreaksCount = 0;
for (let i = 0; i < node.elements.length; i++) {
const element = node.elements[i];
const previousElement = elements[i - 1];
if (i === 0 || element === null || previousElement === null) continue;
const commaToken = sourceCode.getFirstTokenBetween(previousElement, element, ast_exports.isCommaToken);
const lastTokenOfPreviousElement = sourceCode.getTokenBefore(commaToken);
const firstTokenOfCurrentElement = sourceCode.getTokenAfter(commaToken);
if (!(0, ast_exports.isTokenOnSameLine)(lastTokenOfPreviousElement, firstTokenOfCurrentElement)) linebreaksCount++;
}
const needsLinebreaks = elements.length >= options.minItems || options.multiline && elementBreak || options.consistent && linebreaksCount > 0 && linebreaksCount < node.elements.length;
elements.forEach((element, i) => {
const previousElement = elements[i - 1];
if (i === 0 || element === null || previousElement === null) return;
const commaToken = sourceCode.getFirstTokenBetween(previousElement, element, ast_exports.isCommaToken);
const lastTokenOfPreviousElement = sourceCode.getTokenBefore(commaToken);
const firstTokenOfCurrentElement = sourceCode.getTokenAfter(commaToken);
if (needsLinebreaks) {
if ((0, ast_exports.isTokenOnSameLine)(lastTokenOfPreviousElement, firstTokenOfCurrentElement)) reportRequiredLineBreak(firstTokenOfCurrentElement);
} else if (!(0, ast_exports.isTokenOnSameLine)(lastTokenOfPreviousElement, firstTokenOfCurrentElement)) reportNoLineBreak(firstTokenOfCurrentElement);
});
}
return {
ArrayPattern: check,
ArrayExpression: check
};
}
});
export { array_element_newline_default };

View File

@@ -0,0 +1,77 @@
import { ast_exports, canTokensBeAdjacent, createRule } from "../utils.js";
function hasBlockBody(node) {
return node.body.type === "BlockStatement";
}
var arrow_parens_default = createRule({
name: "arrow-parens",
meta: {
type: "layout",
docs: { description: "Require parentheses around arrow function arguments" },
fixable: "code",
schema: [{
type: "string",
enum: ["always", "as-needed"]
}, {
type: "object",
properties: { requireForBlockBody: {
type: "boolean",
default: false
} },
additionalProperties: false
}],
messages: {
unexpectedParens: "Unexpected parentheses around single function argument.",
expectedParens: "Expected parentheses around arrow function argument.",
unexpectedParensInline: "Unexpected parentheses around single function argument having a body with no curly braces.",
expectedParensBlock: "Expected parentheses around arrow function argument having a body with curly braces."
}
},
create(context) {
const asNeeded = context.options[0] === "as-needed";
const requireForBlockBody = asNeeded && context.options[1] && context.options[1].requireForBlockBody === true;
const sourceCode = context.sourceCode;
function findOpeningParenOfParams(node) {
const tokenBeforeParams = sourceCode.getTokenBefore(node.params[0]);
if (tokenBeforeParams && (0, ast_exports.isOpeningParenToken)(tokenBeforeParams) && node.range[0] <= tokenBeforeParams.range[0]) return tokenBeforeParams;
return null;
}
function getClosingParenOfParams(node) {
return sourceCode.getTokenAfter(node.params[0], ast_exports.isClosingParenToken);
}
function hasCommentsInParensOfParams(node, openingParen) {
return sourceCode.commentsExistBetween(openingParen, getClosingParenOfParams(node));
}
function hasUnexpectedTokensBeforeOpeningParen(node, openingParen) {
const expectedCount = node.async ? 1 : 0;
return sourceCode.getFirstToken(node, { skip: expectedCount }) !== openingParen;
}
return { "ArrowFunctionExpression[params.length=1]": function(node) {
const shouldHaveParens = !asNeeded || requireForBlockBody && hasBlockBody(node);
const openingParen = findOpeningParenOfParams(node);
const hasParens = openingParen !== null;
const [param] = node.params;
if (shouldHaveParens && !hasParens) context.report({
node,
messageId: requireForBlockBody ? "expectedParensBlock" : "expectedParens",
loc: param.loc,
*fix(fixer) {
yield fixer.insertTextBefore(param, "(");
yield fixer.insertTextAfter(param, ")");
}
});
if (!shouldHaveParens && hasParens && param.type === "Identifier" && !param.optional && !param.typeAnnotation && !node.returnType && !hasCommentsInParensOfParams(node, openingParen) && !hasUnexpectedTokensBeforeOpeningParen(node, openingParen)) context.report({
node,
messageId: requireForBlockBody ? "unexpectedParensInline" : "unexpectedParens",
loc: param.loc,
*fix(fixer) {
const tokenBeforeOpeningParen = sourceCode.getTokenBefore(openingParen);
const closingParen = getClosingParenOfParams(node);
if (tokenBeforeOpeningParen && tokenBeforeOpeningParen.range[1] === openingParen.range[0] && !canTokensBeAdjacent(tokenBeforeOpeningParen, sourceCode.getFirstToken(param))) yield fixer.insertTextBefore(openingParen, " ");
yield fixer.removeRange([openingParen.range[0], param.range[0]]);
yield fixer.removeRange([param.range[1], closingParen.range[1]]);
}
});
} };
}
});
export { arrow_parens_default };

View File

@@ -0,0 +1,87 @@
import { ast_exports, createRule } from "../utils.js";
var arrow_spacing_default = createRule({
name: "arrow-spacing",
meta: {
type: "layout",
docs: { description: "Enforce consistent spacing before and after the arrow in arrow functions" },
fixable: "whitespace",
schema: [{
type: "object",
properties: {
before: {
type: "boolean",
default: true
},
after: {
type: "boolean",
default: true
}
},
additionalProperties: false
}],
messages: {
expectedBefore: "Missing space before =>.",
unexpectedBefore: "Unexpected space before =>.",
expectedAfter: "Missing space after =>.",
unexpectedAfter: "Unexpected space after =>."
}
},
create(context) {
const rule = Object.assign({}, context.options[0]);
rule.before = rule.before !== false;
rule.after = rule.after !== false;
const sourceCode = context.sourceCode;
function getTokens(node) {
const arrow = sourceCode.getTokenBefore(node.body, ast_exports.isArrowToken);
return {
before: sourceCode.getTokenBefore(arrow),
arrow,
after: sourceCode.getTokenAfter(arrow)
};
}
function countSpaces(tokens) {
const before = tokens.arrow.range[0] - tokens.before.range[1];
const after = tokens.after.range[0] - tokens.arrow.range[1];
return {
before,
after
};
}
function spaces(node) {
const tokens = getTokens(node);
const countSpace = countSpaces(tokens);
if (rule.before) {
if (countSpace.before === 0) context.report({
node: tokens.before,
messageId: "expectedBefore",
fix(fixer) {
return fixer.insertTextBefore(tokens.arrow, " ");
}
});
} else if (countSpace.before > 0) context.report({
node: tokens.before,
messageId: "unexpectedBefore",
fix(fixer) {
return fixer.removeRange([tokens.before.range[1], tokens.arrow.range[0]]);
}
});
if (rule.after) {
if (countSpace.after === 0) context.report({
node: tokens.after,
messageId: "expectedAfter",
fix(fixer) {
return fixer.insertTextAfter(tokens.arrow, " ");
}
});
} else if (countSpace.after > 0) context.report({
node: tokens.after,
messageId: "unexpectedAfter",
fix(fixer) {
return fixer.removeRange([tokens.arrow.range[1], tokens.after.range[0]]);
}
});
}
return { ArrowFunctionExpression: spaces };
}
});
export { arrow_spacing_default };

View File

@@ -0,0 +1,83 @@
import { AST_TOKEN_TYPES, ast_exports, createRule } from "../utils.js";
var block_spacing_default = createRule({
name: "block-spacing",
meta: {
type: "layout",
docs: { description: "Disallow or enforce spaces inside of blocks after opening block and before closing block" },
fixable: "whitespace",
schema: [{
type: "string",
enum: ["always", "never"]
}],
messages: {
missing: "Requires a space {{location}} '{{token}}'.",
extra: "Unexpected space(s) {{location}} '{{token}}'."
}
},
defaultOptions: ["always"],
create(context, [whenToApplyOption]) {
const sourceCode = context.sourceCode;
const always = whenToApplyOption !== "never";
const messageId = always ? "missing" : "extra";
function getOpenBrace(node) {
return sourceCode.getFirstToken(node, { filter: (token) => (0, ast_exports.isOpeningBraceToken)(token) });
}
function isValid(left, right) {
return !(0, ast_exports.isTokenOnSameLine)(left, right) || sourceCode.isSpaceBetween(left, right) === always;
}
function checkSpacingInsideBraces(node) {
const openBrace = getOpenBrace(node);
const closeBrace = sourceCode.getLastToken(node);
const firstToken = sourceCode.getTokenAfter(openBrace, { includeComments: true });
const lastToken = sourceCode.getTokenBefore(closeBrace, { includeComments: true });
if (!(0, ast_exports.isOpeningBraceToken)(openBrace) || !(0, ast_exports.isClosingBraceToken)(closeBrace) || firstToken === closeBrace) return;
if (!always && firstToken.type === AST_TOKEN_TYPES.Line) return;
if (!isValid(openBrace, firstToken)) {
let loc = openBrace.loc;
if (messageId === "extra") loc = {
start: openBrace.loc.end,
end: firstToken.loc.start
};
context.report({
node,
loc,
messageId,
data: {
location: "after",
token: openBrace.value
},
fix(fixer) {
if (always) return fixer.insertTextBefore(firstToken, " ");
return fixer.removeRange([openBrace.range[1], firstToken.range[0]]);
}
});
}
if (!isValid(lastToken, closeBrace)) {
let loc = closeBrace.loc;
if (messageId === "extra") loc = {
start: lastToken.loc.end,
end: closeBrace.loc.start
};
context.report({
node,
loc,
messageId,
data: {
location: "before",
token: closeBrace.value
},
fix(fixer) {
if (always) return fixer.insertTextAfter(lastToken, " ");
return fixer.removeRange([lastToken.range[1], closeBrace.range[0]]);
}
});
}
}
return {
BlockStatement: checkSpacingInsideBraces,
StaticBlock: checkSpacingInsideBraces,
SwitchStatement: checkSpacingInsideBraces
};
}
});
export { block_spacing_default };

View File

@@ -0,0 +1,112 @@
import { STATEMENT_LIST_PARENTS, ast_exports, createRule } from "../utils.js";
var brace_style_default = createRule({
name: "brace-style",
meta: {
type: "layout",
docs: { description: "Enforce consistent brace style for blocks" },
fixable: "whitespace",
schema: [{
type: "string",
enum: [
"1tbs",
"stroustrup",
"allman"
]
}, {
type: "object",
properties: { allowSingleLine: {
type: "boolean",
default: false
} },
additionalProperties: false
}],
messages: {
nextLineOpen: "Opening curly brace does not appear on the same line as controlling statement.",
sameLineOpen: "Opening curly brace appears on the same line as controlling statement.",
blockSameLine: "Statement inside of curly braces should be on next line.",
nextLineClose: "Closing curly brace does not appear on the same line as the subsequent block.",
singleLineClose: "Closing curly brace should be on the same line as opening curly brace or on the line after the previous block.",
sameLineClose: "Closing curly brace appears on the same line as the subsequent block."
}
},
defaultOptions: ["1tbs", { allowSingleLine: false }],
create(context, optionsWithDefaults) {
const [style, { allowSingleLine } = { allowSingleLine: false }] = optionsWithDefaults;
const isAllmanStyle = style === "allman";
const sourceCode = context.sourceCode;
function removeNewlineBetween(firstToken, secondToken) {
const textRange = [firstToken.range[1], secondToken.range[0]];
const textBetween = sourceCode.text.slice(textRange[0], textRange[1]);
if (textBetween.trim()) return null;
return (fixer) => fixer.replaceTextRange(textRange, " ");
}
function validateCurlyPair(openingCurlyToken, closingCurlyToken) {
const tokenBeforeOpeningCurly = sourceCode.getTokenBefore(openingCurlyToken);
const tokenBeforeClosingCurly = sourceCode.getTokenBefore(closingCurlyToken);
const tokenAfterOpeningCurly = sourceCode.getTokenAfter(openingCurlyToken);
const singleLineException = allowSingleLine && (0, ast_exports.isTokenOnSameLine)(openingCurlyToken, closingCurlyToken);
if (!isAllmanStyle && !(0, ast_exports.isTokenOnSameLine)(tokenBeforeOpeningCurly, openingCurlyToken)) context.report({
node: openingCurlyToken,
messageId: "nextLineOpen",
fix: removeNewlineBetween(tokenBeforeOpeningCurly, openingCurlyToken)
});
if (isAllmanStyle && (0, ast_exports.isTokenOnSameLine)(tokenBeforeOpeningCurly, openingCurlyToken) && !singleLineException) context.report({
node: openingCurlyToken,
messageId: "sameLineOpen",
fix: (fixer) => fixer.insertTextBefore(openingCurlyToken, "\n")
});
if ((0, ast_exports.isTokenOnSameLine)(openingCurlyToken, tokenAfterOpeningCurly) && tokenAfterOpeningCurly !== closingCurlyToken && !singleLineException) context.report({
node: openingCurlyToken,
messageId: "blockSameLine",
fix: (fixer) => fixer.insertTextAfter(openingCurlyToken, "\n")
});
if ((0, ast_exports.isTokenOnSameLine)(tokenBeforeClosingCurly, closingCurlyToken) && tokenBeforeClosingCurly !== openingCurlyToken && !singleLineException) context.report({
node: closingCurlyToken,
messageId: "singleLineClose",
fix: (fixer) => fixer.insertTextBefore(closingCurlyToken, "\n")
});
}
function validateCurlyBeforeKeyword(curlyToken) {
const keywordToken = sourceCode.getTokenAfter(curlyToken);
if (style === "1tbs" && !(0, ast_exports.isTokenOnSameLine)(curlyToken, keywordToken)) context.report({
node: curlyToken,
messageId: "nextLineClose",
fix: removeNewlineBetween(curlyToken, keywordToken)
});
if (style !== "1tbs" && (0, ast_exports.isTokenOnSameLine)(curlyToken, keywordToken)) context.report({
node: curlyToken,
messageId: "sameLineClose",
fix: (fixer) => fixer.insertTextAfter(curlyToken, "\n")
});
}
return {
BlockStatement(node) {
if (!STATEMENT_LIST_PARENTS.has(node.parent.type)) validateCurlyPair(sourceCode.getFirstToken(node), sourceCode.getLastToken(node));
},
StaticBlock(node) {
validateCurlyPair(sourceCode.getFirstToken(node, { skip: 1 }), sourceCode.getLastToken(node));
},
ClassBody(node) {
validateCurlyPair(sourceCode.getFirstToken(node), sourceCode.getLastToken(node));
},
SwitchStatement(node) {
const closingCurly = sourceCode.getLastToken(node);
const openingCurly = sourceCode.getTokenBefore(node.cases.length ? node.cases[0] : closingCurly);
validateCurlyPair(openingCurly, closingCurly);
},
IfStatement(node) {
if (node.consequent.type === "BlockStatement" && node.alternate) validateCurlyBeforeKeyword(sourceCode.getLastToken(node.consequent));
},
TryStatement(node) {
validateCurlyBeforeKeyword(sourceCode.getLastToken(node.block));
if (node.handler && node.finalizer) validateCurlyBeforeKeyword(sourceCode.getLastToken(node.handler.body));
},
TSModuleBlock(node) {
const openingCurly = sourceCode.getFirstToken(node);
const closingCurly = sourceCode.getLastToken(node);
validateCurlyPair(openingCurly, closingCurly);
}
};
}
});
export { brace_style_default };

View File

@@ -0,0 +1,284 @@
import { AST_NODE_TYPES, ast_exports, createRule, getNextLocation } from "../utils.js";
const OPTION_VALUE_SCHEME = [
"always-multiline",
"always",
"never",
"only-multiline"
];
var comma_dangle_default = createRule({
name: "comma-dangle",
meta: {
type: "layout",
docs: { description: "Require or disallow trailing commas" },
schema: {
$defs: {
value: {
type: "string",
enum: OPTION_VALUE_SCHEME
},
valueWithIgnore: {
type: "string",
enum: [...OPTION_VALUE_SCHEME, "ignore"]
}
},
type: "array",
items: [{ oneOf: [{ $ref: "#/$defs/value" }, {
type: "object",
properties: {
arrays: { $ref: "#/$defs/valueWithIgnore" },
objects: { $ref: "#/$defs/valueWithIgnore" },
imports: { $ref: "#/$defs/valueWithIgnore" },
exports: { $ref: "#/$defs/valueWithIgnore" },
functions: { $ref: "#/$defs/valueWithIgnore" },
importAttributes: { $ref: "#/$defs/valueWithIgnore" },
dynamicImports: { $ref: "#/$defs/valueWithIgnore" },
enums: { $ref: "#/$defs/valueWithIgnore" },
generics: { $ref: "#/$defs/valueWithIgnore" },
tuples: { $ref: "#/$defs/valueWithIgnore" }
},
additionalProperties: false
}] }],
additionalItems: false
},
fixable: "code",
messages: {
unexpected: "Unexpected trailing comma.",
missing: "Missing trailing comma."
}
},
defaultOptions: ["never"],
create(context, [options]) {
function normalizeOptions(options$1 = {}, ecmaVersion$1) {
const DEFAULT_OPTION_VALUE = "never";
if (typeof options$1 === "string") return {
arrays: options$1,
objects: options$1,
imports: options$1,
exports: options$1,
functions: !ecmaVersion$1 || ecmaVersion$1 === "latest" ? options$1 : ecmaVersion$1 < 2017 ? "ignore" : options$1,
importAttributes: options$1,
dynamicImports: !ecmaVersion$1 || ecmaVersion$1 === "latest" ? options$1 : ecmaVersion$1 < 2025 ? "ignore" : options$1,
enums: options$1,
generics: options$1,
tuples: options$1
};
return {
arrays: options$1.arrays ?? DEFAULT_OPTION_VALUE,
objects: options$1.objects ?? DEFAULT_OPTION_VALUE,
imports: options$1.imports ?? DEFAULT_OPTION_VALUE,
exports: options$1.exports ?? DEFAULT_OPTION_VALUE,
functions: options$1.functions ?? DEFAULT_OPTION_VALUE,
importAttributes: options$1.importAttributes ?? DEFAULT_OPTION_VALUE,
dynamicImports: options$1.dynamicImports ?? DEFAULT_OPTION_VALUE,
enums: options$1.enums ?? DEFAULT_OPTION_VALUE,
generics: options$1.generics ?? DEFAULT_OPTION_VALUE,
tuples: options$1.tuples ?? DEFAULT_OPTION_VALUE
};
}
const ecmaVersion = context?.languageOptions?.ecmaVersion ?? context.parserOptions.ecmaVersion;
const normalizedOptions = normalizeOptions(options, ecmaVersion);
const isTSX = context.parserOptions?.ecmaFeatures?.jsx && context.filename?.endsWith(".tsx");
const sourceCode = context.sourceCode;
const closeBraces = [
"}",
"]",
")",
">"
];
const predicate = {
"always": forceTrailingComma,
"always-multiline": forceTrailingCommaIfMultiline,
"only-multiline": allowTrailingCommaIfMultiline,
"never": forbidTrailingComma,
"ignore": () => {}
};
function last(nodes) {
if (!nodes) return null;
return nodes[nodes.length - 1] ?? null;
}
function getTrailingToken(info) {
switch (info.node.type) {
case "ObjectExpression":
case "ArrayExpression":
case "CallExpression":
case "NewExpression":
case "ImportExpression": return sourceCode.getLastToken(info.node, 1);
default: {
const lastItem = info.lastItem;
if (!lastItem) return null;
const nextToken = sourceCode.getTokenAfter(lastItem);
if ((0, ast_exports.isCommaToken)(nextToken)) return nextToken;
return sourceCode.getLastToken(lastItem);
}
}
}
function isMultiline(info) {
const lastItem = info.lastItem;
if (!lastItem) return false;
const penultimateToken = getTrailingToken(info);
if (!penultimateToken) return false;
const lastToken = sourceCode.getTokenAfter(penultimateToken);
if (!lastToken) return false;
return lastToken.loc.end.line !== penultimateToken.loc.end.line;
}
function isTrailingCommaAllowed(lastItem) {
return lastItem.type !== "RestElement";
}
function forbidTrailingComma(info) {
if (isTSX && info.node.type === AST_NODE_TYPES.TSTypeParameterDeclaration && info.node.params.length === 1) return;
const lastItem = info.lastItem;
if (!lastItem) return;
const trailingToken = getTrailingToken(info);
if (trailingToken && (0, ast_exports.isCommaToken)(trailingToken)) context.report({
node: lastItem,
loc: trailingToken.loc,
messageId: "unexpected",
*fix(fixer) {
yield fixer.remove(trailingToken);
yield fixer.insertTextBefore(sourceCode.getTokenBefore(trailingToken), "");
yield fixer.insertTextAfter(sourceCode.getTokenAfter(trailingToken), "");
}
});
}
function forceTrailingComma(info) {
const lastItem = info.lastItem;
if (!lastItem) return;
if (!isTrailingCommaAllowed(lastItem)) {
forbidTrailingComma(info);
return;
}
const trailingToken = getTrailingToken(info);
if (!trailingToken || trailingToken.value === ",") return;
const nextToken = sourceCode.getTokenAfter(trailingToken);
if (!nextToken || !closeBraces.includes(nextToken.value)) return;
context.report({
node: lastItem,
loc: {
start: trailingToken.loc.end,
end: getNextLocation(sourceCode, trailingToken.loc.end)
},
messageId: "missing",
*fix(fixer) {
yield fixer.insertTextAfter(trailingToken, ",");
yield fixer.insertTextBefore(trailingToken, "");
yield fixer.insertTextAfter(sourceCode.getTokenAfter(trailingToken), "");
}
});
}
function allowTrailingCommaIfMultiline(info) {
if (!isMultiline(info)) forbidTrailingComma(info);
}
function forceTrailingCommaIfMultiline(info) {
if (isMultiline(info)) forceTrailingComma(info);
else forbidTrailingComma(info);
}
return {
ObjectExpression: (node) => {
predicate[normalizedOptions.objects]({
node,
lastItem: last(node.properties)
});
},
ObjectPattern: (node) => {
predicate[normalizedOptions.objects]({
node,
lastItem: last(node.properties)
});
},
ArrayExpression: (node) => {
predicate[normalizedOptions.arrays]({
node,
lastItem: last(node.elements)
});
},
ArrayPattern: (node) => {
predicate[normalizedOptions.arrays]({
node,
lastItem: last(node.elements)
});
},
ImportDeclaration: (node) => {
const lastSpecifier = last(node.specifiers);
if (lastSpecifier?.type === "ImportSpecifier") predicate[normalizedOptions.imports]({
node,
lastItem: lastSpecifier
});
predicate[normalizedOptions.importAttributes]({
node,
lastItem: last(node.attributes)
});
},
ExportNamedDeclaration: (node) => {
predicate[normalizedOptions.exports]({
node,
lastItem: last(node.specifiers)
});
predicate[normalizedOptions.importAttributes]({
node,
lastItem: last(node.attributes)
});
},
ExportAllDeclaration: (node) => {
predicate[normalizedOptions.importAttributes]({
node,
lastItem: last(node.attributes)
});
},
FunctionDeclaration: (node) => {
predicate[normalizedOptions.functions]({
node,
lastItem: last(node.params)
});
},
FunctionExpression: (node) => {
predicate[normalizedOptions.functions]({
node,
lastItem: last(node.params)
});
},
ArrowFunctionExpression: (node) => {
predicate[normalizedOptions.functions]({
node,
lastItem: last(node.params)
});
},
CallExpression: (node) => {
predicate[normalizedOptions.functions]({
node,
lastItem: last(node.arguments)
});
},
NewExpression: (node) => {
predicate[normalizedOptions.functions]({
node,
lastItem: last(node.arguments)
});
},
ImportExpression: (node) => {
predicate[normalizedOptions.dynamicImports]({
node,
lastItem: node.options ?? node.source
});
},
TSEnumDeclaration(node) {
predicate[normalizedOptions.enums]({
node,
lastItem: last(node.body?.members ?? node.members)
});
},
TSTypeParameterDeclaration(node) {
predicate[normalizedOptions.generics]({
node,
lastItem: last(node.params)
});
},
TSTupleType(node) {
predicate[normalizedOptions.tuples]({
node,
lastItem: last(node.elementTypes)
});
}
};
}
});
export { comma_dangle_default };

View File

@@ -0,0 +1,84 @@
import { AST_TOKEN_TYPES, ast_exports, createRule } from "../utils.js";
var comma_spacing_default = createRule({
name: "comma-spacing",
meta: {
type: "layout",
docs: { description: "Enforce consistent spacing before and after commas" },
fixable: "whitespace",
schema: [{
type: "object",
properties: {
before: {
type: "boolean",
default: false
},
after: {
type: "boolean",
default: true
}
},
additionalProperties: false
}],
messages: {
unexpected: `There should be no space {{loc}} ','.`,
missing: `A space is required {{loc}} ','.`
}
},
defaultOptions: [{
before: false,
after: true
}],
create(context, [options = {}]) {
const { before: spaceBefore, after: spaceAfter } = options;
const sourceCode = context.sourceCode;
const tokensAndComments = sourceCode.tokensAndComments;
const ignoredTokens = /* @__PURE__ */ new Set();
function addNullElementsToIgnoreList(node) {
let previousToken = sourceCode.getFirstToken(node);
for (const element of node.elements) {
let token;
if (element == null) {
token = sourceCode.getTokenAfter(previousToken);
if (token && (0, ast_exports.isCommaToken)(token)) ignoredTokens.add(token);
} else token = sourceCode.getTokenAfter(element);
previousToken = token;
}
}
function addTypeParametersTrailingCommaToIgnoreList(node) {
const paramLength = node.params.length;
if (paramLength) {
const param = node.params[paramLength - 1];
const afterToken = sourceCode.getTokenAfter(param);
if (afterToken && (0, ast_exports.isCommaToken)(afterToken)) ignoredTokens.add(afterToken);
}
}
function validateCommaSpacing(commaToken, prevToken, nextToken) {
if (prevToken && (0, ast_exports.isTokenOnSameLine)(prevToken, commaToken) && spaceBefore !== sourceCode.isSpaceBetween(prevToken, commaToken)) context.report({
node: commaToken,
data: { loc: "before" },
messageId: spaceBefore ? "missing" : "unexpected",
fix: (fixer) => spaceBefore ? fixer.insertTextBefore(commaToken, " ") : fixer.replaceTextRange([prevToken.range[1], commaToken.range[0]], "")
});
if (nextToken && (0, ast_exports.isTokenOnSameLine)(commaToken, nextToken) && !(0, ast_exports.isClosingParenToken)(nextToken) && !(0, ast_exports.isClosingBracketToken)(nextToken) && !(0, ast_exports.isClosingBraceToken)(nextToken) && !(!spaceAfter && nextToken.type === AST_TOKEN_TYPES.Line) && spaceAfter !== sourceCode.isSpaceBetween(commaToken, nextToken)) context.report({
node: commaToken,
data: { loc: "after" },
messageId: spaceAfter ? "missing" : "unexpected",
fix: (fixer) => spaceAfter ? fixer.insertTextAfter(commaToken, " ") : fixer.replaceTextRange([commaToken.range[1], nextToken.range[0]], "")
});
}
return {
"TSTypeParameterDeclaration": addTypeParametersTrailingCommaToIgnoreList,
"ArrayExpression": addNullElementsToIgnoreList,
"ArrayPattern": addNullElementsToIgnoreList,
"Program:exit": function() {
tokensAndComments.forEach((token, i) => {
if (!(0, ast_exports.isCommaToken)(token)) return;
const prevToken = tokensAndComments[i - 1];
const nextToken = tokensAndComments[i + 1];
validateCommaSpacing(token, (0, ast_exports.isCommaToken)(prevToken) || ignoredTokens.has(token) ? null : prevToken, nextToken && (0, ast_exports.isCommaToken)(nextToken) || ignoredTokens.has(token) ? null : nextToken);
});
}
};
}
});
export { comma_spacing_default };

View File

@@ -0,0 +1,178 @@
import { ast_exports, createRule } from "../utils.js";
var comma_style_default = createRule({
name: "comma-style",
meta: {
type: "layout",
docs: { description: "Enforce consistent comma style" },
fixable: "code",
schema: [{
type: "string",
enum: ["first", "last"]
}, {
type: "object",
properties: { exceptions: {
type: "object",
additionalProperties: { type: "boolean" }
} },
additionalProperties: false
}],
messages: {
unexpectedLineBeforeAndAfterComma: "Bad line breaking before and after ','.",
expectedCommaFirst: "',' should be placed first.",
expectedCommaLast: "',' should be placed last."
}
},
create(context) {
const style = context.options[0] || "last";
const sourceCode = context.sourceCode;
const exceptions = {};
if (context.options.length === 2 && Object.prototype.hasOwnProperty.call(context.options[1], "exceptions")) {
context.options[1] ??= { exceptions: {} };
const rawExceptions = context.options[1].exceptions;
const keys = Object.keys(rawExceptions);
for (let i = 0; i < keys.length; i++) exceptions[keys[i]] = rawExceptions[keys[i]];
}
function getReplacedText(styleType, text) {
switch (styleType) {
case "between": return `,${text.replace(ast_exports.LINEBREAK_MATCHER, "")}`;
case "first": return `${text},`;
case "last": return `,${text}`;
default: return "";
}
}
function getFixerFunction(styleType, tokenBeforeComma, commaToken, tokenAfterComma) {
const text = sourceCode.text.slice(tokenBeforeComma.range[1], commaToken.range[0]) + sourceCode.text.slice(commaToken.range[1], tokenAfterComma.range[0]);
const range = [tokenBeforeComma.range[1], tokenAfterComma.range[0]];
return function(fixer) {
return fixer.replaceTextRange(range, getReplacedText(styleType, text));
};
}
function validateCommaItemSpacing(tokenBeforeComma, commaToken, tokenAfterComma) {
if ((0, ast_exports.isTokenOnSameLine)(commaToken, tokenAfterComma) && (0, ast_exports.isTokenOnSameLine)(tokenBeforeComma, commaToken)) {} else if (!(0, ast_exports.isTokenOnSameLine)(commaToken, tokenAfterComma) && !(0, ast_exports.isTokenOnSameLine)(tokenBeforeComma, commaToken)) {
const comment = sourceCode.getCommentsAfter(commaToken)[0];
const styleType = comment && comment.type === "Block" && (0, ast_exports.isTokenOnSameLine)(commaToken, comment) ? style : "between";
context.report({
node: commaToken,
messageId: "unexpectedLineBeforeAndAfterComma",
fix: getFixerFunction(styleType, tokenBeforeComma, commaToken, tokenAfterComma)
});
} else if (style === "first" && !(0, ast_exports.isTokenOnSameLine)(commaToken, tokenAfterComma)) context.report({
node: commaToken,
messageId: "expectedCommaFirst",
fix: getFixerFunction(style, tokenBeforeComma, commaToken, tokenAfterComma)
});
else if (style === "last" && (0, ast_exports.isTokenOnSameLine)(commaToken, tokenAfterComma)) context.report({
node: commaToken,
messageId: "expectedCommaLast",
fix: getFixerFunction(style, tokenBeforeComma, commaToken, tokenAfterComma)
});
}
function extractCommaTokens(node, items) {
if (items.length === 0) return [];
const definedItems = items.filter((item) => Boolean(item));
if (definedItems.length === 0) return sourceCode.getTokens(node).filter(ast_exports.isCommaToken);
const commaTokens = [];
const firstItem = definedItems[0];
let prevToken = sourceCode.getTokenBefore(firstItem);
while (prevToken && node.range[0] <= prevToken.range[0]) {
if ((0, ast_exports.isCommaToken)(prevToken)) commaTokens.unshift(prevToken);
else if ((0, ast_exports.isNotOpeningParenToken)(prevToken)) break;
prevToken = sourceCode.getTokenBefore(prevToken);
}
let prevItem = null;
for (const item of definedItems) {
if (prevItem) commaTokens.push(...sourceCode.getTokensBetween(prevItem, item).filter(ast_exports.isCommaToken));
const tokenLastItem = sourceCode.getLastToken(item);
if (tokenLastItem && (0, ast_exports.isCommaToken)(tokenLastItem)) commaTokens.push(tokenLastItem);
prevItem = item;
}
let nextToken = sourceCode.getTokenAfter(prevItem);
while (nextToken && nextToken.range[1] <= node.range[1]) {
if ((0, ast_exports.isCommaToken)(nextToken)) commaTokens.push(nextToken);
else if ((0, ast_exports.isNotClosingParenToken)(nextToken)) break;
nextToken = sourceCode.getTokenAfter(nextToken);
}
return commaTokens;
}
function validateComma(node, items) {
const commaTokens = extractCommaTokens(node, items);
commaTokens.forEach((commaToken) => {
const tokenBeforeComma = sourceCode.getTokenBefore(commaToken);
const tokenAfterComma = sourceCode.getTokenAfter(commaToken);
if ((0, ast_exports.isOpeningBracketToken)(tokenBeforeComma)) return;
if ((0, ast_exports.isCommaToken)(tokenBeforeComma) && (0, ast_exports.isOpeningBracketToken)(sourceCode.getTokenBefore(tokenBeforeComma, ast_exports.isNotCommaToken))) return;
if ((0, ast_exports.isCommaToken)(tokenAfterComma) && !(0, ast_exports.isTokenOnSameLine)(commaToken, tokenAfterComma)) return;
validateCommaItemSpacing(tokenBeforeComma, commaToken, tokenAfterComma);
});
}
const nodes = {};
if (!exceptions.VariableDeclaration) nodes.VariableDeclaration = (node) => validateComma(node, node.declarations);
if (!exceptions.ObjectExpression) nodes.ObjectExpression = validateObjectProperties;
if (!exceptions.ObjectPattern) nodes.ObjectPattern = validateObjectProperties;
if (!exceptions.ArrayExpression) nodes.ArrayExpression = validateArrayElements;
if (!exceptions.ArrayPattern) nodes.ArrayPattern = validateArrayElements;
if (!exceptions.FunctionDeclaration) nodes.FunctionDeclaration = validateFunctionParams;
if (!exceptions.FunctionExpression) nodes.FunctionExpression = validateFunctionParams;
if (!exceptions.ArrowFunctionExpression) nodes.ArrowFunctionExpression = validateFunctionParams;
if (!exceptions.CallExpression) nodes.CallExpression = validateCallArguments;
if (!exceptions.ImportDeclaration) nodes.ImportDeclaration = (node) => {
validateComma(node, node.specifiers);
visitImportAttributes(node);
};
if (!exceptions.NewExpression) nodes.NewExpression = validateCallArguments;
if (!exceptions.ExportAllDeclaration) nodes.ExportAllDeclaration = visitImportAttributes;
if (!exceptions.ExportNamedDeclaration) nodes.ExportNamedDeclaration = (node) => {
validateComma(node, node.specifiers);
visitImportAttributes(node);
};
if (!exceptions.ImportExpression) nodes.ImportExpression = (node) => {
validateComma(node, [node.source, node.options ?? null]);
};
if (!exceptions.SequenceExpression) nodes.SequenceExpression = (node) => validateComma(node, node.expressions);
if (!exceptions.ClassDeclaration) nodes.ClassDeclaration = visitClassImplements;
if (!exceptions.ClassExpression) nodes.ClassExpression = visitClassImplements;
if (!exceptions.TSDeclareFunction) nodes.TSDeclareFunction = validateFunctionParams;
if (!exceptions.TSFunctionType) nodes.TSFunctionType = validateFunctionParams;
if (!exceptions.TSConstructorType) nodes.TSConstructorType = validateFunctionParams;
if (!exceptions.TSEmptyBodyFunctionExpression) nodes.TSEmptyBodyFunctionExpression = validateFunctionParams;
if (!exceptions.TSMethodSignature) nodes.TSMethodSignature = validateFunctionParams;
if (!exceptions.TSCallSignatureDeclaration) nodes.TSCallSignatureDeclaration = validateFunctionParams;
if (!exceptions.TSConstructSignatureDeclaration) nodes.TSConstructSignatureDeclaration = validateFunctionParams;
if (!exceptions.TSTypeParameterDeclaration) nodes.TSTypeParameterDeclaration = validateTypeParams;
if (!exceptions.TSTypeParameterInstantiation) nodes.TSTypeParameterInstantiation = validateTypeParams;
if (!exceptions.TSEnumBody) nodes.TSEnumBody = visitMembers;
if (!exceptions.TSTypeLiteral) nodes.TSTypeLiteral = visitMembers;
if (!exceptions.TSIndexSignature) nodes.TSIndexSignature = (node) => validateComma(node, node.parameters);
if (!exceptions.TSInterfaceDeclaration) nodes.TSInterfaceDeclaration = (node) => validateComma(node, node.extends);
if (!exceptions.TSInterfaceBody) nodes.TSInterfaceBody = (node) => validateComma(node, node.body);
if (!exceptions.TSTupleType) nodes.TSTupleType = (node) => validateComma(node, node.elementTypes);
return nodes;
function validateObjectProperties(node) {
validateComma(node, node.properties);
}
function validateArrayElements(node) {
validateComma(node, node.elements);
}
function validateFunctionParams(node) {
validateComma(node, node.params);
}
function validateCallArguments(node) {
validateComma(node, node.arguments);
}
function visitImportAttributes(node) {
if (!node.attributes) return;
validateComma(node, node.attributes);
}
function visitClassImplements(node) {
if (!node.implements) return;
validateComma(node, node.implements);
}
function visitMembers(node) {
validateComma(node, node.members);
}
function validateTypeParams(node) {
validateComma(node, node.params);
}
}
});
export { comma_style_default };

View File

@@ -0,0 +1,112 @@
import { ast_exports, createRule } from "../utils.js";
var computed_property_spacing_default = createRule({
name: "computed-property-spacing",
meta: {
type: "layout",
docs: { description: "Enforce consistent spacing inside computed property brackets" },
fixable: "whitespace",
schema: [{
type: "string",
enum: ["always", "never"]
}, {
type: "object",
properties: { enforceForClassMembers: {
type: "boolean",
default: true
} },
additionalProperties: false
}],
messages: {
unexpectedSpaceBefore: "There should be no space before '{{tokenValue}}'.",
unexpectedSpaceAfter: "There should be no space after '{{tokenValue}}'.",
missingSpaceBefore: "A space is required before '{{tokenValue}}'.",
missingSpaceAfter: "A space is required after '{{tokenValue}}'."
}
},
create(context) {
const sourceCode = context.sourceCode;
const propertyNameMustBeSpaced = context.options[0] === "always";
const enforceForClassMembers = !context.options[1] || context.options[1].enforceForClassMembers;
function reportNoBeginningSpace(node, token, tokenAfter) {
context.report({
node,
loc: {
start: token.loc.end,
end: tokenAfter.loc.start
},
messageId: "unexpectedSpaceAfter",
data: { tokenValue: token.value },
fix(fixer) {
return fixer.removeRange([token.range[1], tokenAfter.range[0]]);
}
});
}
function reportNoEndingSpace(node, token, tokenBefore) {
context.report({
node,
loc: {
start: tokenBefore.loc.end,
end: token.loc.start
},
messageId: "unexpectedSpaceBefore",
data: { tokenValue: token.value },
fix(fixer) {
return fixer.removeRange([tokenBefore.range[1], token.range[0]]);
}
});
}
function reportRequiredBeginningSpace(node, token) {
context.report({
node,
loc: token.loc,
messageId: "missingSpaceAfter",
data: { tokenValue: token.value },
fix(fixer) {
return fixer.insertTextAfter(token, " ");
}
});
}
function reportRequiredEndingSpace(node, token) {
context.report({
node,
loc: token.loc,
messageId: "missingSpaceBefore",
data: { tokenValue: token.value },
fix(fixer) {
return fixer.insertTextBefore(token, " ");
}
});
}
function checkSpacing(propertyName) {
return function(node) {
if (!node.computed) return;
const property = node[propertyName];
const before = sourceCode.getTokenBefore(property, ast_exports.isOpeningBracketToken);
const first = sourceCode.getTokenAfter(before, { includeComments: true });
const after = sourceCode.getTokenAfter(property, ast_exports.isClosingBracketToken);
const last = sourceCode.getTokenBefore(after, { includeComments: true });
if ((0, ast_exports.isTokenOnSameLine)(before, first)) {
if (propertyNameMustBeSpaced) {
if (!sourceCode.isSpaceBetween(before, first) && (0, ast_exports.isTokenOnSameLine)(before, first)) reportRequiredBeginningSpace(node, before);
} else if (sourceCode.isSpaceBetween(before, first)) reportNoBeginningSpace(node, before, first);
}
if ((0, ast_exports.isTokenOnSameLine)(last, after)) {
if (propertyNameMustBeSpaced) {
if (!sourceCode.isSpaceBetween(last, after) && (0, ast_exports.isTokenOnSameLine)(last, after)) reportRequiredEndingSpace(node, after);
} else if (sourceCode.isSpaceBetween(last, after)) reportNoEndingSpace(node, after, last);
}
};
}
const listeners = {
Property: checkSpacing("key"),
MemberExpression: checkSpacing("property")
};
if (enforceForClassMembers) {
listeners.MethodDefinition = checkSpacing("key");
listeners.PropertyDefinition = checkSpacing("key");
listeners.AccessorProperty = checkSpacing("key");
}
return listeners;
}
});
export { computed_property_spacing_default };

View File

@@ -0,0 +1,214 @@
import { ast_exports, createRule } from "../utils.js";
const commonProperties = {
multiline: { type: "boolean" },
minElements: {
type: "integer",
minimum: 0
},
consistent: { type: "boolean" }
};
const optionValueSchema = { oneOf: [{
type: "string",
enum: ["always", "never"]
}, {
type: "object",
properties: commonProperties,
additionalProperties: false
}] };
var Specialization = /* @__PURE__ */ function(Specialization$1) {
Specialization$1["IfStatementConsequent"] = "IfStatementConsequent";
Specialization$1["IfStatementAlternative"] = "IfStatementAlternative";
Specialization$1["DoWhileStatement"] = "DoWhileStatement";
Specialization$1["ForInStatement"] = "ForInStatement";
Specialization$1["ForOfStatement"] = "ForOfStatement";
Specialization$1["ForStatement"] = "ForStatement";
Specialization$1["WhileStatement"] = "WhileStatement";
Specialization$1["SwitchStatement"] = "SwitchStatement";
Specialization$1["SwitchCase"] = "SwitchCase";
Specialization$1["TryStatementBlock"] = "TryStatementBlock";
Specialization$1["TryStatementHandler"] = "TryStatementHandler";
Specialization$1["TryStatementFinalizer"] = "TryStatementFinalizer";
Specialization$1["BlockStatement"] = "BlockStatement";
Specialization$1["ArrowFunctionExpression"] = "ArrowFunctionExpression";
Specialization$1["FunctionDeclaration"] = "FunctionDeclaration";
Specialization$1["FunctionExpression"] = "FunctionExpression";
Specialization$1["Property"] = "Property";
Specialization$1["ClassBody"] = "ClassBody";
Specialization$1["StaticBlock"] = "StaticBlock";
Specialization$1["WithStatement"] = "WithStatement";
Specialization$1["TSModuleBlock"] = "TSModuleBlock";
return Specialization$1;
}(Specialization || {});
const presets = {
default: {
multiline: false,
minElements: Number.POSITIVE_INFINITY,
consistent: true
},
always: {
multiline: false,
minElements: 0,
consistent: false
},
never: {
multiline: false,
minElements: Number.POSITIVE_INFINITY,
consistent: false
}
};
function normalizeOptionValue(value) {
if (value === "always") return presets.always;
if (value === "never") return presets.never;
if (value) return {
consistent: !!value.consistent,
minElements: value.minElements ?? Number.POSITIVE_INFINITY,
multiline: !!value.multiline
};
return presets.default;
}
function normalizeOptions(options) {
const value = normalizeOptionValue(options);
return Object.fromEntries(Object.entries(Specialization).map(([k]) => [k, typeof options === "object" && options != null && k in options ? normalizeOptionValue(options[k]) : value]));
}
var curly_newline_default = createRule({
name: "curly-newline",
meta: {
type: "layout",
docs: { description: "Enforce consistent line breaks after opening and before closing braces" },
fixable: "whitespace",
schema: [{ oneOf: [{
type: "string",
enum: ["always", "never"]
}, {
type: "object",
properties: {
...Object.fromEntries(Object.entries(Specialization).map(([k]) => [k, optionValueSchema])),
...commonProperties
},
additionalProperties: false
}] }],
messages: {
unexpectedLinebreakBeforeClosingBrace: "Unexpected line break before this closing brace.",
unexpectedLinebreakAfterOpeningBrace: "Unexpected line break after this opening brace.",
expectedLinebreakBeforeClosingBrace: "Expected a line break before this closing brace.",
expectedLinebreakAfterOpeningBrace: "Expected a line break after this opening brace."
}
},
create(context) {
const sourceCode = context.sourceCode;
const normalizedOptions = normalizeOptions(context.options[0]);
function check(node, specialization) {
const options = normalizedOptions[specialization];
let openBrace;
let closeBrace;
let elementCount;
switch (node.type) {
case "SwitchStatement":
closeBrace = sourceCode.getLastToken(node);
openBrace = sourceCode.getTokenBefore(node.cases.length ? node.cases[0] : closeBrace);
elementCount = node.cases.length;
break;
case "StaticBlock":
openBrace = sourceCode.getFirstToken(node, (token) => token.value === "{");
closeBrace = sourceCode.getLastToken(node);
elementCount = node.body.length;
break;
default:
openBrace = sourceCode.getFirstToken(node);
closeBrace = sourceCode.getLastToken(node);
elementCount = node.body.length;
}
let first = sourceCode.getTokenAfter(openBrace, { includeComments: true });
let last = sourceCode.getTokenBefore(closeBrace, { includeComments: true });
const needsLineBreaks = elementCount >= options.minElements || options.multiline && elementCount > 0 && !(0, ast_exports.isTokenOnSameLine)(last, first);
const hasCommentsFirstToken = (0, ast_exports.isCommentToken)(first);
const hasCommentsLastToken = (0, ast_exports.isCommentToken)(last);
first = sourceCode.getTokenAfter(openBrace);
last = sourceCode.getTokenBefore(closeBrace);
if (needsLineBreaks) {
if ((0, ast_exports.isTokenOnSameLine)(openBrace, first)) context.report({
messageId: "expectedLinebreakAfterOpeningBrace",
node,
loc: openBrace.loc,
fix(fixer) {
if (hasCommentsFirstToken) return null;
return fixer.insertTextAfter(openBrace, "\n");
}
});
if ((0, ast_exports.isTokenOnSameLine)(last, closeBrace)) context.report({
messageId: "expectedLinebreakBeforeClosingBrace",
node,
loc: closeBrace.loc,
fix(fixer) {
if (hasCommentsLastToken) return null;
return fixer.insertTextBefore(closeBrace, "\n");
}
});
} else {
const consistent = options.consistent;
const hasLineBreakBetweenOpenBraceAndFirst = !(0, ast_exports.isTokenOnSameLine)(openBrace, first);
const hasLineBreakBetweenCloseBraceAndLast = !(0, ast_exports.isTokenOnSameLine)(last, closeBrace);
if (!consistent && hasLineBreakBetweenOpenBraceAndFirst || consistent && hasLineBreakBetweenOpenBraceAndFirst && !hasLineBreakBetweenCloseBraceAndLast) context.report({
messageId: "unexpectedLinebreakAfterOpeningBrace",
node,
loc: openBrace.loc,
fix(fixer) {
if (hasCommentsFirstToken) return null;
return fixer.removeRange([openBrace.range[1], first.range[0]]);
}
});
if (!consistent && hasLineBreakBetweenCloseBraceAndLast || consistent && !hasLineBreakBetweenOpenBraceAndFirst && hasLineBreakBetweenCloseBraceAndLast) context.report({
messageId: "unexpectedLinebreakBeforeClosingBrace",
node,
loc: closeBrace.loc,
fix(fixer) {
if (hasCommentsLastToken) return null;
return fixer.removeRange([last.range[1], closeBrace.range[0]]);
}
});
}
}
function checkBlockLike(node) {
check(node, node.type);
}
return {
BlockStatement(node) {
const { parent } = node;
switch (parent.type) {
case "DoWhileStatement":
case "ForInStatement":
case "ForOfStatement":
case "ForStatement":
case "WhileStatement":
case "ArrowFunctionExpression":
case "FunctionDeclaration":
case "WithStatement":
check(node, parent.type);
break;
case "FunctionExpression":
if (parent.parent.type === "Property" && parent.parent.method) check(node, "Property");
else check(node, parent.type);
break;
case "IfStatement":
if (node === parent.consequent) check(node, "IfStatementConsequent");
if (node === parent.alternate) check(node, "IfStatementAlternative");
break;
case "TryStatement":
if (node === parent.block) check(node, "TryStatementBlock");
if (node === parent.finalizer) check(node, "TryStatementFinalizer");
break;
case "CatchClause":
check(node, "TryStatementHandler");
break;
default: if (parent.type === "SwitchCase" && parent.consequent.length === 1) check(node, "SwitchCase");
else check(node, "BlockStatement");
}
},
SwitchStatement: checkBlockLike,
ClassBody: checkBlockLike,
StaticBlock: checkBlockLike,
TSModuleBlock: checkBlockLike
};
}
});
export { curly_newline_default };

View File

@@ -0,0 +1,52 @@
import { ast_exports, createRule, isDecimalIntegerNumericToken } from "../utils.js";
var dot_location_default = createRule({
name: "dot-location",
meta: {
type: "layout",
docs: { description: "Enforce consistent newlines before and after dots" },
schema: [{
type: "string",
enum: ["object", "property"]
}],
fixable: "code",
messages: {
expectedDotAfterObject: "Expected dot to be on same line as object.",
expectedDotBeforeProperty: "Expected dot to be on same line as property."
}
},
create(context) {
const config = context.options[0];
const onObject = config === "object" || !config;
const sourceCode = context.sourceCode;
function checkDotLocation(node) {
const property = node.property;
const dotToken = sourceCode.getTokenBefore(property);
if (onObject && dotToken) {
const tokenBeforeDot = sourceCode.getTokenBefore(dotToken);
if (tokenBeforeDot && !(0, ast_exports.isTokenOnSameLine)(tokenBeforeDot, dotToken)) context.report({
node,
loc: dotToken.loc,
messageId: "expectedDotAfterObject",
*fix(fixer) {
if (dotToken.value.startsWith(".") && isDecimalIntegerNumericToken(tokenBeforeDot)) yield fixer.insertTextAfter(tokenBeforeDot, ` ${dotToken.value}`);
else yield fixer.insertTextAfter(tokenBeforeDot, dotToken.value);
yield fixer.remove(dotToken);
}
});
} else if (dotToken && !(0, ast_exports.isTokenOnSameLine)(dotToken, property)) context.report({
node,
loc: dotToken.loc,
messageId: "expectedDotBeforeProperty",
*fix(fixer) {
yield fixer.remove(dotToken);
yield fixer.insertTextBefore(property, dotToken.value);
}
});
}
function checkNode(node) {
if (node.type === "MemberExpression" && !node.computed) checkDotLocation(node);
}
return { MemberExpression: checkNode };
}
});
export { dot_location_default };

View File

@@ -0,0 +1,81 @@
import { createRule, warnDeprecation } from "../utils.js";
var eol_last_default = createRule({
name: "eol-last",
meta: {
type: "layout",
docs: { description: "Require or disallow newline at the end of files" },
fixable: "whitespace",
schema: [{
type: "string",
enum: [
"always",
"never",
"unix",
"windows"
]
}],
messages: {
missing: "Newline required at end of file but not found.",
unexpected: "Newline not allowed at end of file."
}
},
create(context) {
return { Program: function checkBadEOF(node) {
const sourceCode = context.sourceCode;
const src = sourceCode.getText();
const lastLine = sourceCode.lines[sourceCode.lines.length - 1];
const location = {
column: lastLine.length,
line: sourceCode.lines.length
};
const LF = "\n";
const CRLF = `\r${LF}`;
const endsWithNewline = src.endsWith(LF);
if (!src.length) return;
let mode = context.options[0] || "always";
let appendCRLF = false;
if (mode === "unix") {
warnDeprecation("option(\"unix\")", "\"always\" and \"@stylistic/eslint-plugin/rules/linebreak-style\"", "eol-last");
mode = "always";
}
if (mode === "windows") {
warnDeprecation("option(\"windows\")", "\"always\" and \"@stylistic/eslint-plugin/rules/linebreak-style\"", "eol-last");
mode = "always";
appendCRLF = true;
}
if (mode === "always" && !endsWithNewline) context.report({
node,
loc: location,
messageId: "missing",
fix(fixer) {
return fixer.insertTextAfterRange([0, src.length], appendCRLF ? CRLF : LF);
}
});
else if (mode === "never" && endsWithNewline) {
const secondLastLine = sourceCode.lines[sourceCode.lines.length - 2];
context.report({
node,
loc: {
start: {
line: sourceCode.lines.length - 1,
column: secondLastLine.length
},
end: {
line: sourceCode.lines.length,
column: 0
}
},
messageId: "unexpected",
fix(fixer) {
const finalEOLs = /(?:\r?\n)+$/u;
const match = finalEOLs.exec(sourceCode.text);
const start = match.index;
const end = sourceCode.text.length;
return fixer.replaceTextRange([start, end], "");
}
});
}
} };
}
});
export { eol_last_default };

View File

@@ -0,0 +1,76 @@
import { ast_exports, createRule } from "../utils.js";
var function_call_argument_newline_default = createRule({
name: "function-call-argument-newline",
meta: {
type: "layout",
docs: { description: "Enforce line breaks between arguments of a function call" },
fixable: "whitespace",
schema: [{
type: "string",
enum: [
"always",
"never",
"consistent"
]
}],
messages: {
unexpectedLineBreak: "There should be no line break here.",
missingLineBreak: "There should be a line break after this argument."
}
},
create(context) {
const sourceCode = context.sourceCode;
const checkers = {
unexpected: {
messageId: "unexpectedLineBreak",
check: (prevToken, currentToken) => !(0, ast_exports.isTokenOnSameLine)(prevToken, currentToken),
createFix: (token, tokenBefore) => (fixer) => fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], " ")
},
missing: {
messageId: "missingLineBreak",
check: (prevToken, currentToken) => (0, ast_exports.isTokenOnSameLine)(prevToken, currentToken),
createFix: (token, tokenBefore) => (fixer) => fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], "\n")
}
};
function checkArguments(argumentNodes, checker) {
for (let i = 1; i < argumentNodes.length; i++) {
const argumentNode = argumentNodes[i - 1];
const prevArgToken = sourceCode.getLastToken(argumentNode);
const currentArgToken = sourceCode.getFirstToken(argumentNodes[i]);
if (checker.check(prevArgToken, currentArgToken)) {
const tokenBefore = sourceCode.getTokenBefore(currentArgToken, { includeComments: true });
const hasLineCommentBefore = tokenBefore.type === "Line";
context.report({
node: argumentNodes[i - 1],
loc: {
start: tokenBefore.loc.end,
end: currentArgToken.loc.start
},
messageId: checker.messageId,
fix: hasLineCommentBefore ? null : checker.createFix(currentArgToken, tokenBefore)
});
}
}
}
function check(argumentNodes) {
if (argumentNodes.length < 2) return;
const option = context.options[0] || "always";
if (option === "never") checkArguments(argumentNodes, checkers.unexpected);
else if (option === "always") checkArguments(argumentNodes, checkers.missing);
else if (option === "consistent") {
const firstArgToken = sourceCode.getLastToken(argumentNodes[0]);
const secondArgToken = sourceCode.getFirstToken(argumentNodes[1]);
if ((0, ast_exports.isTokenOnSameLine)(firstArgToken, secondArgToken)) checkArguments(argumentNodes, checkers.unexpected);
else checkArguments(argumentNodes, checkers.missing);
}
}
return {
CallExpression: (node) => check(node.arguments),
NewExpression: (node) => check(node.arguments),
ImportExpression: (node) => {
if (node.options) check([node.source, node.options]);
}
};
}
});
export { function_call_argument_newline_default };

View File

@@ -0,0 +1,142 @@
import { ast_exports, createRule } from "../utils.js";
var function_call_spacing_default = createRule({
name: "function-call-spacing",
meta: {
type: "layout",
docs: { description: "Require or disallow spacing between function identifiers and their invocations" },
fixable: "whitespace",
schema: { anyOf: [{
type: "array",
items: [{
type: "string",
enum: ["never"]
}],
minItems: 0,
maxItems: 1
}, {
type: "array",
items: [{
type: "string",
enum: ["always"]
}, {
type: "object",
properties: {
allowNewlines: { type: "boolean" },
optionalChain: {
type: "object",
properties: {
before: { type: "boolean" },
after: { type: "boolean" }
},
additionalProperties: false
}
},
additionalProperties: false
}],
minItems: 0,
maxItems: 2
}] },
messages: {
unexpectedWhitespace: "Unexpected whitespace between function name and paren.",
unexpectedNewline: "Unexpected newline between function name and paren.",
missing: "Missing space between function name and paren."
}
},
defaultOptions: ["never", {}],
create(context, [option, config]) {
const sourceCode = context.sourceCode;
const text = sourceCode.getText();
const { allowNewlines = false, optionalChain = {
before: true,
after: true
} } = config;
function checkSpacing(node, leftToken, rightToken) {
const isOptionalCall = (0, ast_exports.isOptionalCallExpression)(node);
const textBetweenTokens = text.slice(leftToken.range[1], rightToken.range[0]).replace(/\/\*.*?\*\//gu, "");
const hasWhitespace = /\s/u.test(textBetweenTokens);
const hasNewline = hasWhitespace && ast_exports.LINEBREAK_MATCHER.test(textBetweenTokens);
if (option === "never") {
if (hasWhitespace) return context.report({
node,
loc: {
start: leftToken.loc.end,
end: rightToken.loc.start
},
messageId: "unexpectedWhitespace",
fix(fixer) {
if (sourceCode.commentsExistBetween(leftToken, rightToken)) return null;
if (isOptionalCall) return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], "?.");
return fixer.removeRange([leftToken.range[1], rightToken.range[0]]);
}
});
} else if (isOptionalCall) {
const { before: beforeOptionChain = true, after: afterOptionChain = true } = optionalChain;
const hasPrefixSpace = /^\s/u.test(textBetweenTokens);
const hasSuffixSpace = /\s$/u.test(textBetweenTokens);
const hasCorrectPrefixSpace = beforeOptionChain ? hasPrefixSpace : !hasPrefixSpace;
const hasCorrectSuffixSpace = afterOptionChain ? hasSuffixSpace : !hasSuffixSpace;
const hasCorrectNewline = allowNewlines || !hasNewline;
if (!hasCorrectPrefixSpace || !hasCorrectSuffixSpace || !hasCorrectNewline) {
const messageId = !hasCorrectNewline ? "unexpectedNewline" : !beforeOptionChain && hasPrefixSpace || !afterOptionChain && hasSuffixSpace ? "unexpectedWhitespace" : "missing";
context.report({
node,
loc: {
start: leftToken.loc.end,
end: rightToken.loc.start
},
messageId,
fix(fixer) {
if (sourceCode.commentsExistBetween(leftToken, rightToken)) return null;
let text$1 = textBetweenTokens;
if (!allowNewlines) {
const GLOBAL_LINEBREAK_MATCHER = new RegExp(ast_exports.LINEBREAK_MATCHER.source, "g");
text$1 = text$1.replaceAll(GLOBAL_LINEBREAK_MATCHER, " ");
}
if (!hasCorrectPrefixSpace) text$1 = beforeOptionChain ? ` ${text$1}` : text$1.trimStart();
if (!hasCorrectSuffixSpace) text$1 = afterOptionChain ? `${text$1} ` : text$1.trimEnd();
return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], text$1);
}
});
}
} else if (!hasWhitespace) context.report({
node,
loc: {
start: leftToken.loc.end,
end: rightToken.loc.start
},
messageId: "missing",
fix(fixer) {
return fixer.insertTextBefore(rightToken, " ");
}
});
else if (!allowNewlines && hasNewline) context.report({
node,
loc: {
start: leftToken.loc.end,
end: rightToken.loc.start
},
messageId: "unexpectedNewline",
fix(fixer) {
if (sourceCode.commentsExistBetween(leftToken, rightToken)) return null;
return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], " ");
}
});
}
return {
"CallExpression, NewExpression": function(node) {
const closingParenToken = sourceCode.getLastToken(node);
const lastCalleeTokenWithoutPossibleParens = sourceCode.getLastToken(node.typeArguments ?? node.callee);
const openingParenToken = sourceCode.getFirstTokenBetween(lastCalleeTokenWithoutPossibleParens, closingParenToken, ast_exports.isOpeningParenToken);
if (!openingParenToken || openingParenToken.range[1] >= node.range[1]) return;
const lastCalleeToken = sourceCode.getTokenBefore(openingParenToken, ast_exports.isNotOptionalChainPunctuator);
checkSpacing(node, lastCalleeToken, openingParenToken);
},
ImportExpression(node) {
const leftToken = sourceCode.getFirstToken(node);
const rightToken = sourceCode.getTokenAfter(leftToken);
checkSpacing(node, leftToken, rightToken);
}
};
}
});
export { function_call_spacing_default };

View File

@@ -0,0 +1,162 @@
import { ast_exports, createRule } from "../utils.js";
var function_paren_newline_default = createRule({
name: "function-paren-newline",
meta: {
type: "layout",
docs: { description: "Enforce consistent line breaks inside function parentheses" },
fixable: "whitespace",
schema: [{ oneOf: [{
type: "string",
enum: [
"always",
"never",
"consistent",
"multiline",
"multiline-arguments"
]
}, {
type: "object",
properties: { minItems: {
type: "integer",
minimum: 0
} },
additionalProperties: false
}] }],
messages: {
expectedBefore: "Expected newline before ')'.",
expectedAfter: "Expected newline after '('.",
expectedBetween: "Expected newline between arguments/params.",
unexpectedBefore: "Unexpected newline before ')'.",
unexpectedAfter: "Unexpected newline after '('."
}
},
create(context) {
const sourceCode = context.sourceCode;
const rawOption = context.options[0] || "multiline";
const multilineOption = rawOption === "multiline";
const multilineArgumentsOption = rawOption === "multiline-arguments";
const consistentOption = rawOption === "consistent";
let minItems;
if (typeof rawOption === "object") minItems = rawOption.minItems;
else if (rawOption === "always") minItems = 0;
else if (rawOption === "never") minItems = Infinity;
function shouldHaveNewlines(elements, hasLeftNewline) {
if (multilineArgumentsOption && elements.length === 1) return hasLeftNewline;
if (multilineOption || multilineArgumentsOption) return elements.some((element, index) => index !== elements.length - 1 && !(0, ast_exports.isTokenOnSameLine)(element, elements[index + 1]));
if (consistentOption) return hasLeftNewline;
return minItems == null || elements.length >= minItems;
}
function validateParens(parens, elements) {
const leftParen = parens.leftParen;
const rightParen = parens.rightParen;
const tokenAfterLeftParen = sourceCode.getTokenAfter(leftParen);
const tokenBeforeRightParen = sourceCode.getTokenBefore(rightParen);
const hasLeftNewline = !(0, ast_exports.isTokenOnSameLine)(leftParen, tokenAfterLeftParen);
const hasRightNewline = !(0, ast_exports.isTokenOnSameLine)(tokenBeforeRightParen, rightParen);
const needsNewlines = shouldHaveNewlines(elements, hasLeftNewline);
if (hasLeftNewline && !needsNewlines) context.report({
node: leftParen,
messageId: "unexpectedAfter",
fix(fixer) {
return sourceCode.getText().slice(leftParen.range[1], tokenAfterLeftParen.range[0]).trim() ? null : fixer.removeRange([leftParen.range[1], tokenAfterLeftParen.range[0]]);
}
});
else if (!hasLeftNewline && needsNewlines) context.report({
node: leftParen,
messageId: "expectedAfter",
fix: (fixer) => fixer.insertTextAfter(leftParen, "\n")
});
if (hasRightNewline && !needsNewlines) context.report({
node: rightParen,
messageId: "unexpectedBefore",
fix(fixer) {
return sourceCode.getText().slice(tokenBeforeRightParen.range[1], rightParen.range[0]).trim() ? null : fixer.removeRange([tokenBeforeRightParen.range[1], rightParen.range[0]]);
}
});
else if (!hasRightNewline && needsNewlines) context.report({
node: rightParen,
messageId: "expectedBefore",
fix: (fixer) => fixer.insertTextBefore(rightParen, "\n")
});
}
function validateArguments(parens, elements) {
const leftParen = parens.leftParen;
const tokenAfterLeftParen = sourceCode.getTokenAfter(leftParen);
const hasLeftNewline = !(0, ast_exports.isTokenOnSameLine)(leftParen, tokenAfterLeftParen);
const needsNewlines = shouldHaveNewlines(elements, hasLeftNewline);
for (let i = 0; i <= elements.length - 2; i++) {
const currentElement = elements[i];
const nextElement = elements[i + 1];
const hasNewLine = !(0, ast_exports.isTokenOnSameLine)(currentElement, nextElement);
if (!hasNewLine && needsNewlines) context.report({
node: currentElement,
messageId: "expectedBetween",
fix: (fixer) => fixer.insertTextBefore(nextElement, "\n")
});
}
}
function getParenTokens(node) {
const isOpeningParenTokenOutsideTypeParameter = () => {
let typeParameterOpeningLevel = 0;
return (token) => {
if (token.type === "Punctuator" && token.value === "<") typeParameterOpeningLevel += 1;
if (token.type === "Punctuator" && token.value === ">") typeParameterOpeningLevel -= 1;
return typeParameterOpeningLevel !== 0 ? false : (0, ast_exports.isOpeningParenToken)(token);
};
};
switch (node.type) {
case "NewExpression": if (!node.arguments.length && !((0, ast_exports.isOpeningParenToken)(sourceCode.getLastToken(node, { skip: 1 })) && (0, ast_exports.isClosingParenToken)(sourceCode.getLastToken(node)) && node.callee.range[1] < node.range[1])) return null;
case "CallExpression": return {
leftParen: sourceCode.getTokenAfter(node.callee, isOpeningParenTokenOutsideTypeParameter()),
rightParen: sourceCode.getLastToken(node)
};
case "FunctionDeclaration":
case "FunctionExpression": {
const leftParen = sourceCode.getFirstToken(node, isOpeningParenTokenOutsideTypeParameter());
const rightParen = node.params.length ? sourceCode.getTokenAfter(node.params[node.params.length - 1], ast_exports.isClosingParenToken) : sourceCode.getTokenAfter(leftParen);
return {
leftParen,
rightParen
};
}
case "ArrowFunctionExpression": {
const firstToken = sourceCode.getFirstToken(node, { skip: node.async ? 1 : 0 });
if (!(0, ast_exports.isOpeningParenToken)(firstToken)) return null;
const rightParen = node.params.length ? sourceCode.getTokenAfter(node.params[node.params.length - 1], ast_exports.isClosingParenToken) : sourceCode.getTokenAfter(firstToken);
return {
leftParen: firstToken,
rightParen
};
}
case "ImportExpression": {
const leftParen = sourceCode.getFirstToken(node, 1);
const rightParen = sourceCode.getLastToken(node);
return {
leftParen,
rightParen
};
}
default: throw new TypeError(`unexpected node with type ${node.type}`);
}
}
return { [[
"ArrowFunctionExpression",
"CallExpression",
"FunctionDeclaration",
"FunctionExpression",
"ImportExpression",
"NewExpression"
].join(", ")](node) {
const parens = getParenTokens(node);
let params;
if (node.type === "ImportExpression") params = [node.source, ...node.options ? [node.options] : []];
else if ((0, ast_exports.isFunction)(node)) params = node.params;
else params = node.arguments;
if (parens) {
validateParens(parens, params);
if (multilineArgumentsOption) validateArguments(parens, params);
}
} };
}
});
export { function_paren_newline_default };

View File

@@ -0,0 +1,135 @@
import { createRule } from "../utils.js";
const OVERRIDE_SCHEMA = { oneOf: [{
type: "string",
enum: [
"before",
"after",
"both",
"neither"
]
}, {
type: "object",
properties: {
before: { type: "boolean" },
after: { type: "boolean" }
},
additionalProperties: false
}] };
var generator_star_spacing_default = createRule({
name: "generator-star-spacing",
meta: {
type: "layout",
docs: { description: "Enforce consistent spacing around `*` operators in generator functions" },
fixable: "whitespace",
schema: [{ oneOf: [{
type: "string",
enum: [
"before",
"after",
"both",
"neither"
]
}, {
type: "object",
properties: {
before: { type: "boolean" },
after: { type: "boolean" },
named: OVERRIDE_SCHEMA,
anonymous: OVERRIDE_SCHEMA,
method: OVERRIDE_SCHEMA,
shorthand: OVERRIDE_SCHEMA
},
additionalProperties: false
}] }],
defaultOptions: [{
before: true,
after: false
}],
messages: {
missingBefore: "Missing space before *.",
missingAfter: "Missing space after *.",
unexpectedBefore: "Unexpected space before *.",
unexpectedAfter: "Unexpected space after *."
}
},
create(context, [options]) {
const optionDefinitions = {
before: {
before: true,
after: false
},
after: {
before: false,
after: true
},
both: {
before: true,
after: true
},
neither: {
before: false,
after: false
}
};
function optionToDefinition(option, defaults) {
if (!option) return defaults;
return typeof option === "string" ? optionDefinitions[option] : Object.assign({}, defaults, option);
}
const modes = function() {
const defaults = optionToDefinition(options, optionDefinitions.before);
const { named, anonymous, method, shorthand } = options;
return {
named: optionToDefinition(named, defaults),
anonymous: optionToDefinition(anonymous, defaults),
method: optionToDefinition(method, defaults),
shorthand: optionToDefinition(shorthand ?? method, defaults)
};
}();
const sourceCode = context.sourceCode;
function isStarToken(token) {
return token.value === "*" && token.type === "Punctuator";
}
function getStarToken(node) {
return sourceCode.getFirstToken("method" in node.parent && node.parent.method || node.parent.type === "MethodDefinition" ? node.parent : node, isStarToken);
}
function capitalize(str) {
return str[0].toUpperCase() + str.slice(1);
}
function checkSpacing(kind, side, leftToken, rightToken) {
if (!!(rightToken.range[0] - leftToken.range[1]) !== modes[kind][side]) {
const after = leftToken.value === "*";
const spaceRequired = modes[kind][side];
const node = after ? leftToken : rightToken;
const messageId = `${spaceRequired ? "missing" : "unexpected"}${capitalize(side)}`;
context.report({
node,
messageId,
fix(fixer) {
if (spaceRequired) {
if (after) return fixer.insertTextAfter(node, " ");
return fixer.insertTextBefore(node, " ");
}
return fixer.removeRange([leftToken.range[1], rightToken.range[0]]);
}
});
}
}
function checkFunction(node) {
if (!node.generator) return;
const starToken = getStarToken(node);
const prevToken = sourceCode.getTokenBefore(starToken);
const nextToken = sourceCode.getTokenAfter(starToken);
let kind = "named";
if (node.parent.type === "Property" && node.parent.method) kind = "shorthand";
else if (node.parent.type === "MethodDefinition") kind = "method";
else if (!node.id) kind = "anonymous";
if (!((kind === "method" || kind === "shorthand") && starToken === sourceCode.getFirstToken(node.parent))) checkSpacing(kind, "before", prevToken, starToken);
checkSpacing(kind, "after", starToken, nextToken);
}
return {
FunctionDeclaration: checkFunction,
FunctionExpression: checkFunction
};
}
});
export { generator_star_spacing_default };

View File

@@ -0,0 +1,42 @@
import { ast_exports, createRule, hasCommentsBetween } from "../utils.js";
var implicit_arrow_linebreak_default = createRule({
name: "implicit-arrow-linebreak",
meta: {
type: "layout",
docs: { description: "Enforce the location of arrow function bodies" },
fixable: "whitespace",
schema: [{
type: "string",
enum: ["beside", "below"]
}],
messages: {
expected: "Expected a linebreak before this expression.",
unexpected: "Expected no linebreak before this expression."
}
},
create(context) {
const sourceCode = context.sourceCode;
const option = context.options[0] || "beside";
function validateExpression(node) {
if (node.body.type === "BlockStatement") return;
const arrowToken = sourceCode.getTokenBefore(node.body, ast_exports.isNotOpeningParenToken);
const firstTokenOfBody = sourceCode.getTokenAfter(arrowToken);
const onSameLine = (0, ast_exports.isTokenOnSameLine)(arrowToken, firstTokenOfBody);
if (onSameLine && option === "below") context.report({
node: firstTokenOfBody,
messageId: "expected",
fix: (fixer) => fixer.insertTextBefore(firstTokenOfBody, "\n")
});
else if (!onSameLine && option === "beside") context.report({
node: firstTokenOfBody,
messageId: "unexpected",
fix(fixer) {
if (hasCommentsBetween(sourceCode, arrowToken, firstTokenOfBody)) return null;
return fixer.replaceTextRange([arrowToken.range[1], firstTokenOfBody.range[0]], " ");
}
});
}
return { ArrowFunctionExpression: (node) => validateExpression(node) };
}
});
export { implicit_arrow_linebreak_default };

View File

@@ -0,0 +1,140 @@
import { ASSIGNMENT_OPERATOR, createRule, isKeywordToken } from "../utils.js";
var indent_binary_ops_default = createRule({
name: "indent-binary-ops",
meta: {
type: "layout",
docs: { description: "Indentation for binary operators" },
fixable: "whitespace",
schema: [{ oneOf: [{
type: "integer",
minimum: 0
}, {
type: "string",
enum: ["tab"]
}] }],
messages: { wrongIndentation: "Expected indentation of {{expected}}" }
},
defaultOptions: [2],
create: (context, options) => {
const { sourceCode } = context;
const indentStr = options[0] === "tab" ? " " : " ".repeat(options[0] ?? 2);
const indentCache = /* @__PURE__ */ new Map();
function getIndentOfLine(line) {
if (indentCache.has(line)) return indentCache.get(line);
return sourceCode.lines[line - 1].match(/^\s*/)?.[0] ?? "";
}
function subtractionIndent(indent) {
if (options[0] === "tab") return indent.slice(1);
return indent.slice(options[0] ?? 2);
}
function getTargetIndent(indent, needAdditionIndent, needSubtractionIndent) {
if (needAdditionIndent && !needSubtractionIndent) return indent + indentStr;
if (!needAdditionIndent && needSubtractionIndent) return subtractionIndent(indent);
return indent;
}
function firstTokenOfLine(line) {
return sourceCode.tokensAndComments.find((token) => token.loc.start.line === line);
}
function lastTokenOfLine(line) {
return [...sourceCode.tokensAndComments].reverse().find((token) => token.loc.end.line === line);
}
function isGreaterThanCloseBracketOfLine(line) {
const tokensAndCommentsOfLine = sourceCode.tokensAndComments.filter((token) => token.loc.start.line === line);
let openBracketCount = 0;
let closeBracketCount = 0;
for (const token of tokensAndCommentsOfLine) {
if (token.value === "(") openBracketCount++;
if (token.value === ")") closeBracketCount++;
}
return openBracketCount < closeBracketCount;
}
function isTypeKeywordOfNode(typeToken, node) {
while (node.parent) {
node = node.parent;
if (node.type === "TSTypeAliasDeclaration" && context.sourceCode.getTokenBefore(node.id) === typeToken) return true;
}
return false;
}
function handler(node, right) {
if (node.loc.start.line === node.loc.end.line) return;
let tokenRight = sourceCode.getFirstToken(right);
let tokenOperator = sourceCode.getTokenBefore(tokenRight);
while (tokenOperator.value === "(") {
tokenRight = tokenOperator;
tokenOperator = sourceCode.getTokenBefore(tokenRight);
if (tokenOperator.range[0] <= right.parent.range[0]) return;
}
const tokenBeforeAll = sourceCode.getTokenBefore(node);
const tokenLeft = sourceCode.getTokenBefore(tokenOperator);
const isMultiline = tokenRight.loc.start.line !== tokenLeft.loc.start.line;
if (!isMultiline) return;
const firstTokenOfLineLeft = firstTokenOfLine(tokenLeft.loc.start.line);
const lastTokenOfLineLeft = lastTokenOfLine(tokenLeft.loc.start.line);
const needAdditionIndent = isKeywordToken(firstTokenOfLineLeft) && ![
"typeof",
"instanceof",
"this"
].includes(firstTokenOfLineLeft.value) || firstTokenOfLineLeft?.type === "Identifier" && firstTokenOfLineLeft.value === "type" && isTypeKeywordOfNode(firstTokenOfLineLeft, node) || [
":",
"[",
"(",
"<"
].concat(ASSIGNMENT_OPERATOR).includes(lastTokenOfLineLeft?.value || "") || [
"[",
"(",
"{",
"=>",
":"
].concat(ASSIGNMENT_OPERATOR).includes(tokenBeforeAll?.value || "") && firstTokenOfLineLeft?.loc.start.line === tokenBeforeAll?.loc.start.line;
const needSubtractionIndent = lastTokenOfLineLeft?.value === ")" && isGreaterThanCloseBracketOfLine(tokenLeft.loc.start.line) && ![
"]",
")",
"}"
].includes(firstTokenOfLineLeft?.value || "");
const indentLeft = getIndentOfLine(tokenLeft.loc.start.line);
const indentRight = getIndentOfLine(tokenRight.loc.start.line);
const indentTarget = getTargetIndent(indentLeft, needAdditionIndent, needSubtractionIndent);
if (indentTarget !== indentRight) {
const start = {
line: tokenRight.loc.start.line,
column: 0
};
const end = {
line: tokenRight.loc.start.line,
column: indentRight.length
};
context.report({
loc: {
start,
end
},
messageId: "wrongIndentation",
data: { expected: `${indentTarget.length} ${options[0] === "tab" ? "tab" : "space"}${indentTarget.length === 1 ? "" : "s"}` },
fix(fixer) {
return fixer.replaceTextRange([sourceCode.getIndexFromLoc(start), sourceCode.getIndexFromLoc(end)], indentTarget);
}
});
indentCache.set(tokenRight.loc.start.line, indentTarget);
}
}
return {
BinaryExpression(node) {
handler(node, node.right);
},
LogicalExpression(node) {
handler(node, node.right);
},
TSUnionType(node) {
if (node.types.length > 1) node.types.forEach((type) => {
handler(node, type);
});
},
TSIntersectionType(node) {
if (node.types.length > 1) node.types.forEach((type) => {
handler(node, type);
});
}
};
}
});
export { indent_binary_ops_default };

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,79 @@
import { createRule } from "../utils.js";
const INLINE_ELEMENTS = new Set([
"a",
"abbr",
"acronym",
"b",
"bdo",
"big",
"button",
"cite",
"code",
"dfn",
"em",
"i",
"img",
"input",
"kbd",
"label",
"map",
"object",
"q",
"samp",
"script",
"select",
"small",
"span",
"strong",
"sub",
"sup",
"textarea",
"tt",
"var"
]);
const messages = {
spacingAfterPrev: "Ambiguous spacing after previous element {{element}}",
spacingBeforeNext: "Ambiguous spacing before next element {{element}}"
};
var jsx_child_element_spacing_default = createRule({
name: "jsx-child-element-spacing",
meta: {
type: "layout",
docs: { description: "Enforce or disallow spaces inside of curly braces in JSX attributes and expressions" },
messages,
schema: []
},
create(context) {
const TEXT_FOLLOWING_ELEMENT_PATTERN = /^[\t\v\f\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]*\n\s*\S/;
const TEXT_PRECEDING_ELEMENT_PATTERN = /\S[\t\v\f\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]*\n\s*$/;
const elementName = (node) => node.openingElement && node.openingElement.name && node.openingElement.name.type === "JSXIdentifier" && node.openingElement.name.name || "";
const isInlineElement = (node) => node.type === "JSXElement" && INLINE_ELEMENTS.has(elementName(node));
const handleJSX = (node) => {
let lastChild = null;
let child = null;
[...node.children, null].forEach((nextChild) => {
if ((lastChild || nextChild) && (!lastChild || isInlineElement(lastChild)) && child && (child.type === "Literal" || child.type === "JSXText") && (!nextChild || isInlineElement(nextChild)) && true) {
if (lastChild && String(child.value).match(TEXT_FOLLOWING_ELEMENT_PATTERN)) context.report({
messageId: "spacingAfterPrev",
node: lastChild,
loc: lastChild.loc.end,
data: { element: elementName(lastChild) }
});
else if (nextChild && String(child.value).match(TEXT_PRECEDING_ELEMENT_PATTERN)) context.report({
messageId: "spacingBeforeNext",
node: nextChild,
loc: nextChild.loc.start,
data: { element: elementName(nextChild) }
});
}
lastChild = child;
child = nextChild;
});
};
return {
JSXElement: handleJSX,
JSXFragment: handleJSX
};
}
});
export { jsx_child_element_spacing_default };

View File

@@ -0,0 +1,215 @@
import { createRule } from "../utils.js";
const messages = { bracketLocation: "The closing bracket must be {{location}}{{details}}" };
var jsx_closing_bracket_location_default = createRule({
name: "jsx-closing-bracket-location",
meta: {
type: "layout",
docs: { description: "Enforce closing bracket location in JSX" },
fixable: "code",
messages,
schema: [{ anyOf: [
{
type: "string",
enum: [
"after-props",
"props-aligned",
"tag-aligned",
"line-aligned"
]
},
{
type: "object",
properties: { location: {
type: "string",
enum: [
"after-props",
"props-aligned",
"tag-aligned",
"line-aligned"
]
} },
additionalProperties: false
},
{
type: "object",
properties: {
nonEmpty: { oneOf: [{
type: "string",
enum: [
"after-props",
"props-aligned",
"tag-aligned",
"line-aligned"
]
}, {
type: "boolean",
enum: [false]
}] },
selfClosing: { oneOf: [{
type: "string",
enum: [
"after-props",
"props-aligned",
"tag-aligned",
"line-aligned"
]
}, {
type: "boolean",
enum: [false]
}] }
},
additionalProperties: false
}
] }]
},
create(context) {
const MESSAGE_LOCATION = {
"after-props": "placed after the last prop",
"after-tag": "placed after the opening tag",
"props-aligned": "aligned with the last prop",
"tag-aligned": "aligned with the opening tag",
"line-aligned": "aligned with the line containing the opening tag"
};
const DEFAULT_LOCATION = "tag-aligned";
const config = context.options[0];
const options = {
nonEmpty: DEFAULT_LOCATION,
selfClosing: DEFAULT_LOCATION
};
if (typeof config === "string") {
options.nonEmpty = config;
options.selfClosing = config;
} else if (typeof config === "object") {
if ("location" in config) {
options.nonEmpty = config.location;
options.selfClosing = config.location;
}
if ("nonEmpty" in config) options.nonEmpty = config.nonEmpty;
if ("selfClosing" in config) options.selfClosing = config.selfClosing;
}
function getExpectedLocation(tokens) {
let location;
if (typeof tokens.lastProp === "undefined") location = "after-tag";
else if (tokens.opening.line === tokens.lastProp.lastLine) location = "after-props";
else location = tokens.selfClosing ? options.selfClosing : options.nonEmpty;
return location;
}
function getCorrectColumn(tokens, expectedLocation) {
switch (expectedLocation) {
case "props-aligned": return tokens.lastProp.column;
case "tag-aligned": return tokens.opening.column;
case "line-aligned": return tokens.openingStartOfLine.column;
default: return null;
}
}
function hasCorrectLocation(tokens, expectedLocation) {
switch (expectedLocation) {
case "after-tag": return tokens.tag.line === tokens.closing.line;
case "after-props": return tokens.lastProp.lastLine === tokens.closing.line;
case "props-aligned":
case "tag-aligned":
case "line-aligned": {
const correctColumn = getCorrectColumn(tokens, expectedLocation);
return correctColumn === tokens.closing.column;
}
default: return true;
}
}
function getIndentation(tokens, expectedLocation, correctColumn) {
const newColumn = correctColumn || 0;
let indentation;
let spaces = [];
switch (expectedLocation) {
case "props-aligned":
indentation = /^\s*/.exec(context.sourceCode.lines[tokens.lastProp.firstLine - 1])[0];
break;
case "tag-aligned":
case "line-aligned":
indentation = /^\s*/.exec(context.sourceCode.lines[tokens.opening.line - 1])[0];
break;
default: indentation = "";
}
if (indentation.length + 1 < newColumn) spaces = Array.from({ length: +correctColumn + 1 - indentation.length });
return indentation + spaces.join(" ");
}
function getTokensLocations(node) {
const sourceCode = context.sourceCode;
const opening = sourceCode.getFirstToken(node).loc.start;
const closing = sourceCode.getLastTokens(node, node.selfClosing ? 2 : 1)[0].loc.start;
const tag = sourceCode.getFirstToken(node.name).loc.start;
let lastProp;
if (node.attributes.length) {
lastProp = node.attributes[node.attributes.length - 1];
lastProp = {
column: sourceCode.getFirstToken(lastProp).loc.start.column,
firstLine: sourceCode.getFirstToken(lastProp).loc.start.line,
lastLine: sourceCode.getLastToken(lastProp).loc.end.line
};
}
const openingLine = sourceCode.lines[opening.line - 1];
const closingLine = sourceCode.lines[closing.line - 1];
const isTab = {
openTab: /^\t/.test(openingLine),
closeTab: /^\t/.test(closingLine)
};
const openingStartOfLine = {
column: /^\s*/.exec(openingLine)?.[0].length,
line: opening.line
};
return {
isTab,
tag,
opening,
closing,
lastProp,
selfClosing: node.selfClosing,
openingStartOfLine
};
}
return { "JSXOpeningElement:exit": function(node) {
const lastAttributeNode = node.attributes.at(-1);
const cachedLastAttributeEndPos = lastAttributeNode ? lastAttributeNode.range[1] : null;
let expectedNextLine;
const tokens = getTokensLocations(node);
let expectedLocation = getExpectedLocation(tokens);
let usingSameIndentation = true;
if (expectedLocation === "tag-aligned") usingSameIndentation = tokens.isTab.openTab === tokens.isTab.closeTab;
const lastComment = context.sourceCode.getCommentsInside(node).at(-1);
const hasTrailingComment = lastComment && lastComment.range[0] > (lastAttributeNode ?? node.name).range[1];
if ((expectedLocation === "after-props" || expectedLocation === "after-tag") && !(hasCorrectLocation(tokens, expectedLocation) && usingSameIndentation) && hasTrailingComment) expectedLocation = "line-aligned";
if (hasCorrectLocation(tokens, expectedLocation) && usingSameIndentation) return;
const data = {
location: MESSAGE_LOCATION[expectedLocation],
details: ""
};
const correctColumn = getCorrectColumn(tokens, expectedLocation);
if (correctColumn !== null) {
expectedNextLine = tokens.lastProp && tokens.lastProp.lastLine === tokens.closing.line;
data.details = ` (expected column ${correctColumn + 1}${expectedNextLine ? " on the next line)" : ")"}`;
}
context.report({
node,
messageId: "bracketLocation",
loc: tokens.closing,
data,
fix(fixer) {
const closingTag = tokens.selfClosing ? "/>" : ">";
switch (expectedLocation) {
case "after-tag":
if (cachedLastAttributeEndPos) return fixer.replaceTextRange([cachedLastAttributeEndPos, node.range[1]], (expectedNextLine ? "\n" : "") + closingTag);
return fixer.replaceTextRange([node.name.range[1], node.range[1]], (expectedNextLine ? "\n" : " ") + closingTag);
case "after-props": return fixer.replaceTextRange([cachedLastAttributeEndPos, node.range[1]], (expectedNextLine ? "\n" : "") + closingTag);
case "props-aligned":
case "tag-aligned":
case "line-aligned": {
const rangeStart = hasTrailingComment ? lastComment.range[1] : cachedLastAttributeEndPos;
return fixer.replaceTextRange([rangeStart, node.range[1]], `\n${getIndentation(tokens, expectedLocation, correctColumn)}${closingTag}`);
}
}
return null;
}
});
} };
}
});
export { jsx_closing_bracket_location_default };

View File

@@ -0,0 +1,63 @@
import { createRule, isNodeFirstInLine } from "../utils.js";
const messages = {
onOwnLine: "Closing tag of a multiline JSX expression must be on its own line.",
matchIndent: "Expected closing tag to match indentation of opening.",
alignWithOpening: "Expected closing tag to be aligned with the line containing the opening tag"
};
const DEFAULT_LOCATION = "tag-aligned";
const MESSAGE_LOCATION = {
"tag-aligned": "matchIndent",
"line-aligned": "alignWithOpening"
};
var jsx_closing_tag_location_default = createRule({
name: "jsx-closing-tag-location",
meta: {
type: "layout",
docs: { description: "Enforce closing tag location for multiline JSX" },
fixable: "whitespace",
messages,
schema: [{ anyOf: [{
type: "string",
enum: ["tag-aligned", "line-aligned"],
default: DEFAULT_LOCATION
}] }]
},
defaultOptions: [DEFAULT_LOCATION],
create(context) {
const option = context.options[0] || DEFAULT_LOCATION;
function getIndentation(openingStartOfLine, opening) {
if (option === "line-aligned") return openingStartOfLine.column;
else return opening.loc.start.column;
}
function handleClosingElement(node) {
if (!node.parent) return;
const sourceCode = context.sourceCode;
const opening = "openingFragment" in node.parent ? node.parent.openingFragment : node.parent.openingElement;
const openingLoc = sourceCode.getFirstToken(opening).loc.start;
const openingLine = sourceCode.lines[openingLoc.line - 1];
const openingStartOfLine = {
column: /^\s*/.exec(openingLine)?.[0].length,
line: openingLoc.line
};
if (opening.loc.start.line === node.loc.start.line) return;
if (opening.loc.start.column === node.loc.start.column && option === "tag-aligned") return;
if (openingStartOfLine.column === node.loc.start.column && option === "line-aligned") return;
const messageId = isNodeFirstInLine(context, node) ? MESSAGE_LOCATION[option] : "onOwnLine";
context.report({
node,
messageId,
loc: node.loc,
fix(fixer) {
const indent = new Array((getIndentation(openingStartOfLine, opening) || 0) + 1).join(" ");
if (isNodeFirstInLine(context, node)) return fixer.replaceTextRange([node.range[0] - node.loc.start.column, node.range[0]], indent);
return fixer.insertTextBefore(node, `\n${indent}`);
}
});
}
return {
JSXClosingElement: handleClosingElement,
JSXClosingFragment: handleClosingElement
};
}
});
export { jsx_closing_tag_location_default };

View File

@@ -0,0 +1,217 @@
import { ast_exports, createRule, isJSX, isWhiteSpaces } from "../utils.js";
const OPTION_ALWAYS = "always";
const OPTION_NEVER = "never";
const OPTION_IGNORE = "ignore";
const OPTION_VALUES = [
OPTION_ALWAYS,
OPTION_NEVER,
OPTION_IGNORE
];
const DEFAULT_CONFIG = {
props: OPTION_NEVER,
children: OPTION_NEVER,
propElementValues: OPTION_IGNORE
};
const messages = {
unnecessaryCurly: "Curly braces are unnecessary here.",
missingCurly: "Need to wrap this literal in a JSX expression."
};
var jsx_curly_brace_presence_default = createRule({
name: "jsx-curly-brace-presence",
meta: {
type: "layout",
docs: { description: "Disallow unnecessary JSX expressions when literals alone are sufficient or enforce JSX expressions on literals in JSX children or attributes" },
fixable: "code",
messages,
schema: [{ anyOf: [{
type: "object",
properties: {
props: {
type: "string",
enum: OPTION_VALUES
},
children: {
type: "string",
enum: OPTION_VALUES
},
propElementValues: {
type: "string",
enum: OPTION_VALUES
}
},
additionalProperties: false
}, {
type: "string",
enum: OPTION_VALUES
}] }]
},
create(context) {
const HTML_ENTITY_REGEX = () => /&[A-Z\d#]+;/gi;
const ruleOptions = context.options[0];
const userConfig = typeof ruleOptions === "string" ? {
props: ruleOptions,
children: ruleOptions,
propElementValues: OPTION_IGNORE
} : Object.assign({}, DEFAULT_CONFIG, ruleOptions);
function containsLineTerminators(rawStringValue) {
return ast_exports.LINEBREAK_MATCHER.test(rawStringValue);
}
function containsBackslash(rawStringValue) {
return rawStringValue.includes("\\");
}
function containsHTMLEntity(rawStringValue) {
return HTML_ENTITY_REGEX().test(rawStringValue);
}
function containsOnlyHtmlEntities(rawStringValue) {
return rawStringValue.replace(HTML_ENTITY_REGEX(), "").trim() === "";
}
function containsDisallowedJSXTextChars(rawStringValue) {
return /[{<>}]/.test(rawStringValue);
}
function containsQuoteCharacters(value) {
return /['"]/.test(value);
}
function containsMultilineComment(value) {
return /\/\*/.test(value);
}
function escapeDoubleQuotes(rawStringValue) {
return rawStringValue.replace(/\\"/g, "\"").replace(/"/g, "\\\"");
}
function escapeBackslashes(rawStringValue) {
return rawStringValue.replace(/\\/g, "\\\\");
}
function needToEscapeCharacterForJSX(raw, node) {
return containsBackslash(raw) || containsHTMLEntity(raw) || node.parent.type !== "JSXAttribute" && containsDisallowedJSXTextChars(raw);
}
function containsWhitespaceExpression(child) {
if (child.type === "JSXExpressionContainer") {
const value = child.expression.value;
return value ? isWhiteSpaces(value) : false;
}
return false;
}
function isLineBreak(text) {
return containsLineTerminators(text) && text.trim() === "";
}
function wrapNonHTMLEntities(text) {
const HTML_ENTITY = "<HTML_ENTITY>";
const withCurlyBraces = text.split(HTML_ENTITY_REGEX()).map((word) => word === "" ? "" : `{${JSON.stringify(word)}}`).join(HTML_ENTITY);
const htmlEntities = text.match(HTML_ENTITY_REGEX());
return htmlEntities.reduce((acc, htmlEntity) => acc.replace(HTML_ENTITY, htmlEntity), withCurlyBraces);
}
function wrapWithCurlyBraces(rawText) {
if (!containsLineTerminators(rawText)) return `{${JSON.stringify(rawText)}}`;
return rawText.split("\n").map((line) => {
if (line.trim() === "") return line;
const firstCharIndex = line.search(/\S/);
const leftWhitespace = line.slice(0, firstCharIndex);
const text = line.slice(firstCharIndex);
if (containsHTMLEntity(line)) return `${leftWhitespace}${wrapNonHTMLEntities(text)}`;
return `${leftWhitespace}{${JSON.stringify(text)}}`;
}).join("\n");
}
function reportUnnecessaryCurly(JSXExpressionNode) {
context.report({
messageId: "unnecessaryCurly",
node: JSXExpressionNode,
fix(fixer) {
const expression = JSXExpressionNode.expression;
let textToReplace;
if (isJSX(expression)) {
const sourceCode = context.sourceCode;
textToReplace = sourceCode.getText(expression);
} else {
const parentType = JSXExpressionNode.parent.type;
if (parentType === "JSXAttribute") textToReplace = `"${expression.type === "TemplateLiteral" ? expression.quasis[0].value.raw : expression.raw.slice(1, -1)}"`;
else if (isJSX(expression)) {
const sourceCode = context.sourceCode;
textToReplace = sourceCode.getText(expression);
} else textToReplace = expression.type === "TemplateLiteral" ? expression.quasis[0].value.cooked : expression.value;
}
return fixer.replaceText(JSXExpressionNode, textToReplace);
}
});
}
function reportMissingCurly(literalNode) {
context.report({
messageId: "missingCurly",
node: literalNode,
fix(fixer) {
if (isJSX(literalNode)) return fixer.replaceText(literalNode, `{${context.sourceCode.getText(literalNode)}}`);
if (containsOnlyHtmlEntities(literalNode.raw) || literalNode.parent.type === "JSXAttribute" && containsLineTerminators(literalNode.raw) || isLineBreak(literalNode.raw)) return null;
const expression = literalNode.parent.type === "JSXAttribute" ? `{"${escapeDoubleQuotes(escapeBackslashes(literalNode.raw.slice(1, -1)))}"}` : wrapWithCurlyBraces(literalNode.raw);
return fixer.replaceText(literalNode, expression);
}
});
}
function isWhiteSpaceLiteral(node) {
return node.type && node.type === "Literal" && node.value && isWhiteSpaces(node.value);
}
function isStringWithTrailingWhiteSpaces(value) {
return /^\s|\s$/.test(value);
}
function isLiteralWithTrailingWhiteSpaces(node) {
return node.type && node.type === "Literal" && node.value && isStringWithTrailingWhiteSpaces(node.value);
}
function lintUnnecessaryCurly(JSXExpressionNode) {
const expression = JSXExpressionNode.expression;
const expressionType = expression.type;
const sourceCode = context.sourceCode;
if (sourceCode.getCommentsInside && sourceCode.getCommentsInside(JSXExpressionNode).length > 0) return;
if ((expressionType === "Literal" || expressionType === "JSXText") && typeof expression.value === "string" && (JSXExpressionNode.parent.type === "JSXAttribute" && !isWhiteSpaceLiteral(expression) || !isLiteralWithTrailingWhiteSpaces(expression)) && !containsMultilineComment(expression.value) && !needToEscapeCharacterForJSX(expression.raw, JSXExpressionNode) && (isJSX(JSXExpressionNode.parent) || !containsQuoteCharacters(expression.value))) reportUnnecessaryCurly(JSXExpressionNode);
else if (expressionType === "TemplateLiteral" && expression.expressions.length === 0 && !expression.quasis[0].value.raw.includes("\n") && !isStringWithTrailingWhiteSpaces(expression.quasis[0].value.raw) && !needToEscapeCharacterForJSX(expression.quasis[0].value.raw, JSXExpressionNode) && !containsQuoteCharacters(expression.quasis[0].value.cooked)) reportUnnecessaryCurly(JSXExpressionNode);
else if (isJSX(expression)) reportUnnecessaryCurly(JSXExpressionNode);
}
function areRuleConditionsSatisfied(parent, config, ruleCondition) {
return parent.type === "JSXAttribute" && typeof config.props === "string" && config.props === ruleCondition || isJSX(parent) && typeof config.children === "string" && config.children === ruleCondition;
}
function getAdjacentSiblings(node, children) {
for (let i = 1; i < children.length - 1; i++) {
const child = children[i];
if (node === child) return [children[i - 1], children[i + 1]];
}
if (node === children[0] && children[1]) return [children[1]];
if (node === children[children.length - 1] && children[children.length - 2]) return [children[children.length - 2]];
return [];
}
function hasAdjacentJsxExpressionContainers(node, children) {
if (!children) return false;
const childrenExcludingWhitespaceLiteral = children.filter((child) => !isWhiteSpaceLiteral(child));
const adjSiblings = getAdjacentSiblings(node, childrenExcludingWhitespaceLiteral);
return adjSiblings.some((x) => x.type && x.type === "JSXExpressionContainer");
}
function hasAdjacentJsx(node, children) {
if (!children) return false;
const childrenExcludingWhitespaceLiteral = children.filter((child) => !isWhiteSpaceLiteral(child));
const adjSiblings = getAdjacentSiblings(node, childrenExcludingWhitespaceLiteral);
return adjSiblings.some((x) => x.type && ["JSXExpressionContainer", "JSXElement"].includes(x.type));
}
function shouldCheckForUnnecessaryCurly(node, config) {
const parent = node.parent;
if (parent.type && parent.type === "JSXAttribute" && node.expression && node.expression.type && node.expression.type !== "Literal" && node.expression.type !== "StringLiteral" && node.expression.type !== "TemplateLiteral") return false;
if (isJSX(parent) && hasAdjacentJsxExpressionContainers(node, parent.children)) return false;
if (containsWhitespaceExpression(node) && hasAdjacentJsx(node, parent.children)) return false;
if (parent.children && parent.children.length === 1 && containsWhitespaceExpression(node)) return false;
return areRuleConditionsSatisfied(parent, config, OPTION_NEVER);
}
function shouldCheckForMissingCurly(node, config) {
if (isJSX(node)) return config.propElementValues !== OPTION_IGNORE;
if (isLineBreak(node.raw) || containsOnlyHtmlEntities(node.raw)) return false;
const parent = node.parent;
if (parent.children && parent.children.length === 1 && containsWhitespaceExpression(parent.children[0])) return false;
return areRuleConditionsSatisfied(parent, config, OPTION_ALWAYS);
}
return {
"JSXAttribute > JSXExpressionContainer > JSXElement": function(node) {
if (userConfig.propElementValues === OPTION_NEVER) reportUnnecessaryCurly(node.parent);
},
JSXExpressionContainer(node) {
if (shouldCheckForUnnecessaryCurly(node, userConfig)) lintUnnecessaryCurly(node);
},
"JSXAttribute > JSXElement, Literal, JSXText": function(node) {
if (shouldCheckForMissingCurly(node, userConfig)) reportMissingCurly(node);
}
};
}
});
export { jsx_curly_brace_presence_default };

View File

@@ -0,0 +1,109 @@
import { ast_exports, createRule, isSingleLine } from "../utils.js";
function getNormalizedOption(context) {
const rawOption = context.options[0] || "consistent";
if (rawOption === "consistent") return {
multiline: "consistent",
singleline: "consistent"
};
if (rawOption === "never") return {
multiline: "forbid",
singleline: "forbid"
};
return {
multiline: rawOption.multiline || "consistent",
singleline: rawOption.singleline || "consistent"
};
}
const messages = {
expectedBefore: "Expected newline before '}'.",
expectedAfter: "Expected newline after '{'.",
unexpectedBefore: "Unexpected newline before '}'.",
unexpectedAfter: "Unexpected newline after '{'."
};
var jsx_curly_newline_default = createRule({
name: "jsx-curly-newline",
meta: {
type: "layout",
docs: { description: "Enforce consistent linebreaks in curly braces in JSX attributes and expressions" },
fixable: "whitespace",
schema: [{ anyOf: [{
type: "string",
enum: ["consistent", "never"]
}, {
type: "object",
properties: {
singleline: {
type: "string",
enum: [
"consistent",
"require",
"forbid"
]
},
multiline: {
type: "string",
enum: [
"consistent",
"require",
"forbid"
]
}
},
additionalProperties: false
}] }],
messages
},
create(context) {
const sourceCode = context.sourceCode;
const option = getNormalizedOption(context);
function shouldHaveNewlines(expression, hasLeftNewline) {
switch (!isSingleLine(expression) ? option.multiline : option.singleline) {
case "forbid": return false;
case "require": return true;
case "consistent":
default: return hasLeftNewline;
}
}
function validateCurlys(curlys, expression) {
const leftCurly = curlys.leftCurly;
const rightCurly = curlys.rightCurly;
const tokenAfterLeftCurly = sourceCode.getTokenAfter(leftCurly);
const tokenBeforeRightCurly = sourceCode.getTokenBefore(rightCurly);
const hasLeftNewline = !(0, ast_exports.isTokenOnSameLine)(leftCurly, tokenAfterLeftCurly);
const hasRightNewline = !(0, ast_exports.isTokenOnSameLine)(tokenBeforeRightCurly, rightCurly);
const needsNewlines = shouldHaveNewlines(expression, hasLeftNewline);
if (hasLeftNewline && !needsNewlines) context.report({
node: leftCurly,
messageId: "unexpectedAfter",
fix(fixer) {
return sourceCode.getText().slice(leftCurly.range[1], tokenAfterLeftCurly?.range[0]).trim() ? null : fixer.removeRange([leftCurly.range[1], tokenAfterLeftCurly.range[0]]);
}
});
else if (!hasLeftNewline && needsNewlines) context.report({
node: leftCurly,
messageId: "expectedAfter",
fix: (fixer) => fixer.insertTextAfter(leftCurly, "\n")
});
if (hasRightNewline && !needsNewlines) context.report({
node: rightCurly,
messageId: "unexpectedBefore",
fix(fixer) {
return sourceCode.getText().slice(tokenBeforeRightCurly.range[1], rightCurly.range[0]).trim() ? null : fixer.removeRange([tokenBeforeRightCurly.range[1], rightCurly.range[0]]);
}
});
else if (!hasRightNewline && needsNewlines) context.report({
node: rightCurly,
messageId: "expectedBefore",
fix: (fixer) => fixer.insertTextBefore(rightCurly, "\n")
});
}
return { JSXExpressionContainer(node) {
const curlyTokens = {
leftCurly: sourceCode.getFirstToken(node),
rightCurly: sourceCode.getLastToken(node)
};
validateCurlys(curlyTokens, node.expression);
} };
}
});
export { jsx_curly_newline_default };

View File

@@ -0,0 +1,236 @@
import { ast_exports, createRule } from "../utils.js";
const SPACING = {
always: "always",
never: "never"
};
const SPACING_VALUES = [SPACING.always, SPACING.never];
const messages = {
noNewlineAfter: "There should be no newline after '{{token}}'",
noNewlineBefore: "There should be no newline before '{{token}}'",
noSpaceAfter: "There should be no space after '{{token}}'",
noSpaceBefore: "There should be no space before '{{token}}'",
spaceNeededAfter: "A space is required after '{{token}}'",
spaceNeededBefore: "A space is required before '{{token}}'"
};
const BASIC_CONFIG_SCHEMA = {
type: "object",
properties: {
when: {
type: "string",
enum: SPACING_VALUES
},
allowMultiline: { type: "boolean" },
spacing: {
type: "object",
properties: { objectLiterals: {
type: "string",
enum: SPACING_VALUES
} },
additionalProperties: false
}
},
additionalProperties: false
};
const BASIC_CONFIG_OR_BOOLEAN_SCHEMA = { anyOf: [BASIC_CONFIG_SCHEMA, { type: "boolean" }] };
var jsx_curly_spacing_default = createRule({
name: "jsx-curly-spacing",
meta: {
type: "layout",
docs: { description: "Enforce or disallow spaces inside of curly braces in JSX attributes and expressions" },
fixable: "code",
messages,
schema: {
type: "array",
items: [{ anyOf: [{
type: "object",
additionalProperties: false,
properties: {
...BASIC_CONFIG_SCHEMA.properties,
attributes: BASIC_CONFIG_OR_BOOLEAN_SCHEMA,
children: BASIC_CONFIG_OR_BOOLEAN_SCHEMA
}
}, {
type: "string",
enum: SPACING_VALUES
}] }, {
type: "object",
properties: {
allowMultiline: { type: "boolean" },
spacing: {
type: "object",
properties: { objectLiterals: {
type: "string",
enum: SPACING_VALUES
} },
additionalProperties: false
}
},
additionalProperties: false
}]
}
},
create(context) {
function normalizeConfig(configOrTrue, defaults, lastPass = false) {
const config = configOrTrue === true ? {} : configOrTrue;
const when = config.when || defaults.when;
const allowMultiline = "allowMultiline" in config ? config.allowMultiline : defaults.allowMultiline;
const spacing = config.spacing || {};
let objectLiteralSpaces = spacing.objectLiterals || defaults.objectLiteralSpaces;
if (lastPass) objectLiteralSpaces = objectLiteralSpaces || when;
return {
when,
allowMultiline,
objectLiteralSpaces
};
}
const DEFAULT_WHEN = SPACING.never;
const DEFAULT_ALLOW_MULTILINE = true;
const DEFAULT_ATTRIBUTES = true;
const DEFAULT_CHILDREN = false;
let originalConfig = context.options[0] || {};
if (SPACING_VALUES.includes(originalConfig)) originalConfig = Object.assign({ when: context.options[0] }, context.options[1]);
originalConfig = originalConfig;
const defaultConfig = normalizeConfig(originalConfig, {
when: DEFAULT_WHEN,
allowMultiline: DEFAULT_ALLOW_MULTILINE
});
const attributes = "attributes" in originalConfig ? originalConfig.attributes : DEFAULT_ATTRIBUTES;
const attributesConfig = attributes ? normalizeConfig(attributes, defaultConfig, true) : null;
const children = "children" in originalConfig ? originalConfig.children : DEFAULT_CHILDREN;
const childrenConfig = children ? normalizeConfig(children, defaultConfig, true) : null;
function fixByTrimmingWhitespace(fixer, fromLoc, toLoc, mode, spacing = "") {
let replacementText = context.sourceCode.text.slice(fromLoc, toLoc);
if (mode === "start") replacementText = replacementText.replace(/^\s+/gm, "");
else replacementText = replacementText.replace(/\s+$/gm, "");
if (spacing === SPACING.always) if (mode === "start") replacementText += " ";
else replacementText = ` ${replacementText}`;
return fixer.replaceTextRange([fromLoc, toLoc], replacementText);
}
function reportNoBeginningNewline(node, token, spacing) {
context.report({
node,
loc: token.loc.start,
messageId: "noNewlineAfter",
data: { token: token.value },
fix(fixer) {
const nextToken = context.sourceCode.getTokenAfter(token);
return fixByTrimmingWhitespace(fixer, token.range[1], nextToken.range[0], "start", spacing);
}
});
}
function reportNoEndingNewline(node, token, spacing) {
context.report({
node,
loc: token.loc.start,
messageId: "noNewlineBefore",
data: { token: token.value },
fix(fixer) {
const previousToken = context.sourceCode.getTokenBefore(token);
return fixByTrimmingWhitespace(fixer, previousToken.range[1], token.range[0], "end", spacing);
}
});
}
function reportNoBeginningSpace(node, token) {
context.report({
node,
loc: token.loc.start,
messageId: "noSpaceAfter",
data: { token: token.value },
fix(fixer) {
const sourceCode = context.sourceCode;
const nextToken = sourceCode.getTokenAfter(token);
const nextComment = sourceCode.getCommentsAfter(token);
if (nextComment.length > 0) return fixByTrimmingWhitespace(fixer, token.range[1], Math.min(nextToken.range[0], nextComment[0].range[0]), "start");
return fixByTrimmingWhitespace(fixer, token.range[1], nextToken.range[0], "start");
}
});
}
function reportNoEndingSpace(node, token) {
context.report({
node,
loc: token.loc.start,
messageId: "noSpaceBefore",
data: { token: token.value },
fix(fixer) {
const sourceCode = context.sourceCode;
const previousToken = sourceCode.getTokenBefore(token);
const previousComment = sourceCode.getCommentsBefore(token);
if (previousComment.length > 0) return fixByTrimmingWhitespace(fixer, Math.max(previousToken.range[1], previousComment[0].range[1]), token.range[0], "end");
return fixByTrimmingWhitespace(fixer, previousToken.range[1], token.range[0], "end");
}
});
}
function reportRequiredBeginningSpace(node, token) {
context.report({
node,
loc: token.loc.start,
messageId: "spaceNeededAfter",
data: { token: token.value },
fix(fixer) {
return fixer.insertTextAfter(token, " ");
}
});
}
function reportRequiredEndingSpace(node, token) {
context.report({
node,
loc: token.loc.start,
messageId: "spaceNeededBefore",
data: { token: token.value },
fix(fixer) {
return fixer.insertTextBefore(token, " ");
}
});
}
function validateBraceSpacing(node) {
let config;
switch (node.parent?.type) {
case "JSXAttribute":
case "JSXOpeningElement":
config = attributesConfig;
break;
case "JSXElement":
case "JSXFragment":
config = childrenConfig;
break;
default: return;
}
if (config === null) return;
const sourceCode = context.sourceCode;
const first = sourceCode.getFirstToken(node);
const last = sourceCode.getLastToken(node);
let second = sourceCode.getTokenAfter(first, { includeComments: true });
let penultimate = sourceCode.getTokenBefore(last, { includeComments: true });
if (!second) {
second = sourceCode.getTokenAfter(first);
const leadingComments = sourceCode.getCommentsBefore(second);
second = leadingComments ? leadingComments[0] : second;
}
if (!penultimate) {
penultimate = sourceCode.getTokenBefore(last);
const trailingComments = sourceCode.getCommentsAfter(penultimate);
penultimate = trailingComments ? trailingComments[trailingComments.length - 1] : penultimate;
}
const isObjectLiteral = first.value === second.value;
const spacing = isObjectLiteral ? config.objectLiteralSpaces : config.when;
if (spacing === SPACING.always) {
if (!sourceCode.isSpaceBetween(first, second)) reportRequiredBeginningSpace(node, first);
else if (!config.allowMultiline && !(0, ast_exports.isTokenOnSameLine)(first, second)) reportNoBeginningNewline(node, first, spacing);
if (!sourceCode.isSpaceBetween(penultimate, last)) reportRequiredEndingSpace(node, last);
else if (!config.allowMultiline && !(0, ast_exports.isTokenOnSameLine)(penultimate, last)) reportNoEndingNewline(node, last, spacing);
} else if (spacing === SPACING.never) {
if (!(0, ast_exports.isTokenOnSameLine)(first, second)) {
if (!config.allowMultiline) reportNoBeginningNewline(node, first, spacing);
} else if (sourceCode.isSpaceBetween(first, second)) reportNoBeginningSpace(node, first);
if (!(0, ast_exports.isTokenOnSameLine)(penultimate, last)) {
if (!config.allowMultiline) reportNoEndingNewline(node, last, spacing);
} else if (sourceCode.isSpaceBetween(penultimate, last)) reportNoEndingSpace(node, last);
}
}
return {
JSXExpressionContainer: validateBraceSpacing,
JSXSpreadAttribute: validateBraceSpacing
};
}
});
export { jsx_curly_spacing_default };

View File

@@ -0,0 +1,68 @@
import { createRule } from "../utils.js";
const messages = {
noSpaceBefore: "There should be no space before '='",
noSpaceAfter: "There should be no space after '='",
needSpaceBefore: "A space is required before '='",
needSpaceAfter: "A space is required after '='"
};
var jsx_equals_spacing_default = createRule({
name: "jsx-equals-spacing",
meta: {
type: "layout",
docs: { description: "Enforce or disallow spaces around equal signs in JSX attributes" },
fixable: "code",
messages,
schema: [{
type: "string",
enum: ["always", "never"]
}]
},
create(context) {
const config = context.options[0] || "never";
return { JSXOpeningElement(node) {
node.attributes.forEach((attrNode) => {
if (!(attrNode.type !== "JSXSpreadAttribute" && attrNode.value !== null)) return;
const sourceCode = context.sourceCode;
const equalToken = sourceCode.getTokenAfter(attrNode.name);
const spacedBefore = sourceCode.isSpaceBetween(attrNode.name, equalToken);
const spacedAfter = sourceCode.isSpaceBetween(equalToken, attrNode.value);
if (config === "never") {
if (spacedBefore) context.report({
node: attrNode,
messageId: "noSpaceBefore",
loc: equalToken.loc.start,
fix(fixer) {
return fixer.removeRange([attrNode.name.range[1], equalToken.range[0]]);
}
});
if (spacedAfter) context.report({
node: attrNode,
messageId: "noSpaceAfter",
loc: equalToken.loc.start,
fix(fixer) {
return fixer.removeRange([equalToken.range[1], attrNode.value.range[0]]);
}
});
} else if (config === "always") {
if (!spacedBefore) context.report({
messageId: "needSpaceBefore",
node: attrNode,
loc: equalToken.loc.start,
fix(fixer) {
return fixer.insertTextBefore(equalToken, " ");
}
});
if (!spacedAfter) context.report({
node: attrNode,
messageId: "needSpaceAfter",
loc: equalToken.loc.start,
fix(fixer) {
return fixer.insertTextAfter(equalToken, " ");
}
});
}
});
} };
}
});
export { jsx_equals_spacing_default };

View File

@@ -0,0 +1,50 @@
import { createRule, isSingleLine } from "../utils.js";
const messages = {
propOnNewLine: "Property should be placed on a new line",
propOnSameLine: "Property should be placed on the same line as the component declaration"
};
var jsx_first_prop_new_line_default = createRule({
name: "jsx-first-prop-new-line",
meta: {
type: "layout",
docs: { description: "Enforce proper position of the first property in JSX" },
fixable: "code",
messages,
schema: [{
type: "string",
enum: [
"always",
"never",
"multiline",
"multiline-multiprop",
"multiprop"
]
}]
},
create(context) {
const configuration = context.options[0] || "multiline-multiprop";
return { JSXOpeningElement(node) {
if (configuration === "multiline" && !isSingleLine(node) || configuration === "multiline-multiprop" && !isSingleLine(node) && node.attributes.length > 1 || configuration === "multiprop" && node.attributes.length > 1 || configuration === "always") node.attributes.some((decl) => {
if (decl.loc.start.line === node.loc.start.line) context.report({
node: decl,
messageId: "propOnNewLine",
fix(fixer) {
return fixer.replaceTextRange([(node.typeArguments || node.name).range[1], decl.range[0]], "\n");
}
});
return true;
});
else if (configuration === "never" && node.attributes.length > 0 || configuration === "multiprop" && !isSingleLine(node) && node.attributes.length <= 1) {
const firstNode = node.attributes[0];
if (node.loc.start.line < firstNode.loc.start.line) context.report({
node: firstNode,
messageId: "propOnSameLine",
fix(fixer) {
return fixer.replaceTextRange([node.name.range[1], firstNode.range[0]], " ");
}
});
}
} };
}
});
export { jsx_first_prop_new_line_default };

View File

@@ -0,0 +1,66 @@
import { ast_exports, createRule, isJSX, isSingleLine } from "../utils.js";
const messages = { missingLineBreak: "Missing line break around JSX" };
function endWithComma(context, node) {
const sourceCode = context.sourceCode;
const nextToken = sourceCode.getTokenAfter(node);
return !!nextToken && nextToken.value === "," && nextToken.range[0] >= node.range[1];
}
var jsx_function_call_newline_default = createRule({
name: "jsx-function-call-newline",
meta: {
type: "layout",
docs: { description: "Enforce line breaks before and after JSX elements when they are used as arguments to a function." },
fixable: "whitespace",
messages,
schema: [{
type: "string",
enum: ["always", "multiline"]
}]
},
create(context) {
const option = context.options[0] || "multiline";
function needsOpeningNewLine(node) {
const previousToken = context.sourceCode.getTokenBefore(node);
if ((0, ast_exports.isTokenOnSameLine)(previousToken, node)) return true;
return false;
}
function needsClosingNewLine(node) {
const nextToken = context.sourceCode.getTokenAfter(node);
if (endWithComma(context, node)) return false;
if (node.loc.end.line === nextToken.loc.end.line) return true;
return false;
}
function check(node) {
if (!node || !isJSX(node)) return;
const sourceCode = context.sourceCode;
if (option === "always" || !isSingleLine(node)) {
const needsOpening = needsOpeningNewLine(node);
const needsClosing = needsClosingNewLine(node);
if (needsOpening || needsClosing) context.report({
node,
messageId: "missingLineBreak",
fix: (fixer) => {
const text = sourceCode.getText(node);
let fixed = text;
if (needsOpening) fixed = `\n${fixed}`;
if (needsClosing) fixed = `${fixed}\n`;
return fixer.replaceText(node, fixed);
}
});
}
}
function handleCallExpression(node) {
if (node.arguments.length === 0) return;
node.arguments.forEach(check);
}
return {
CallExpression(node) {
handleCallExpression(node);
},
NewExpression(node) {
handleCallExpression(node);
}
};
}
});
export { jsx_function_call_newline_default };

View File

@@ -0,0 +1,108 @@
import { createRule, isNodeFirstInLine } from "../utils.js";
const messages = { wrongIndent: "Expected indentation of {{needed}} {{type}} {{characters}} but found {{gotten}}." };
var jsx_indent_props_default = createRule({
name: "jsx-indent-props",
meta: {
type: "layout",
docs: { description: "Enforce props indentation in JSX" },
fixable: "code",
messages,
schema: [{ anyOf: [
{
type: "string",
enum: ["tab", "first"]
},
{ type: "integer" },
{
type: "object",
properties: {
indentMode: { anyOf: [{
type: "string",
enum: ["tab", "first"]
}, { type: "integer" }] },
ignoreTernaryOperator: { type: "boolean" }
},
additionalProperties: false
}
] }]
},
create(context) {
const options = context.options[0];
const extraColumnStart = 0;
let indentType = "space";
let indentSize = 4;
const line = {
isUsingOperator: false,
currentOperator: false
};
let ignoreTernaryOperator = false;
if (context.options.length) {
const isConfigObject = typeof context.options[0] === "object";
const indentMode = isConfigObject ? typeof options === "object" && options.indentMode : options;
if (indentMode === "first") {
indentSize = "first";
indentType = "space";
} else if (indentMode === "tab") {
indentSize = 1;
indentType = "tab";
} else if (typeof indentMode === "number") {
indentSize = indentMode;
indentType = "space";
}
if (typeof options === "object" && options.ignoreTernaryOperator) ignoreTernaryOperator = true;
}
function getNodeIndent(node) {
let src = context.sourceCode.getText(node, node.loc.start.column + extraColumnStart);
const lines = src.split("\n");
src = lines[0];
let regExp;
if (indentType === "space") regExp = /^ +/;
else regExp = /^\t+/;
const indent = regExp.exec(src);
const useOperator = /^[ \t]*:/.test(src) || /^[ \t]*\?/.test(src);
const useBracket = /</.test(src);
line.currentOperator = false;
if (useOperator) {
line.isUsingOperator = true;
line.currentOperator = true;
} else if (useBracket) line.isUsingOperator = false;
return indent ? indent[0].length : 0;
}
function checkNodesIndent(nodes, indent) {
let nestedIndent = indent;
nodes.forEach((node) => {
const nodeIndent = getNodeIndent(node);
if (line.isUsingOperator && !line.currentOperator && indentSize !== "first" && !ignoreTernaryOperator) {
nestedIndent += indentSize;
line.isUsingOperator = false;
}
if (node.type !== "ArrayExpression" && node.type !== "ObjectExpression" && nodeIndent !== nestedIndent && isNodeFirstInLine(context, node)) context.report({
node,
messageId: "wrongIndent",
data: {
needed: nestedIndent,
type: indentType,
characters: nestedIndent === 1 ? "character" : "characters",
gotten: nodeIndent
},
fix(fixer) {
return fixer.replaceTextRange([node.range[0] - node.loc.start.column, node.range[0]], new Array(nestedIndent + 1).join(indentType === "space" ? " " : " "));
}
});
});
}
return { JSXOpeningElement(node) {
if (!node.attributes.length) return;
let propIndent;
if (indentSize === "first") {
const firstPropNode = node.attributes[0];
propIndent = firstPropNode.loc.start.column;
} else {
const elementIndent = getNodeIndent(node);
propIndent = elementIndent + indentSize;
}
checkNodesIndent(node.attributes, propIndent);
} };
}
});
export { jsx_indent_props_default };

View File

@@ -0,0 +1,188 @@
import { ast_exports, createRule, getFirstNodeInLine, isJSX, isNodeFirstInLine, isReturningJSX } from "../utils.js";
const messages = { wrongIndent: "Expected indentation of {{needed}} {{type}} {{characters}} but found {{gotten}}." };
var jsx_indent_default = createRule({
name: "jsx-indent",
meta: {
type: "layout",
docs: { description: "Enforce JSX indentation. Deprecated, use `indent` rule instead." },
deprecated: {
message: "The rule was replaced with a more general rule.",
deprecatedSince: "5.0.0",
replacedBy: [{ rule: {
name: "indent",
url: "https://eslint.style/rules/indent"
} }]
},
fixable: "whitespace",
messages,
schema: [{ anyOf: [{
type: "string",
enum: ["tab"]
}, { type: "integer" }] }, {
type: "object",
properties: {
checkAttributes: { type: "boolean" },
indentLogicalExpressions: { type: "boolean" }
},
additionalProperties: false
}]
},
create(context) {
const extraColumnStart = 0;
let indentType = "space";
let indentSize = 4;
if (context.options.length) {
if (context.options[0] === "tab") {
indentSize = 1;
indentType = "tab";
} else if (typeof context.options[0] === "number") {
indentSize = context.options[0];
indentType = "space";
}
}
const indentChar = indentType === "space" ? " " : " ";
const options = context.options[1] || {};
const checkAttributes = options.checkAttributes || false;
const indentLogicalExpressions = options.indentLogicalExpressions || false;
function getFixerFunction(node, needed) {
const indent = new Array(needed + 1).join(indentChar);
if (node.type === "JSXText" || node.type === "Literal") return function fix(fixer) {
const regExp = /\n[\t ]*(\S)/g;
const fixedText = node.raw.replace(regExp, (match, p1) => `\n${indent}${p1}`);
return fixer.replaceText(node, fixedText);
};
if (node.type === "ReturnStatement") {
const raw = context.sourceCode.getText(node);
const lines = raw.split("\n");
if (lines.length > 1) return function fix(fixer) {
const lastLineStart = raw.lastIndexOf("\n");
const lastLine = raw.slice(lastLineStart).replace(/^\n[\t ]*(\S)/, (match, p1) => `\n${indent}${p1}`);
return fixer.replaceTextRange([node.range[0] + lastLineStart, node.range[1]], lastLine);
};
}
return function fix(fixer) {
return fixer.replaceTextRange([node.range[0] - node.loc.start.column, node.range[0]], indent);
};
}
function report(node, needed, gotten, loc) {
const msgContext = {
needed,
type: indentType,
characters: needed === 1 ? "character" : "characters",
gotten
};
context.report({
node,
messageId: "wrongIndent",
data: msgContext,
fix: getFixerFunction(node, needed),
...loc ? { loc } : {}
});
}
function getNodeIndent(node, byLastLine = false, excludeCommas = false) {
let src = context.sourceCode.getText(node, node.loc.start.column + extraColumnStart);
const lines = src.split("\n");
if (byLastLine) src = lines[lines.length - 1];
else src = lines[0];
const skip = excludeCommas ? "," : "";
let regExp;
if (indentType === "space") regExp = /* @__PURE__ */ new RegExp(`^[ ${skip}]+`);
else regExp = /* @__PURE__ */ new RegExp(`^[\t${skip}]+`);
const indent = regExp.exec(src);
return indent ? indent[0].length : 0;
}
function isRightInLogicalExp(node) {
return node.parent && node.parent.parent && node.parent.parent.type === "LogicalExpression" && node.parent.parent.right === node.parent && !indentLogicalExpressions;
}
function isAlternateInConditionalExp(node) {
return node.parent && node.parent.parent && node.parent.parent.type === "ConditionalExpression" && node.parent.parent.alternate === node.parent && context.sourceCode.getTokenBefore(node).value !== "(";
}
function isSecondOrSubsequentExpWithinDoExp(node) {
if (!node.parent || !node.parent.parent || node.parent.parent.type !== "ExpressionStatement") return false;
const expStmt = node.parent.parent;
const isInBlockStmtWithinDoExp = expStmt.parent && expStmt.parent.type === "BlockStatement" && expStmt.parent.parent && expStmt.parent.parent.type === "DoExpression";
if (!isInBlockStmtWithinDoExp) return false;
const blockStmt = expStmt.parent;
const blockStmtFirstExp = blockStmt.body[0];
return !(blockStmtFirstExp === expStmt);
}
function checkNodesIndent(node, indent, excludeCommas = false) {
const nodeIndent = getNodeIndent(node, false, excludeCommas);
const isCorrectRightInLogicalExp = isRightInLogicalExp(node) && nodeIndent - indent === indentSize;
const isCorrectAlternateInCondExp = isAlternateInConditionalExp(node) && nodeIndent - indent === 0;
if (nodeIndent !== indent && isNodeFirstInLine(context, node) && !isCorrectRightInLogicalExp && !isCorrectAlternateInCondExp) report(node, indent, nodeIndent);
}
function checkLiteralNodeIndent(node, indent) {
const value = node.value;
const regExp = indentType === "space" ? /\n( *)[\t ]*\S/g : /\n(\t*)[\t ]*\S/g;
const nodeIndentsPerLine = Array.from(String(value).matchAll(regExp), (match) => match[1] ? match[1].length : 0);
const hasFirstInLineNode = nodeIndentsPerLine.length > 0;
if (hasFirstInLineNode && !nodeIndentsPerLine.every((actualIndent) => actualIndent === indent)) nodeIndentsPerLine.forEach((nodeIndent) => {
report(node, indent, nodeIndent);
});
}
function handleOpeningElement(node) {
const sourceCode = context.sourceCode;
let prevToken = sourceCode.getTokenBefore(node);
if (!prevToken) return;
if (prevToken.type === "JSXText" || (0, ast_exports.isCommaToken)(prevToken)) {
prevToken = sourceCode.getNodeByRangeIndex(prevToken.range[0]);
prevToken = prevToken.type === "Literal" || prevToken.type === "JSXText" ? prevToken.parent : prevToken;
} else if ((0, ast_exports.isColonToken)(prevToken)) {
do
prevToken = sourceCode.getTokenBefore(prevToken);
while (prevToken.type === "Punctuator" && prevToken.value !== "/");
prevToken = sourceCode.getNodeByRangeIndex(prevToken.range[0]);
while (prevToken.parent && prevToken.parent.type !== "ConditionalExpression") prevToken = prevToken.parent;
}
prevToken = prevToken.type === "JSXExpressionContainer" ? prevToken.expression : prevToken;
const parentElementIndent = getNodeIndent(prevToken);
const indent = prevToken.loc.start.line === node.loc.start.line || isRightInLogicalExp(node) || isAlternateInConditionalExp(node) || isSecondOrSubsequentExpWithinDoExp(node) ? 0 : indentSize;
checkNodesIndent(node, parentElementIndent + indent);
}
function handleClosingElement(node) {
if (!node.parent) return;
const peerElementIndent = getNodeIndent(node.parent.openingElement || node.parent.openingFragment);
checkNodesIndent(node, peerElementIndent);
}
function handleAttribute(node) {
if (!checkAttributes || !node.value || node.value.type !== "JSXExpressionContainer") return;
const nameIndent = getNodeIndent(node.name);
const lastToken = context.sourceCode.getLastToken(node.value);
const firstInLine = getFirstNodeInLine(context, lastToken);
if (firstInLine.loc.start.line !== lastToken.loc.start.line) return;
const indent = node.name.loc.start.line === firstInLine.loc.start.line ? 0 : nameIndent;
checkNodesIndent(firstInLine, indent);
}
function handleLiteral(node) {
if (!node.parent) return;
if (node.parent.type !== "JSXElement" && node.parent.type !== "JSXFragment") return;
const parentNodeIndent = getNodeIndent(node.parent);
checkLiteralNodeIndent(node, parentNodeIndent + indentSize);
}
return {
JSXOpeningElement: handleOpeningElement,
JSXOpeningFragment: handleOpeningElement,
JSXClosingElement: handleClosingElement,
JSXClosingFragment: handleClosingElement,
JSXAttribute: handleAttribute,
JSXExpressionContainer(node) {
if (!node.parent) return;
const parentNodeIndent = getNodeIndent(node.parent);
checkNodesIndent(node, parentNodeIndent + indentSize);
},
Literal: handleLiteral,
JSXText: handleLiteral,
ReturnStatement(node) {
if (!node.parent || !node.argument || !isJSX(node.argument)) return;
let fn = node.parent;
while (fn && fn.type !== "FunctionDeclaration" && fn.type !== "FunctionExpression") fn = fn.parent;
if (!fn || !isReturningJSX(node, context, true)) return;
const openingIndent = getNodeIndent(node);
const closingIndent = getNodeIndent(node, true);
if (openingIndent !== closingIndent) report(node, openingIndent, closingIndent);
}
};
}
});
export { jsx_indent_default };

View File

@@ -0,0 +1,99 @@
import { ast_exports, createRule, isSingleLine } from "../utils.js";
function getPropName(context, propNode) {
if (propNode.type === "JSXSpreadAttribute") return context.sourceCode.getText(propNode.argument);
return propNode.name.name;
}
const messages = { newLine: "Prop `{{prop}}` must be placed on a new line" };
var jsx_max_props_per_line_default = createRule({
name: "jsx-max-props-per-line",
meta: {
type: "layout",
docs: { description: "Enforce maximum of props on a single line in JSX" },
fixable: "code",
messages,
schema: [{ anyOf: [{
type: "object",
properties: { maximum: {
type: "object",
properties: {
single: {
type: "integer",
minimum: 1
},
multi: {
type: "integer",
minimum: 1
}
},
additionalProperties: false
} },
additionalProperties: false
}, {
type: "object",
properties: {
maximum: {
type: "number",
minimum: 1
},
when: {
type: "string",
enum: ["always", "multiline"]
}
},
additionalProperties: false
}] }]
},
create(context) {
const configuration = context.options[0] || {};
const maximum = configuration.maximum || 1;
const maxConfig = typeof maximum === "number" ? {
single: configuration.when === "multiline" ? Infinity : maximum,
multi: maximum
} : {
single: maximum.single || Infinity,
multi: maximum.multi || Infinity
};
function generateFixFunction(line, max) {
const sourceCode = context.sourceCode;
const output = [];
const front = line[0].range[0];
const back = line[line.length - 1].range[1];
for (let i = 0; i < line.length; i += max) {
const nodes = line.slice(i, i + max);
output.push(nodes.reduce((prev, curr) => {
if (prev === "") return sourceCode.getText(curr);
return `${prev} ${sourceCode.getText(curr)}`;
}, ""));
}
const code = output.join("\n");
return function fix(fixer) {
return fixer.replaceTextRange([front, back], code);
};
}
return { JSXOpeningElement(node) {
if (!node.attributes.length) return;
const isSingleLineTag = isSingleLine(node);
if ((isSingleLineTag ? maxConfig.single : maxConfig.multi) === Infinity) return;
const firstProp = node.attributes[0];
const linePartitionedProps = [[firstProp]];
node.attributes.reduce((last, decl) => {
if ((0, ast_exports.isTokenOnSameLine)(last, decl)) linePartitionedProps[linePartitionedProps.length - 1].push(decl);
else linePartitionedProps.push([decl]);
return decl;
});
linePartitionedProps.forEach((propsInLine) => {
const maxPropsCountPerLine = isSingleLineTag && propsInLine[0].loc.start.line === node.loc.start.line ? maxConfig.single : maxConfig.multi;
if (propsInLine.length > maxPropsCountPerLine) {
const name = getPropName(context, propsInLine[maxPropsCountPerLine]);
context.report({
messageId: "newLine",
node: propsInLine[maxPropsCountPerLine],
data: { prop: name },
fix: generateFixFunction(propsInLine, maxPropsCountPerLine)
});
}
});
} };
}
});
export { jsx_max_props_per_line_default };

View File

@@ -0,0 +1,95 @@
import { createRule, isSingleLine } from "../utils.js";
const messages = {
require: "JSX element should start in a new line",
prevent: "JSX element should not start in a new line",
allowMultilines: "Multiline JSX elements should start in a new line"
};
var jsx_newline_default = createRule({
name: "jsx-newline",
package: "jsx",
meta: {
type: "layout",
docs: { description: "Require or prevent a new line after jsx elements and expressions." },
fixable: "code",
messages,
schema: [{
type: "object",
properties: {
prevent: {
default: false,
type: "boolean"
},
allowMultilines: {
default: false,
type: "boolean"
}
},
additionalProperties: false,
if: { properties: { allowMultilines: { const: true } } },
then: {
properties: { prevent: { const: true } },
required: ["prevent"]
}
}]
},
create(context) {
const jsxElementParents = /* @__PURE__ */ new Set();
const sourceCode = context.sourceCode;
function isBlockCommentInCurlyBraces(element) {
const elementRawValue = sourceCode.getText(element);
return /^\s*\{\/\*/.test(elementRawValue);
}
function isNonBlockComment(element) {
return !isBlockCommentInCurlyBraces(element) && (element.type === "JSXElement" || element.type === "JSXExpressionContainer");
}
return {
"Program:exit": function() {
jsxElementParents.forEach((parent) => {
parent.children.forEach((element, index, elements) => {
if (element.type === "JSXElement" || element.type === "JSXExpressionContainer") {
const configuration = context.options[0] || {};
const prevent = configuration.prevent || false;
const allowMultilines = configuration.allowMultilines || false;
const firstAdjacentSibling = elements[index + 1];
const secondAdjacentSibling = elements[index + 2];
const hasSibling = firstAdjacentSibling && secondAdjacentSibling && (firstAdjacentSibling.type === "Literal" || firstAdjacentSibling.type === "JSXText");
if (!hasSibling) return;
const isWithoutNewLine = !/\n\s*\n/.test(firstAdjacentSibling.value);
if (isBlockCommentInCurlyBraces(element)) return;
const nextNonBlockComment = elements.slice(index + 2).find(isNonBlockComment);
if (allowMultilines && (!isSingleLine(element) || nextNonBlockComment && !isSingleLine(nextNonBlockComment))) {
if (!isWithoutNewLine) return;
const regex$1 = /(\n)(?!.*\1)/g;
const replacement$1 = "\n\n";
const messageId$1 = "allowMultilines";
context.report({
messageId: messageId$1,
node: secondAdjacentSibling,
fix(fixer) {
return fixer.replaceText(firstAdjacentSibling, sourceCode.getText(firstAdjacentSibling).replace(regex$1, replacement$1));
}
});
return;
}
if (isWithoutNewLine === prevent) return;
const messageId = prevent ? "prevent" : "require";
const regex = prevent ? /(\n\n)(?!.*\1)/g : /(\n)(?!.*\1)/g;
const replacement = prevent ? "\n" : "\n\n";
context.report({
messageId,
node: secondAdjacentSibling,
fix(fixer) {
return fixer.replaceText(firstAdjacentSibling, sourceCode.getText(firstAdjacentSibling).replace(regex, replacement));
}
});
}
});
});
},
":matches(JSXElement, JSXFragment) > :matches(JSXElement, JSXExpressionContainer)": (node) => {
jsxElementParents.add(node.parent);
}
};
}
});
export { jsx_newline_default };

View File

@@ -0,0 +1,161 @@
import { createRule, isWhiteSpaces } from "../utils.js";
const optionDefaults = { allow: "none" };
const messages = { moveToNewLine: "`{{descriptor}}` must be placed on a new line" };
var jsx_one_expression_per_line_default = createRule({
name: "jsx-one-expression-per-line",
meta: {
type: "layout",
docs: { description: "Require one JSX element per line" },
fixable: "whitespace",
messages,
schema: [{
type: "object",
properties: { allow: {
type: "string",
enum: [
"none",
"literal",
"single-child",
"single-line",
"non-jsx"
]
} },
default: optionDefaults,
additionalProperties: false
}]
},
create(context) {
const options = Object.assign({}, optionDefaults, context.options[0]);
function nodeKey(node) {
return `${node.loc.start.line},${node.loc.start.column}`;
}
function nodeDescriptor(n) {
return "openingElement" in n && n.openingElement && "name" in n.openingElement.name ? String(n.openingElement.name.name) : context.sourceCode.getText(n).replace(/\n/g, "");
}
function report(node, fix) {
context.report({
messageId: "moveToNewLine",
node,
data: { descriptor: nodeDescriptor(node) },
fix
});
}
function handleJSX(node) {
const children = node.children;
if (!children || !children.length) return;
if (options.allow === "non-jsx" && !children.some((child) => child.type === "JSXFragment" || child.type === "JSXElement")) return;
const isFragment = node.type === "JSXFragment";
const openingElement = isFragment ? node.openingFragment : node.openingElement;
const closingElement = isFragment ? node.closingFragment : node.closingElement;
const openingElementStartLine = openingElement.loc.start.line;
const openingElementEndLine = openingElement.loc.end.line;
const closingElementStartLine = closingElement.loc.start.line;
const closingElementEndLine = closingElement.loc.end.line;
if (children.length === 1) {
const child = children[0];
if (openingElementStartLine === openingElementEndLine && openingElementEndLine === closingElementStartLine && closingElementStartLine === closingElementEndLine && closingElementEndLine === child.loc.start.line && child.loc.start.line === child.loc.end.line) {
if (options.allow === "single-child" || options.allow === "literal" && (child.type === "Literal" || child.type === "JSXText") || options.allow === "single-line") return;
}
}
if (options.allow === "single-line") {
const firstChild = children[0];
const lastChild = children[children.length - 1];
const lineDifference = lastChild.loc.end.line - firstChild.loc.start.line;
let lineBreaks = 0;
if (firstChild.type === "Literal" || firstChild.type === "JSXText") {
if (/^\s*?\n/.test(firstChild.raw)) lineBreaks += 1;
}
if (lastChild.type === "Literal" || lastChild.type === "JSXText") {
if (/\n\s*$/.test(lastChild.raw)) lineBreaks += 1;
}
if (lineDifference === 0 && lineBreaks === 0 || lineDifference === 2 && lineBreaks === 2) return;
}
const childrenGroupedByLine = {};
const fixDetailsByNode = {};
children.forEach((child) => {
let countNewLinesBeforeContent = 0;
let countNewLinesAfterContent = 0;
if (child.type === "Literal" || child.type === "JSXText") {
if (isWhiteSpaces(child.raw)) return;
countNewLinesBeforeContent = (child.raw.match(/^\s*\n/g) || []).length;
countNewLinesAfterContent = (child.raw.match(/\n\s*$/g) || []).length;
}
const startLine = child.loc.start.line + countNewLinesBeforeContent;
const endLine = child.loc.end.line - countNewLinesAfterContent;
if (startLine === endLine) {
if (!childrenGroupedByLine[startLine]) childrenGroupedByLine[startLine] = [];
childrenGroupedByLine[startLine].push(child);
} else {
if (!childrenGroupedByLine[startLine]) childrenGroupedByLine[startLine] = [];
childrenGroupedByLine[startLine].push(child);
if (!childrenGroupedByLine[endLine]) childrenGroupedByLine[endLine] = [];
childrenGroupedByLine[endLine].push(child);
}
});
const lines = Object.keys(childrenGroupedByLine);
if (lines.length === 1 && options.allow === "single-line") {
const line = parseInt(lines[0]);
const children$1 = childrenGroupedByLine[line];
const firstChild = children$1[0];
if (line === openingElementEndLine) report(firstChild, (fixer) => fixer.insertTextBefore(firstChild, "\n"));
const lastChild = children$1.at(-1);
if (line === closingElementStartLine) report(lastChild, (fixer) => fixer.insertTextAfter(lastChild, "\n"));
} else {
lines.forEach((_line) => {
const line = parseInt(_line, 10);
const firstIndex = 0;
const lastIndex = childrenGroupedByLine[line].length - 1;
childrenGroupedByLine[line].forEach((child, i) => {
let prevChild;
let nextChild;
if (i === firstIndex) {
if (line === openingElementEndLine) prevChild = openingElement;
} else prevChild = childrenGroupedByLine[line][i - 1];
if (i === lastIndex) {
if (line === closingElementStartLine) nextChild = closingElement;
}
if (!prevChild && !nextChild) return;
const spaceBetweenPrev = () => {
const tokenBetweenNodes = context.sourceCode.getTokensBetween(prevChild, child)[0];
return (prevChild.type === "Literal" || prevChild.type === "JSXText") && prevChild.raw.endsWith(" ") || (child.type === "Literal" || child.type === "JSXText") && child.raw.startsWith(" ") || isWhiteSpaces(tokenBetweenNodes?.value);
};
const spaceBetweenNext = () => {
const tokenBetweenNodes = context.sourceCode.getTokensBetween(child, nextChild)[0];
return (nextChild.type === "Literal" || nextChild.type === "JSXText") && nextChild.raw.startsWith(" ") || (child.type === "Literal" || child.type === "JSXText") && child.raw.endsWith(" ") || isWhiteSpaces(tokenBetweenNodes?.value);
};
const source = context.sourceCode.getText(child);
const leadingSpace = !!(prevChild && spaceBetweenPrev());
const trailingSpace = !!(nextChild && spaceBetweenNext());
const leadingNewLine = !!prevChild;
const trailingNewLine = !!nextChild;
const key = nodeKey(child);
if (!fixDetailsByNode[key]) fixDetailsByNode[key] = {
node: child,
source
};
if (leadingSpace) fixDetailsByNode[key].leadingSpace = true;
if (leadingNewLine) fixDetailsByNode[key].leadingNewLine = true;
if (trailingNewLine) fixDetailsByNode[key].trailingNewLine = true;
if (trailingSpace) fixDetailsByNode[key].trailingSpace = true;
});
});
Object.keys(fixDetailsByNode).forEach((key) => {
const details = fixDetailsByNode[key];
const nodeToReport = details.node;
const source = details.source.replace(/(^ +| +$)/g, "");
const leadingSpaceString = details.leadingSpace ? "\n{' '}" : "";
const trailingSpaceString = details.trailingSpace ? "{' '}\n" : "";
const leadingNewLineString = details.leadingNewLine ? "\n" : "";
const trailingNewLineString = details.trailingNewLine ? "\n" : "";
const replaceText = `${leadingSpaceString}${leadingNewLineString}${source}${trailingNewLineString}${trailingSpaceString}`;
report(nodeToReport, (fixer) => fixer.replaceText(nodeToReport, replaceText));
});
}
}
return {
JSXElement: handleJSX,
JSXFragment: handleJSX
};
}
});
export { jsx_one_expression_per_line_default };

View File

@@ -0,0 +1,93 @@
import { createRule, getElementType, isDOMComponent } from "../utils.js";
import picomatch from "picomatch";
function testDigit(char) {
const charCode = char.charCodeAt(0);
return charCode >= 48 && charCode <= 57;
}
function testUpperCase(char) {
const upperCase = char.toUpperCase();
return char === upperCase && upperCase !== char.toLowerCase();
}
function testLowerCase(char) {
const lowerCase = char.toLowerCase();
return char === lowerCase && lowerCase !== char.toUpperCase();
}
function testPascalCase(name) {
if (!testUpperCase(name.charAt(0))) return false;
const anyNonAlphaNumeric = Array.prototype.some.call(name.slice(1), (char) => char.toLowerCase() === char.toUpperCase() && !testDigit(char));
if (anyNonAlphaNumeric) return false;
return Array.prototype.some.call(name.slice(1), (char) => testLowerCase(char) || testDigit(char));
}
function testAllCaps(name) {
const firstChar = name.charAt(0);
if (!(testUpperCase(firstChar) || testDigit(firstChar))) return false;
for (let i = 1; i < name.length - 1; i += 1) {
const char = name.charAt(i);
if (!(testUpperCase(char) || testDigit(char) || char === "_")) return false;
}
const lastChar = name.charAt(name.length - 1);
if (!(testUpperCase(lastChar) || testDigit(lastChar))) return false;
return true;
}
const messages = {
usePascalCase: "Imported JSX component {{name}} must be in PascalCase",
usePascalOrSnakeCase: "Imported JSX component {{name}} must be in PascalCase or SCREAMING_SNAKE_CASE"
};
var jsx_pascal_case_default = createRule({
name: "jsx-pascal-case",
meta: {
type: "suggestion",
docs: { description: "Enforce PascalCase for user-defined JSX components" },
messages,
schema: [{
type: "object",
properties: {
allowAllCaps: { type: "boolean" },
allowLeadingUnderscore: { type: "boolean" },
allowNamespace: { type: "boolean" },
ignore: {
items: { type: "string" },
type: "array",
uniqueItems: true
}
},
additionalProperties: false
}]
},
create(context) {
const configuration = context.options[0] || {};
const { allowAllCaps = false, allowLeadingUnderscore = false, allowNamespace = false, ignore = [] } = configuration;
const isMatchIgnore = picomatch(ignore, { noglobstar: true });
function ignoreCheck(name) {
return isMatchIgnore(name) || ignore.includes(name);
}
return { JSXOpeningElement(node) {
const isCompatTag = isDOMComponent(node);
if (isCompatTag) return;
const name = getElementType(node);
let checkNames = [name];
let index = 0;
if (name.includes(":")) checkNames = name.split(":");
else if (name.includes(".")) checkNames = name.split(".");
do {
const splitName = checkNames[index];
if (splitName.length === 1) return;
const isIgnored = ignoreCheck(splitName);
const checkName = allowLeadingUnderscore && splitName.startsWith("_") ? splitName.slice(1) : splitName;
const isPascalCase = testPascalCase(checkName);
const isAllowedAllCaps = allowAllCaps && testAllCaps(checkName);
if (!isPascalCase && !isAllowedAllCaps && !isIgnored) {
const messageId = allowAllCaps ? "usePascalOrSnakeCase" : "usePascalCase";
context.report({
messageId,
node,
data: { name: splitName }
});
break;
}
index += 1;
} while (index < checkNames.length && !allowNamespace);
} };
}
});
export { jsx_pascal_case_default };

View File

@@ -0,0 +1,86 @@
import { createRule } from "../utils.js";
const messages = {
noLineGap: "Expected no line gap between “{{prop1}}” and “{{prop2}}”",
onlyOneSpace: "Expected only one space between “{{prop1}}” and “{{prop2}}”"
};
var jsx_props_no_multi_spaces_default = createRule({
name: "jsx-props-no-multi-spaces",
meta: {
type: "layout",
docs: { description: "Disallow multiple spaces between inline JSX props. Deprecated, use `no-multi-spaces` rule instead." },
deprecated: {
message: "The rule was replaced with a more general rule.",
deprecatedSince: "5.0.0",
replacedBy: [{ rule: {
name: "no-multi-spaces",
url: "https://eslint.style/rules/no-multi-spaces"
} }]
},
fixable: "code",
messages,
schema: []
},
create(context) {
const sourceCode = context.sourceCode;
function getPropName(propNode) {
switch (propNode.type) {
case "JSXSpreadAttribute": return sourceCode.getText(propNode.argument);
case "JSXIdentifier": return propNode.name;
case "JSXMemberExpression": return `${getPropName(propNode.object)}.${propNode.property.name}`;
default: return propNode.name ? propNode.name.name : `${sourceCode.getText(propNode.object)}.${propNode.property.name}`;
}
}
function hasEmptyLines(first, second) {
const comments = sourceCode.getCommentsBefore ? sourceCode.getCommentsBefore(second) : [];
const nodes = [].concat(first, comments, second);
for (let i = 1; i < nodes.length; i += 1) {
const prev = nodes[i - 1];
const curr = nodes[i];
if (curr.loc.start.line - prev.loc.end.line >= 2) return true;
}
return false;
}
function checkSpacing(prev, node) {
if (hasEmptyLines(prev, node)) context.report({
messageId: "noLineGap",
node,
data: {
prop1: getPropName(prev),
prop2: getPropName(node)
}
});
if (prev.loc.end.line !== node.loc.end.line) return;
const between = sourceCode.text.slice(prev.range[1], node.range[0]);
if (between !== " ") context.report({
node,
messageId: "onlyOneSpace",
data: {
prop1: getPropName(prev),
prop2: getPropName(node)
},
fix(fixer) {
return fixer.replaceTextRange([prev.range[1], node.range[0]], " ");
}
});
}
function containsGenericType(node) {
const containsTypeParams = typeof node.typeArguments !== "undefined";
return containsTypeParams && node.typeArguments?.type === "TSTypeParameterInstantiation";
}
function getGenericNode(node) {
const name = node.name;
if (containsGenericType(node)) {
const type = node.typeArguments;
return Object.assign({}, node, { range: [name.range[0], type?.range[1]] });
}
return name;
}
return { JSXOpeningElement(node) {
node.attributes.reduce((prev, prop) => {
checkSpacing(prev, prop);
return prop;
}, getGenericNode(node));
} };
}
});
export { jsx_props_no_multi_spaces_default };

View File

@@ -0,0 +1,49 @@
import { createRule, isStringLiteral, isSurroundedBy } from "../utils.js";
const QUOTE_SETTINGS = {
"prefer-double": {
quote: "\"",
description: "singlequote",
convert(str) {
return str.replace(/'/gu, "\"");
}
},
"prefer-single": {
quote: "'",
description: "doublequote",
convert(str) {
return str.replace(/"/gu, "'");
}
}
};
var jsx_quotes_default = createRule({
name: "jsx-quotes",
meta: {
type: "layout",
docs: { description: "Enforce the consistent use of either double or single quotes in JSX attributes" },
fixable: "whitespace",
schema: [{
type: "string",
enum: ["prefer-single", "prefer-double"]
}],
messages: { unexpected: "Unexpected usage of {{description}}." }
},
create(context) {
const quoteOption = context.options[0] || "prefer-double";
const setting = QUOTE_SETTINGS[quoteOption];
function usesExpectedQuotes(node) {
return node.value.includes(setting.quote) || isSurroundedBy(node.raw, setting.quote);
}
return { JSXAttribute(node) {
const attributeValue = node.value;
if (attributeValue && isStringLiteral(attributeValue) && !usesExpectedQuotes(attributeValue)) context.report({
node: attributeValue,
messageId: "unexpected",
data: { description: setting.description },
fix(fixer) {
return fixer.replaceText(attributeValue, setting.convert(attributeValue.raw));
}
});
} };
}
});
export { jsx_quotes_default };

View File

@@ -0,0 +1,59 @@
import { createRule, isDOMComponent } from "../utils.js";
const optionDefaults = {
component: true,
html: true
};
const messages = { notSelfClosing: "Empty components are self-closing" };
var jsx_self_closing_comp_default = createRule({
name: "jsx-self-closing-comp",
meta: {
type: "layout",
docs: { description: "Disallow extra closing tags for components without children" },
fixable: "code",
messages,
schema: [{
type: "object",
properties: {
component: {
default: optionDefaults.component,
type: "boolean"
},
html: {
default: optionDefaults.html,
type: "boolean"
}
},
additionalProperties: false
}]
},
create(context) {
function isComponent(node) {
return node.name && (node.name.type === "JSXIdentifier" || node.name.type === "JSXMemberExpression") && !isDOMComponent(node);
}
function childrenIsEmpty(node) {
return node.parent.children.length === 0;
}
function childrenIsMultilineSpaces(node) {
const childrens = node.parent.children;
return childrens.length === 1 && childrens[0].type === "JSXText" && childrens[0].value.includes("\n") && childrens[0].value.replace(/(?!\xA0)\s/g, "") === "";
}
function isShouldBeSelfClosed(node) {
const configuration = Object.assign({}, optionDefaults, context.options[0]);
return (configuration.component && isComponent(node) || configuration.html && isDOMComponent(node)) && !node.selfClosing && (childrenIsEmpty(node) || childrenIsMultilineSpaces(node));
}
return { JSXOpeningElement(node) {
if (!isShouldBeSelfClosed(node)) return;
context.report({
messageId: "notSelfClosing",
node,
fix(fixer) {
const openingElementEnding = node.range[1] - 1;
const closingElementEnding = node.parent.closingElement?.range[1] ?? NaN;
const range = [openingElementEnding, closingElementEnding];
return fixer.replaceTextRange(range, " />");
}
});
} };
}
});
export { jsx_self_closing_comp_default };

View File

@@ -0,0 +1,372 @@
import { createRule, getPropName, isDOMComponent, isSingleLine } from "../utils.js";
function isCallbackPropName(name) {
return /^on[A-Z]/.test(name);
}
const messages = {
listIsEmpty: "A customized reserved first list must not be empty",
listReservedPropsFirst: "Reserved props must be listed before all other props",
listReservedPropsLast: "Reserved props must be listed after all other props",
listCallbacksLast: "Callbacks must be listed after all other props",
listShorthandFirst: "Shorthand props must be listed before all other props",
listShorthandLast: "Shorthand props must be listed after all other props",
listMultilineFirst: "Multiline props must be listed before all other props",
listMultilineLast: "Multiline props must be listed after all other props",
sortPropsByAlpha: "Props should be sorted alphabetically"
};
const RESERVED_PROPS_LIST = [
"children",
"dangerouslySetInnerHTML",
"key",
"ref"
];
function getReservedPropIndex(name, list) {
return list.indexOf(name.split(":")[0]);
}
let attributeMap;
function shouldSortToEnd(node) {
const attr = attributeMap.get(node);
return !!attr && !!attr.hasComment;
}
function contextCompare(a, b, options) {
let aProp = getPropName(a);
let bProp = getPropName(b);
const aPropNamespace = aProp.split(":")[0];
const bPropNamespace = bProp.split(":")[0];
const aSortToEnd = shouldSortToEnd(a);
const bSortToEnd = shouldSortToEnd(b);
if (aSortToEnd && !bSortToEnd) return 1;
if (!aSortToEnd && bSortToEnd) return -1;
if (options.reservedFirst) {
const aIndex = getReservedPropIndex(aProp, options.reservedList);
const bIndex = getReservedPropIndex(bProp, options.reservedList);
if (aIndex > -1 && bIndex === -1) return -1;
if (aIndex === -1 && bIndex > -1) return 1;
if (aIndex > -1 && bIndex > -1 && aPropNamespace !== bPropNamespace) return aIndex > bIndex ? 1 : -1;
}
if (options.reservedLast.length > 0) {
const aLastIndex = getReservedPropIndex(aProp, options.reservedLast);
const bLastIndex = getReservedPropIndex(bProp, options.reservedLast);
if (aLastIndex > -1 && bLastIndex === -1) return 1;
if (aLastIndex === -1 && bLastIndex > -1) return -1;
if (aLastIndex > -1 && bLastIndex > -1 && aPropNamespace !== bPropNamespace) return aLastIndex > bLastIndex ? -1 : 1;
}
if (options.callbacksLast) {
const aIsCallback = isCallbackPropName(aProp);
const bIsCallback = isCallbackPropName(bProp);
if (aIsCallback && !bIsCallback) return 1;
if (!aIsCallback && bIsCallback) return -1;
}
if (options.shorthandFirst || options.shorthandLast) {
const shorthandSign = options.shorthandFirst ? -1 : 1;
if (!a.value && b.value) return shorthandSign;
if (a.value && !b.value) return -shorthandSign;
}
if (options.multiline !== "ignore") {
const multilineSign = options.multiline === "first" ? -1 : 1;
const aIsMultiline = !isSingleLine(a);
const bIsMultiline = !isSingleLine(b);
if (aIsMultiline && !bIsMultiline) return multilineSign;
if (!aIsMultiline && bIsMultiline) return -multilineSign;
}
if (options.noSortAlphabetically) return 0;
const actualLocale = options.locale === "auto" ? void 0 : options.locale;
if (options.ignoreCase) {
aProp = aProp.toLowerCase();
bProp = bProp.toLowerCase();
return aProp.localeCompare(bProp, actualLocale);
}
if (aProp === bProp) return 0;
if (options.locale === "auto") return aProp < bProp ? -1 : 1;
return aProp.localeCompare(bProp, actualLocale);
}
function getGroupsOfSortableAttributes(attributes, context) {
const sourceCode = context.sourceCode;
const sortableAttributeGroups = [];
let groupCount = 0;
function addtoSortableAttributeGroups(attribute) {
sortableAttributeGroups[groupCount - 1].push(attribute);
}
for (let i = 0; i < attributes.length; i++) {
const attribute = attributes[i];
const nextAttribute = attributes[i + 1];
const attributeline = attribute.loc.start.line;
let comment = [];
try {
comment = sourceCode.getCommentsAfter(attribute);
} catch {}
const lastAttr = attributes[i - 1];
const attrIsSpread = attribute.type === "JSXSpreadAttribute";
if (!lastAttr || lastAttr.type === "JSXSpreadAttribute" && !attrIsSpread) {
groupCount += 1;
sortableAttributeGroups[groupCount - 1] = [];
}
if (!attrIsSpread) if (comment.length === 0) {
attributeMap.set(attribute, {
end: attribute.range[1],
hasComment: false
});
addtoSortableAttributeGroups(attribute);
} else {
const firstComment = comment[0];
const commentline = firstComment.loc.start.line;
if (comment.length === 1) {
if (attributeline + 1 === commentline && nextAttribute) {
attributeMap.set(attribute, {
end: nextAttribute.range[1],
hasComment: true
});
addtoSortableAttributeGroups(attribute);
i += 1;
} else if (attributeline === commentline) {
if (firstComment.type === "Block" && nextAttribute) {
attributeMap.set(attribute, {
end: nextAttribute.range[1],
hasComment: true
});
i += 1;
} else if (firstComment.type === "Block") attributeMap.set(attribute, {
end: firstComment.range[1],
hasComment: true
});
else attributeMap.set(attribute, {
end: firstComment.range[1],
hasComment: false
});
addtoSortableAttributeGroups(attribute);
}
} else if (comment.length > 1 && attributeline + 1 === comment[1].loc.start.line && nextAttribute) {
const commentNextAttribute = sourceCode.getCommentsAfter(nextAttribute);
attributeMap.set(attribute, {
end: nextAttribute.range[1],
hasComment: true
});
if (commentNextAttribute.length === 1 && nextAttribute.loc.start.line === commentNextAttribute[0].loc.start.line) attributeMap.set(attribute, {
end: commentNextAttribute[0].range[1],
hasComment: true
});
addtoSortableAttributeGroups(attribute);
i += 1;
}
}
}
return sortableAttributeGroups;
}
function generateFixerFunction(node, context, reservedList) {
const sourceCode = context.sourceCode;
const attributes = node.attributes.slice(0);
const configuration = context.options[0] || {};
const ignoreCase = configuration.ignoreCase || false;
const callbacksLast = configuration.callbacksLast || false;
const shorthandFirst = configuration.shorthandFirst || false;
const shorthandLast = configuration.shorthandLast || false;
const multiline = configuration.multiline || "ignore";
const noSortAlphabetically = configuration.noSortAlphabetically || false;
const reservedFirst = configuration.reservedFirst || false;
const reservedLast = configuration.reservedLast || [];
const locale = configuration.locale || "auto";
const options = {
ignoreCase,
callbacksLast,
shorthandFirst,
shorthandLast,
multiline,
noSortAlphabetically,
reservedFirst,
reservedList,
reservedLast,
locale
};
const sortableAttributeGroups = getGroupsOfSortableAttributes(attributes, context);
const sortedAttributeGroups = sortableAttributeGroups.slice(0).map((group) => [...group].sort((a, b) => contextCompare(a, b, options)));
return function fixFunction(fixer) {
const fixers = [];
let source = sourceCode.getText();
sortableAttributeGroups.forEach((sortableGroup, ii) => {
sortableGroup.forEach((attr, jj) => {
const sortedAttr = sortedAttributeGroups[ii][jj];
const sortedAttrText = source.slice(sortedAttr.range[0], attributeMap.get(sortedAttr).end);
fixers.push({
range: [attr.range[0], attributeMap.get(attr).end],
text: sortedAttrText
});
});
});
fixers.sort((a, b) => b.range[0] - a.range[0]);
const firstFixer = fixers[0];
const lastFixer = fixers[fixers.length - 1];
const rangeStart = lastFixer ? lastFixer.range[0] : 0;
const rangeEnd = firstFixer ? firstFixer.range[1] : -0;
fixers.forEach((fix) => {
source = `${source.slice(0, fix.range[0])}${fix.text}${source.slice(fix.range[1])}`;
});
return fixer.replaceTextRange([rangeStart, rangeEnd], source.slice(rangeStart, rangeEnd));
};
}
function validateReservedFirstConfig(context, reservedFirst) {
if (reservedFirst) {
if (Array.isArray(reservedFirst)) {
if (reservedFirst.length === 0) return function Report(decl) {
context.report({
node: decl,
messageId: "listIsEmpty"
});
};
}
}
}
const reportedNodeAttributes = /* @__PURE__ */ new WeakMap();
function reportNodeAttribute(nodeAttribute, errorType, node, context, reservedList) {
const errors = reportedNodeAttributes.get(nodeAttribute) || [];
if (errors.includes(errorType)) return;
errors.push(errorType);
reportedNodeAttributes.set(nodeAttribute, errors);
context.report({
node: nodeAttribute.name ?? "",
messageId: errorType,
fix: generateFixerFunction(node, context, reservedList)
});
}
var jsx_sort_props_default = createRule({
name: "jsx-sort-props",
meta: {
type: "layout",
docs: { description: "Enforce props alphabetical sorting" },
fixable: "code",
messages,
schema: [{
type: "object",
properties: {
callbacksLast: { type: "boolean" },
shorthandFirst: { type: "boolean" },
shorthandLast: { type: "boolean" },
multiline: {
type: "string",
enum: [
"ignore",
"first",
"last"
],
default: "ignore"
},
ignoreCase: { type: "boolean" },
noSortAlphabetically: { type: "boolean" },
reservedFirst: { oneOf: [{
type: "array",
items: { type: "string" }
}, { type: "boolean" }] },
reservedLast: {
type: "array",
items: { type: "string" }
},
locale: {
type: "string",
default: "auto"
}
},
additionalProperties: false
}]
},
create(context) {
const configuration = context.options[0] || {};
const ignoreCase = configuration.ignoreCase || false;
const callbacksLast = configuration.callbacksLast || false;
const shorthandFirst = configuration.shorthandFirst || false;
const shorthandLast = configuration.shorthandLast || false;
const multiline = configuration.multiline || "ignore";
const noSortAlphabetically = configuration.noSortAlphabetically || false;
const reservedFirst = configuration.reservedFirst || false;
const reservedFirstError = validateReservedFirstConfig(context, reservedFirst);
const reservedList = Array.isArray(reservedFirst) ? reservedFirst : RESERVED_PROPS_LIST;
const reservedLastList = configuration.reservedLast || [];
const locale = configuration.locale || "auto";
return {
Program() {
attributeMap = /* @__PURE__ */ new WeakMap();
},
JSXOpeningElement(node) {
const nodeReservedList = reservedFirst && !isDOMComponent(node) ? reservedList.filter((prop) => prop !== "dangerouslySetInnerHTML") : reservedList;
node.attributes.reduce((memo, decl, idx, attrs) => {
if (decl.type === "JSXSpreadAttribute") return attrs[idx + 1];
let previousPropName = getPropName(memo);
let currentPropName = getPropName(decl);
const previousReservedNamespace = previousPropName.split(":")[0];
const currentReservedNamespace = currentPropName.split(":")[0];
const previousValue = memo.value;
const currentValue = decl.value;
const previousIsCallback = isCallbackPropName(previousPropName);
const currentIsCallback = isCallbackPropName(currentPropName);
if (ignoreCase) {
previousPropName = previousPropName.toLowerCase();
currentPropName = currentPropName.toLowerCase();
}
if (reservedFirst) {
if (reservedFirstError) {
reservedFirstError(decl);
return memo;
}
const previousReservedIndex = getReservedPropIndex(previousPropName, nodeReservedList);
const currentReservedIndex = getReservedPropIndex(currentPropName, nodeReservedList);
if (previousReservedIndex > -1 && currentReservedIndex === -1) return decl;
if (reservedFirst !== true && previousReservedIndex > currentReservedIndex || previousReservedIndex === -1 && currentReservedIndex > -1) {
reportNodeAttribute(decl, "listReservedPropsFirst", node, context, nodeReservedList);
return memo;
}
if (previousReservedIndex > -1 && currentReservedIndex > -1 && currentReservedIndex > previousReservedIndex && previousReservedNamespace !== currentReservedNamespace) return decl;
}
if (reservedLastList.length > 0) {
const previousReservedIndex = getReservedPropIndex(previousPropName, reservedLastList);
const currentReservedIndex = getReservedPropIndex(currentPropName, reservedLastList);
if (previousReservedIndex === -1 && currentReservedIndex > -1) return decl;
if (previousReservedIndex < currentReservedIndex || previousReservedIndex > -1 && currentReservedIndex === -1) {
reportNodeAttribute(decl, "listReservedPropsLast", node, context, nodeReservedList);
return memo;
}
if (previousReservedIndex > -1 && currentReservedIndex > -1 && currentReservedIndex > previousReservedIndex && previousReservedNamespace !== currentReservedNamespace) return decl;
}
if (callbacksLast) {
if (!previousIsCallback && currentIsCallback) return decl;
if (previousIsCallback && !currentIsCallback) {
reportNodeAttribute(memo, "listCallbacksLast", node, context, nodeReservedList);
return memo;
}
}
if (shorthandFirst) {
if (currentValue && !previousValue) return decl;
if (!currentValue && previousValue) {
reportNodeAttribute(decl, "listShorthandFirst", node, context, nodeReservedList);
return memo;
}
}
if (shorthandLast) {
if (!currentValue && previousValue) return decl;
if (currentValue && !previousValue) {
reportNodeAttribute(memo, "listShorthandLast", node, context, nodeReservedList);
return memo;
}
}
const previousIsMultiline = !isSingleLine(memo);
const currentIsMultiline = !isSingleLine(decl);
if (multiline === "first") {
if (previousIsMultiline && !currentIsMultiline) return decl;
if (!previousIsMultiline && currentIsMultiline) {
reportNodeAttribute(decl, "listMultilineFirst", node, context, nodeReservedList);
return memo;
}
} else if (multiline === "last") {
if (!previousIsMultiline && currentIsMultiline) return decl;
if (previousIsMultiline && !currentIsMultiline) {
reportNodeAttribute(memo, "listMultilineLast", node, context, nodeReservedList);
return memo;
}
}
if (!noSortAlphabetically && (ignoreCase || locale !== "auto" ? previousPropName.localeCompare(currentPropName, locale === "auto" ? void 0 : locale) > 0 : previousPropName > currentPropName)) {
reportNodeAttribute(decl, "sortPropsByAlpha", node, context, nodeReservedList);
return memo;
}
return decl;
}, node.attributes[0]);
}
};
}
});
export { jsx_sort_props_default };

View File

@@ -0,0 +1,269 @@
import { ast_exports, createRule, getTokenBeforeClosingBracket, isSingleLine } from "../utils.js";
const messages = {
selfCloseSlashNoSpace: "Whitespace is forbidden between `/` and `>`; write `/>`",
selfCloseSlashNeedSpace: "Whitespace is required between `/` and `>`; write `/ >`",
closeSlashNoSpace: "Whitespace is forbidden between `<` and `/`; write `</`",
closeSlashNeedSpace: "Whitespace is required between `<` and `/`; write `< /`",
beforeSelfCloseNoSpace: "A space is forbidden before closing bracket",
beforeSelfCloseNeedSpace: "A space is required before closing bracket",
beforeSelfCloseNeedNewline: "A newline is required before closing bracket",
afterOpenNoSpace: "A space is forbidden after opening bracket",
afterOpenNeedSpace: "A space is required after opening bracket",
beforeCloseNoSpace: "A space is forbidden before closing bracket",
beforeCloseNeedSpace: "Whitespace is required before closing bracket",
beforeCloseNeedNewline: "A newline is required before closing bracket"
};
function validateClosingSlash(context, node, option) {
const sourceCode = context.sourceCode;
let adjacent;
if ("selfClosing" in node && node.selfClosing) {
const lastTokens = sourceCode.getLastTokens(node, 2);
adjacent = !sourceCode.isSpaceBetween(lastTokens[0], lastTokens[1]);
if (option === "never") {
if (!adjacent) context.report({
node,
messageId: "selfCloseSlashNoSpace",
loc: {
start: lastTokens[0].loc.start,
end: lastTokens[1].loc.end
},
fix(fixer) {
return fixer.removeRange([lastTokens[0].range[1], lastTokens[1].range[0]]);
}
});
} else if (option === "always" && adjacent) context.report({
node,
messageId: "selfCloseSlashNeedSpace",
loc: {
start: lastTokens[0].loc.start,
end: lastTokens[1].loc.end
},
fix(fixer) {
return fixer.insertTextBefore(lastTokens[1], " ");
}
});
} else {
const firstTokens = sourceCode.getFirstTokens(node, 2);
adjacent = !sourceCode.isSpaceBetween(firstTokens[0], firstTokens[1]);
if (option === "never") {
if (!adjacent) context.report({
node,
messageId: "closeSlashNoSpace",
loc: {
start: firstTokens[0].loc.start,
end: firstTokens[1].loc.end
},
fix(fixer) {
return fixer.removeRange([firstTokens[0].range[1], firstTokens[1].range[0]]);
}
});
} else if (option === "always" && adjacent) context.report({
node,
messageId: "closeSlashNeedSpace",
loc: {
start: firstTokens[0].loc.start,
end: firstTokens[1].loc.end
},
fix(fixer) {
return fixer.insertTextBefore(firstTokens[1], " ");
}
});
}
}
function validateBeforeSelfClosing(context, node, option) {
const sourceCode = context.sourceCode;
const leftToken = getTokenBeforeClosingBracket(node);
const closingSlash = sourceCode.getTokenAfter(leftToken);
if (!isSingleLine(node) && option === "proportional-always") {
if ((0, ast_exports.isTokenOnSameLine)(leftToken, closingSlash)) {
context.report({
node,
messageId: "beforeSelfCloseNeedNewline",
loc: leftToken.loc.end,
fix(fixer) {
return fixer.insertTextBefore(closingSlash, "\n");
}
});
return;
}
}
if (!(0, ast_exports.isTokenOnSameLine)(leftToken, closingSlash)) return;
const adjacent = !sourceCode.isSpaceBetween(leftToken, closingSlash);
if ((option === "always" || option === "proportional-always") && adjacent) context.report({
node,
messageId: "beforeSelfCloseNeedSpace",
loc: closingSlash.loc.start,
fix(fixer) {
return fixer.insertTextBefore(closingSlash, " ");
}
});
else if (option === "never" && !adjacent) context.report({
node,
messageId: "beforeSelfCloseNoSpace",
loc: closingSlash.loc.start,
fix(fixer) {
const previousToken = sourceCode.getTokenBefore(closingSlash);
return fixer.removeRange([previousToken.range[1], closingSlash.range[0]]);
}
});
}
function validateAfterOpening(context, node, option) {
const sourceCode = context.sourceCode;
const openingToken = sourceCode.getTokenBefore(node.name);
if (option === "allow-multiline") {
if (openingToken.loc.start.line !== node.name.loc.start.line) return;
}
const adjacent = !sourceCode.isSpaceBetween(openingToken, node.name);
if (option === "never" || option === "allow-multiline") {
if (!adjacent) context.report({
node,
messageId: "afterOpenNoSpace",
loc: {
start: openingToken.loc.start,
end: node.name.loc.start
},
fix(fixer) {
return fixer.removeRange([openingToken.range[1], node.name.range[0]]);
}
});
} else if (option === "always" && adjacent) context.report({
node,
messageId: "afterOpenNeedSpace",
loc: {
start: openingToken.loc.start,
end: node.name.loc.start
},
fix(fixer) {
return fixer.insertTextBefore(node.name, " ");
}
});
}
function validateBeforeClosing(context, node, option) {
if (!("selfClosing" in node && node.selfClosing)) {
const sourceCode = context.sourceCode;
const leftToken = option === "proportional-always" ? getTokenBeforeClosingBracket(node) : sourceCode.getLastTokens(node, 2)[0];
const closingToken = sourceCode.getTokenAfter(leftToken);
if (!isSingleLine(node) && option === "proportional-always") {
if ((0, ast_exports.isTokenOnSameLine)(leftToken, closingToken)) {
context.report({
node,
messageId: "beforeCloseNeedNewline",
loc: leftToken.loc.end,
fix(fixer) {
return fixer.insertTextBefore(closingToken, "\n");
}
});
return;
}
}
if (leftToken.loc.start.line !== closingToken.loc.start.line) return;
const adjacent = !sourceCode.isSpaceBetween(leftToken, closingToken);
if (option === "never" && !adjacent) context.report({
node,
messageId: "beforeCloseNoSpace",
loc: {
start: leftToken.loc.end,
end: closingToken.loc.start
},
fix(fixer) {
return fixer.removeRange([leftToken.range[1], closingToken.range[0]]);
}
});
else if (option === "always" && adjacent) context.report({
node,
loc: {
start: leftToken.loc.end,
end: closingToken.loc.start
},
messageId: "beforeCloseNeedSpace",
fix(fixer) {
return fixer.insertTextBefore(closingToken, " ");
}
});
else if (option === "proportional-always" && node.type === "JSXOpeningElement" && adjacent !== isSingleLine(node)) context.report({
node,
messageId: "beforeCloseNeedSpace",
loc: {
start: leftToken.loc.end,
end: closingToken.loc.start
},
fix(fixer) {
return fixer.insertTextBefore(closingToken, " ");
}
});
}
}
const optionDefaults = {
closingSlash: "never",
beforeSelfClosing: "always",
afterOpening: "never",
beforeClosing: "allow"
};
var jsx_tag_spacing_default = createRule({
name: "jsx-tag-spacing",
meta: {
type: "layout",
docs: { description: "Enforce whitespace in and around the JSX opening and closing brackets" },
fixable: "whitespace",
messages,
schema: [{
type: "object",
properties: {
closingSlash: {
type: "string",
enum: [
"always",
"never",
"allow"
]
},
beforeSelfClosing: {
type: "string",
enum: [
"always",
"proportional-always",
"never",
"allow"
]
},
afterOpening: {
type: "string",
enum: [
"always",
"allow-multiline",
"never",
"allow"
]
},
beforeClosing: {
type: "string",
enum: [
"always",
"proportional-always",
"never",
"allow"
]
}
},
default: optionDefaults,
additionalProperties: false
}]
},
create(context) {
const options = Object.assign({}, optionDefaults, context.options[0]);
return {
JSXOpeningElement(node) {
if (options.closingSlash !== "allow" && node.selfClosing) validateClosingSlash(context, node, options.closingSlash);
if (options.afterOpening !== "allow") validateAfterOpening(context, node, options.afterOpening);
if (options.beforeSelfClosing !== "allow" && node.selfClosing) validateBeforeSelfClosing(context, node, options.beforeSelfClosing);
if (options.beforeClosing !== "allow") validateBeforeClosing(context, node, options.beforeClosing);
},
JSXClosingElement(node) {
if (options.afterOpening !== "allow") validateAfterOpening(context, node, options.afterOpening);
if (options.closingSlash !== "allow") validateClosingSlash(context, node, options.closingSlash);
if (options.beforeClosing !== "allow") validateBeforeClosing(context, node, options.beforeClosing);
}
};
}
});
export { jsx_tag_spacing_default };

View File

@@ -0,0 +1,235 @@
import { ast_exports, createRule, isJSX, isSingleLine } from "../utils.js";
const DEFAULTS = {
declaration: "parens",
assignment: "parens",
return: "parens",
arrow: "parens",
condition: "ignore",
logical: "ignore",
prop: "ignore",
propertyValue: "ignore"
};
const messages = {
missingParens: "Missing parentheses around multilines JSX",
parensOnNewLines: "Parentheses around JSX should be on separate lines"
};
var jsx_wrap_multilines_default = createRule({
name: "jsx-wrap-multilines",
meta: {
type: "layout",
docs: { description: "Disallow missing parentheses around multiline JSX" },
fixable: "code",
messages,
schema: [{
type: "object",
properties: {
declaration: {
type: ["string", "boolean"],
enum: [
true,
false,
"ignore",
"parens",
"parens-new-line"
]
},
assignment: {
type: ["string", "boolean"],
enum: [
true,
false,
"ignore",
"parens",
"parens-new-line"
]
},
return: {
type: ["string", "boolean"],
enum: [
true,
false,
"ignore",
"parens",
"parens-new-line"
]
},
arrow: {
type: ["string", "boolean"],
enum: [
true,
false,
"ignore",
"parens",
"parens-new-line"
]
},
condition: {
type: ["string", "boolean"],
enum: [
true,
false,
"ignore",
"parens",
"parens-new-line"
]
},
logical: {
type: ["string", "boolean"],
enum: [
true,
false,
"ignore",
"parens",
"parens-new-line"
]
},
prop: {
type: ["string", "boolean"],
enum: [
true,
false,
"ignore",
"parens",
"parens-new-line"
]
},
propertyValue: {
type: ["string", "boolean"],
enum: [
true,
false,
"ignore",
"parens",
"parens-new-line"
]
}
},
additionalProperties: false
}]
},
create(context) {
function getOption(type) {
const userOptions = context.options[0] || {};
if (type in userOptions) return userOptions[type];
return DEFAULTS[type];
}
function isEnabled(type) {
const option = getOption(type);
return option && option !== "ignore";
}
function needsOpeningNewLine(node) {
const previousToken = context.sourceCode.getTokenBefore(node);
if (!(0, ast_exports.isParenthesized)(node, context.sourceCode)) return false;
if ((0, ast_exports.isTokenOnSameLine)(previousToken, node)) return true;
return false;
}
function needsClosingNewLine(node) {
const nextToken = context.sourceCode.getTokenAfter(node);
if (!(0, ast_exports.isParenthesized)(node, context.sourceCode)) return false;
if ((0, ast_exports.isTokenOnSameLine)(node, nextToken)) return true;
return false;
}
function trimTokenBeforeNewline(tokenBefore) {
const isBracket = tokenBefore.value === "{" || tokenBefore.value === "[";
return `${tokenBefore.value.trim()}${isBracket ? "" : " "}`;
}
function check(node, type) {
if (!node || !isJSX(node)) return;
const sourceCode = context.sourceCode;
const option = getOption(type);
if ((option === true || option === "parens") && !(0, ast_exports.isParenthesized)(node, context.sourceCode) && !isSingleLine(node)) context.report({
node,
messageId: "missingParens",
fix: (fixer) => fixer.replaceText(node, `(${sourceCode.getText(node)})`)
});
if (option === "parens-new-line" && !isSingleLine(node)) if (!(0, ast_exports.isParenthesized)(node, context.sourceCode)) {
const tokenBefore = sourceCode.getTokenBefore(node);
const tokenAfter = sourceCode.getTokenAfter(node);
const start = node.loc.start;
if (tokenBefore.loc.end.line < start.line) {
const textBefore = sourceCode.getText().slice(tokenBefore.range[1], node.range[0]).trim();
const isTab = /^\t/.test(sourceCode.lines[start.line - 1]);
const INDENT = isTab ? " " : " ";
const indentBefore = INDENT.repeat(start.column);
const indentAfter = INDENT.repeat(Math.max(0, start.column - (isTab ? 1 : 2)));
context.report({
node,
messageId: "missingParens",
fix: (fixer) => fixer.replaceTextRange([tokenBefore.range[0], tokenAfter && (tokenAfter.value === ";" || tokenAfter.value === "}") ? tokenAfter.range[0] : node.range[1]], `${trimTokenBeforeNewline(tokenBefore)}(\n${indentBefore}${textBefore}${textBefore.length > 0 ? `\n${indentBefore}` : ""}${sourceCode.getText(node)}\n${indentAfter})`)
});
} else context.report({
node,
messageId: "missingParens",
fix: (fixer) => fixer.replaceText(node, `(\n${sourceCode.getText(node)}\n)`)
});
} else {
const needsOpening = needsOpeningNewLine(node);
const needsClosing = needsClosingNewLine(node);
if (needsOpening || needsClosing) context.report({
node,
messageId: "parensOnNewLines",
fix: (fixer) => {
const text = sourceCode.getText(node);
let fixed = text;
if (needsOpening) fixed = `\n${fixed}`;
if (needsClosing) fixed = `${fixed}\n`;
return fixer.replaceText(node, fixed);
}
});
}
}
return {
VariableDeclarator(node) {
const type = "declaration";
if (!isEnabled(type)) return;
if (!isEnabled("condition") && node.init && node.init.type === "ConditionalExpression") {
check(node.init.consequent, type);
check(node.init.alternate, type);
return;
}
check(node.init, type);
},
AssignmentExpression(node) {
const type = "assignment";
if (!isEnabled(type)) return;
if (!isEnabled("condition") && node.right.type === "ConditionalExpression") {
check(node.right.consequent, type);
check(node.right.alternate, type);
return;
}
check(node.right, type);
},
ReturnStatement(node) {
const type = "return";
if (isEnabled(type)) check(node.argument, type);
},
"ArrowFunctionExpression:exit": (node) => {
const arrowBody = node.body;
const type = "arrow";
if (isEnabled(type) && arrowBody.type !== "BlockStatement") check(arrowBody, type);
},
ConditionalExpression(node) {
const type = "condition";
if (isEnabled(type)) {
check(node.consequent, type);
check(node.alternate, type);
}
},
LogicalExpression(node) {
const type = "logical";
if (isEnabled(type)) check(node.right, type);
},
JSXAttribute(node) {
const type = "prop";
if (isEnabled(type) && node.value && node.value.type === "JSXExpressionContainer") check(node.value.expression, type);
},
ObjectExpression(node) {
const type = "propertyValue";
if (isEnabled(type)) node.properties.forEach((property) => {
if (property.type === "Property" && property.value.type === "JSXElement") check(property.value, type);
});
}
};
}
});
export { jsx_wrap_multilines_default };

View File

@@ -0,0 +1,553 @@
import { AST_NODE_TYPES, ast_exports, createRule, getStaticPropertyName, getStringLength, isSingleLine } from "../utils.js";
const listeningNodes = [
"ObjectExpression",
"ObjectPattern",
"ImportDeclaration",
"ExportNamedDeclaration",
"ExportAllDeclaration",
"TSTypeLiteral",
"TSInterfaceBody",
"ClassBody"
];
function initOptionProperty(toOptions, fromOptions) {
toOptions.mode = fromOptions.mode || "strict";
if (typeof fromOptions.beforeColon !== "undefined") toOptions.beforeColon = +fromOptions.beforeColon;
else toOptions.beforeColon = 0;
if (typeof fromOptions.afterColon !== "undefined") toOptions.afterColon = +fromOptions.afterColon;
else toOptions.afterColon = 1;
if (typeof fromOptions.align !== "undefined") if (typeof fromOptions.align === "object") toOptions.align = fromOptions.align;
else toOptions.align = {
on: fromOptions.align,
mode: toOptions.mode,
beforeColon: toOptions.beforeColon,
afterColon: toOptions.afterColon
};
return toOptions;
}
function initOptions(toOptions, fromOptions) {
if (typeof fromOptions.align === "object") {
toOptions.align = initOptionProperty({}, fromOptions.align);
toOptions.align.on = fromOptions.align.on || "colon";
toOptions.align.mode = fromOptions.align.mode || "strict";
toOptions.multiLine = initOptionProperty({}, fromOptions.multiLine || fromOptions);
toOptions.singleLine = initOptionProperty({}, fromOptions.singleLine || fromOptions);
} else {
toOptions.multiLine = initOptionProperty({}, fromOptions.multiLine || fromOptions);
toOptions.singleLine = initOptionProperty({}, fromOptions.singleLine || fromOptions);
if (toOptions.multiLine.align) toOptions.align = {
on: toOptions.multiLine.align.on,
mode: toOptions.multiLine.align.mode || toOptions.multiLine.mode,
beforeColon: toOptions.multiLine.align.beforeColon,
afterColon: toOptions.multiLine.align.afterColon
};
}
toOptions.ignoredNodes = fromOptions.ignoredNodes || [];
return toOptions;
}
var key_spacing_default = createRule({
name: "key-spacing",
meta: {
type: "layout",
docs: { description: "Enforce consistent spacing between property names and type annotations in types and interfaces" },
fixable: "whitespace",
schema: [{ anyOf: [
{
type: "object",
properties: {
align: { anyOf: [{
type: "string",
enum: ["colon", "value"]
}, {
type: "object",
properties: {
mode: {
type: "string",
enum: ["strict", "minimum"]
},
on: {
type: "string",
enum: ["colon", "value"]
},
beforeColon: { type: "boolean" },
afterColon: { type: "boolean" }
},
additionalProperties: false
}] },
mode: {
type: "string",
enum: ["strict", "minimum"]
},
beforeColon: { type: "boolean" },
afterColon: { type: "boolean" },
ignoredNodes: {
type: "array",
items: {
type: "string",
enum: listeningNodes
}
}
},
additionalProperties: false
},
{
type: "object",
properties: {
singleLine: {
type: "object",
properties: {
mode: {
type: "string",
enum: ["strict", "minimum"]
},
beforeColon: { type: "boolean" },
afterColon: { type: "boolean" }
},
additionalProperties: false
},
multiLine: {
type: "object",
properties: {
align: { anyOf: [{
type: "string",
enum: ["colon", "value"]
}, {
type: "object",
properties: {
mode: {
type: "string",
enum: ["strict", "minimum"]
},
on: {
type: "string",
enum: ["colon", "value"]
},
beforeColon: { type: "boolean" },
afterColon: { type: "boolean" }
},
additionalProperties: false
}] },
mode: {
type: "string",
enum: ["strict", "minimum"]
},
beforeColon: { type: "boolean" },
afterColon: { type: "boolean" }
},
additionalProperties: false
}
},
additionalProperties: false
},
{
type: "object",
properties: {
singleLine: {
type: "object",
properties: {
mode: {
type: "string",
enum: ["strict", "minimum"]
},
beforeColon: { type: "boolean" },
afterColon: { type: "boolean" }
},
additionalProperties: false
},
multiLine: {
type: "object",
properties: {
mode: {
type: "string",
enum: ["strict", "minimum"]
},
beforeColon: { type: "boolean" },
afterColon: { type: "boolean" }
},
additionalProperties: false
},
align: {
type: "object",
properties: {
mode: {
type: "string",
enum: ["strict", "minimum"]
},
on: {
type: "string",
enum: ["colon", "value"]
},
beforeColon: { type: "boolean" },
afterColon: { type: "boolean" }
},
additionalProperties: false
}
},
additionalProperties: false
}
] }],
messages: {
extraKey: "Extra space after {{computed}}key '{{key}}'.",
extraValue: "Extra space before value for {{computed}}key '{{key}}'.",
missingKey: "Missing space after {{computed}}key '{{key}}'.",
missingValue: "Missing space before value for {{computed}}key '{{key}}'."
}
},
defaultOptions: [{}],
create(context, [_options]) {
const options = _options || {};
const ruleOptions = initOptions({}, options);
const multiLineOptions = ruleOptions.multiLine;
const singleLineOptions = ruleOptions.singleLine;
const alignmentOptions = ruleOptions.align || null;
const ignoredNodes = ruleOptions.ignoredNodes;
const sourceCode = context.sourceCode;
function containsLineTerminator(str) {
return ast_exports.LINEBREAK_MATCHER.test(str);
}
function isSingleLineImportAttributes(node, sourceCode$1) {
if (node.type === "TSImportType") {
if ("options" in node && node.options) return isSingleLine(node.options);
return false;
}
const openingBrace = sourceCode$1.getTokenBefore(node.attributes[0], ast_exports.isOpeningBraceToken);
const closingBrace = sourceCode$1.getTokenAfter(node.attributes[node.attributes.length - 1], ast_exports.isClosingBraceToken);
return (0, ast_exports.isTokenOnSameLine)(closingBrace, openingBrace);
}
function isSingleLineProperties(properties) {
const [firstProp] = properties;
const lastProp = properties.at(-1);
return (0, ast_exports.isTokenOnSameLine)(lastProp, firstProp);
}
function isKeyValueProperty(property) {
if (property.type === "ImportAttribute") return true;
return !("method" in property && property.method || "shorthand" in property && property.shorthand || "kind" in property && property.kind !== "init" || property.type !== "Property");
}
function getNextColon(node) {
return sourceCode.getTokenAfter(node, ast_exports.isColonToken);
}
function getLastTokenBeforeColon(node) {
const colonToken = getNextColon(node);
return sourceCode.getTokenBefore(colonToken);
}
function getFirstTokenAfterColon(node) {
const colonToken = getNextColon(node);
return sourceCode.getTokenAfter(colonToken);
}
function continuesPropertyGroup(lastMember, candidate) {
const groupEndLine = lastMember.loc.start.line;
const candidateValueStartLine = (isKeyValueProperty(candidate) ? getFirstTokenAfterColon(candidate.key) : candidate).loc.start.line;
if (candidateValueStartLine - groupEndLine <= 1) return true;
const leadingComments = sourceCode.getCommentsBefore(candidate);
if (leadingComments.length && leadingComments[0].loc.start.line - groupEndLine <= 1 && candidateValueStartLine - leadingComments.at(-1).loc.end.line <= 1) {
for (let i = 1; i < leadingComments.length; i++) if (leadingComments[i].loc.start.line - leadingComments[i - 1].loc.end.line > 1) return false;
return true;
}
return false;
}
function getKey(property) {
const key = property.key;
if (property.type !== "ImportAttribute" && property.computed) return sourceCode.getText().slice(key.range[0], key.range[1]);
return getStaticPropertyName(property);
}
function report(property, side, whitespace, expected, mode) {
const diff = whitespace.length - expected;
if ((diff && mode === "strict" || diff < 0 && mode === "minimum" || diff > 0 && !expected && mode === "minimum") && !(expected && containsLineTerminator(whitespace))) {
const nextColon = getNextColon(property.key);
const tokenBeforeColon = sourceCode.getTokenBefore(nextColon, { includeComments: true });
const tokenAfterColon = sourceCode.getTokenAfter(nextColon, { includeComments: true });
const isKeySide = side === "key";
const isExtra = diff > 0;
const diffAbs = Math.abs(diff);
const spaces = new Array(diffAbs + 1).join(" ");
const locStart = isKeySide ? tokenBeforeColon.loc.end : nextColon.loc.start;
const locEnd = isKeySide ? nextColon.loc.start : tokenAfterColon.loc.start;
const missingLoc = isKeySide ? tokenBeforeColon.loc : tokenAfterColon.loc;
const loc = isExtra ? {
start: locStart,
end: locEnd
} : missingLoc;
let fix;
if (isExtra) {
let range;
if (isKeySide) range = [tokenBeforeColon.range[1], tokenBeforeColon.range[1] + diffAbs];
else range = [tokenAfterColon.range[0] - diffAbs, tokenAfterColon.range[0]];
fix = function(fixer) {
return fixer.removeRange(range);
};
} else if (isKeySide) fix = function(fixer) {
return fixer.insertTextAfter(tokenBeforeColon, spaces);
};
else fix = function(fixer) {
return fixer.insertTextBefore(tokenAfterColon, spaces);
};
let messageId;
if (isExtra) messageId = side === "key" ? "extraKey" : "extraValue";
else messageId = side === "key" ? "missingKey" : "missingValue";
context.report({
node: property[side],
loc,
messageId,
data: {
computed: property.type !== "ImportAttribute" && property.computed ? "computed " : "",
key: getKey(property)
},
fix
});
}
}
function getKeyWidth(property) {
const startToken = sourceCode.getFirstToken(property);
const endToken = getLastTokenBeforeColon(property.key);
return getStringLength(sourceCode.getText().slice(startToken.range[0], endToken.range[1]));
}
function getPropertyWhitespace(property) {
const whitespace = /(\s*):(\s*)/u.exec(sourceCode.getText().slice(property.key.range[1], property.value.range[0]));
if (whitespace) return {
beforeColon: whitespace[1],
afterColon: whitespace[2]
};
return null;
}
function createGroups(properties) {
if (properties.length === 1) return [properties];
return properties.reduce((groups, property) => {
const currentGroup = groups.at(-1);
const prev = currentGroup.at(-1);
if (!prev || continuesPropertyGroup(prev, property)) currentGroup.push(property);
else groups.push([property]);
return groups;
}, [[]]);
}
function verifyGroupAlignment(properties) {
const length = properties.length;
const widths = properties.map(getKeyWidth);
const align = alignmentOptions.on;
let targetWidth = Math.max(...widths);
let beforeColon;
let afterColon;
let mode;
if (alignmentOptions && length > 1) {
beforeColon = alignmentOptions.beforeColon;
afterColon = alignmentOptions.afterColon;
mode = alignmentOptions.mode;
} else {
beforeColon = multiLineOptions.beforeColon;
afterColon = multiLineOptions.afterColon;
mode = alignmentOptions.mode;
}
targetWidth += align === "colon" ? beforeColon : afterColon;
for (let i = 0; i < length; i++) {
const property = properties[i];
const whitespace = getPropertyWhitespace(property);
if (whitespace) {
const width = widths[i];
if (align === "value") {
report(property, "key", whitespace.beforeColon, beforeColon, mode);
report(property, "value", whitespace.afterColon, targetWidth - width, mode);
} else {
report(property, "key", whitespace.beforeColon, targetWidth - width, mode);
report(property, "value", whitespace.afterColon, afterColon, mode);
}
}
}
}
function verifySpacing(node, lineOptions) {
if (ignoredNodes.includes(node.parent.type)) return;
const actual = getPropertyWhitespace(node);
if (actual) {
report(node, "key", actual.beforeColon, lineOptions.beforeColon, lineOptions.mode);
report(node, "value", actual.afterColon, lineOptions.afterColon, lineOptions.mode);
}
}
function verifyListSpacing(properties, lineOptions) {
const length = properties.length;
for (let i = 0; i < length; i++) verifySpacing(properties[i], lineOptions);
}
function verifyAlignment(properties) {
createGroups(properties).forEach((group) => {
const properties$1 = group.filter(isKeyValueProperty);
if (properties$1.length > 0 && isSingleLineProperties(properties$1)) verifyListSpacing(properties$1, multiLineOptions);
else verifyGroupAlignment(properties$1);
});
}
function verifyImportAttributes(node) {
if (ignoredNodes.includes(node.type)) return;
if (!node.attributes) return;
if (!node.attributes.length) return;
if (isSingleLineImportAttributes(node, sourceCode)) verifyListSpacing(node.attributes, singleLineOptions);
else verifyAlignment(node.attributes);
}
const baseRules = alignmentOptions ? {
ObjectExpression(node) {
if (ignoredNodes.includes(node.type)) return;
if (isSingleLine(node)) verifyListSpacing(node.properties.filter(isKeyValueProperty), singleLineOptions);
else verifyAlignment(node.properties);
},
ImportDeclaration(node) {
verifyImportAttributes(node);
},
ExportNamedDeclaration(node) {
verifyImportAttributes(node);
},
ExportAllDeclaration(node) {
verifyImportAttributes(node);
}
} : {
Property(node) {
verifySpacing(node, isSingleLine(node.parent) ? singleLineOptions : multiLineOptions);
},
ImportAttribute(node) {
const parent = node.parent;
verifySpacing(node, isSingleLineImportAttributes(parent, sourceCode) ? singleLineOptions : multiLineOptions);
}
};
function adjustedColumn(position) {
const line = position.line - 1;
return getStringLength(sourceCode.lines.at(line).slice(0, position.column));
}
function isKeyTypeNode(node) {
return (node.type === AST_NODE_TYPES.TSPropertySignature || node.type === AST_NODE_TYPES.TSIndexSignature || node.type === AST_NODE_TYPES.PropertyDefinition) && !!node.typeAnnotation;
}
function isApplicable(node) {
return isKeyTypeNode(node) && (0, ast_exports.isTokenOnSameLine)(node, node.typeAnnotation);
}
function getKeyText(node) {
if (node.type !== AST_NODE_TYPES.TSIndexSignature) return sourceCode.getText(node.key);
const code = sourceCode.getText(node);
return code.slice(0, sourceCode.getTokenAfter(node.parameters.at(-1), ast_exports.isClosingBracketToken).range[1] - node.range[0]);
}
function getKeyLocEnd(node) {
return getLastTokenBeforeColon(node.type !== AST_NODE_TYPES.TSIndexSignature ? node.key : node.parameters.at(-1)).loc.end;
}
function checkBeforeColon(node, expectedWhitespaceBeforeColon, mode) {
const { typeAnnotation } = node;
const colon = typeAnnotation.loc.start.column;
const keyEnd = getKeyLocEnd(node);
const difference = colon - keyEnd.column - expectedWhitespaceBeforeColon;
if (mode === "strict" ? difference : difference < 0) context.report({
node,
messageId: difference > 0 ? "extraKey" : "missingKey",
fix: (fixer) => {
if (difference > 0) return fixer.removeRange([typeAnnotation.range[0] - difference, typeAnnotation.range[0]]);
return fixer.insertTextBefore(typeAnnotation, " ".repeat(-difference));
},
data: {
computed: "",
key: getKeyText(node)
}
});
}
function checkAfterColon(node, expectedWhitespaceAfterColon, mode) {
const { typeAnnotation } = node;
const colonToken = sourceCode.getFirstToken(typeAnnotation);
const typeStart = sourceCode.getTokenAfter(colonToken, { includeComments: true }).loc.start.column;
const difference = typeStart - colonToken.loc.start.column - 1 - expectedWhitespaceAfterColon;
if (mode === "strict" ? difference : difference < 0) context.report({
node,
messageId: difference > 0 ? "extraValue" : "missingValue",
fix: (fixer) => {
if (difference > 0) return fixer.removeRange([colonToken.range[1], colonToken.range[1] + difference]);
return fixer.insertTextAfter(colonToken, " ".repeat(-difference));
},
data: {
computed: "",
key: getKeyText(node)
}
});
}
function continuesAlignGroup(lastMember, candidate) {
const groupEndLine = lastMember.loc.start.line;
const candidateValueStartLine = (isKeyTypeNode(candidate) ? candidate.typeAnnotation : candidate).loc.start.line;
if (candidateValueStartLine === groupEndLine) return false;
if (candidateValueStartLine - groupEndLine === 1) return true;
const leadingComments = sourceCode.getCommentsBefore(candidate);
if (leadingComments.length && leadingComments[0].loc.start.line - groupEndLine <= 1 && candidateValueStartLine - leadingComments.at(-1).loc.end.line <= 1) {
for (let i = 1; i < leadingComments.length; i++) if (leadingComments[i].loc.start.line - leadingComments[i - 1].loc.end.line > 1) return false;
return true;
}
return false;
}
function checkAlignGroup(group) {
let alignColumn = 0;
const align = (typeof options.align === "object" ? options.align.on : typeof options.multiLine?.align === "object" ? options.multiLine.align.on : options.multiLine?.align ?? options.align) ?? "colon";
const beforeColon = (typeof options.align === "object" ? options.align.beforeColon : options.multiLine ? typeof options.multiLine.align === "object" ? options.multiLine.align.beforeColon : options.multiLine.beforeColon : options.beforeColon) ?? false;
const expectedWhitespaceBeforeColon = beforeColon ? 1 : 0;
const afterColon = (typeof options.align === "object" ? options.align.afterColon : options.multiLine ? typeof options.multiLine.align === "object" ? options.multiLine.align.afterColon : options.multiLine.afterColon : options.afterColon) ?? true;
const expectedWhitespaceAfterColon = afterColon ? 1 : 0;
const mode = (typeof options.align === "object" ? options.align.mode : options.multiLine ? typeof options.multiLine.align === "object" ? options.multiLine.align.mode ?? options.multiLine.mode : options.multiLine.mode : options.mode) ?? "strict";
for (const node of group) if (isKeyTypeNode(node)) {
const keyEnd = adjustedColumn(getKeyLocEnd(node));
alignColumn = Math.max(alignColumn, align === "colon" ? keyEnd + expectedWhitespaceBeforeColon : keyEnd + 1 + expectedWhitespaceAfterColon + expectedWhitespaceBeforeColon);
}
for (const node of group) {
if (!isApplicable(node)) continue;
const { typeAnnotation } = node;
const toCheck = align === "colon" ? typeAnnotation : typeAnnotation.typeAnnotation;
const difference = adjustedColumn(toCheck.loc.start) - alignColumn;
if (difference) context.report({
node,
messageId: difference > 0 ? align === "colon" ? "extraKey" : "extraValue" : align === "colon" ? "missingKey" : "missingValue",
fix: (fixer) => {
if (difference > 0) return fixer.removeRange([toCheck.range[0] - difference, toCheck.range[0]]);
return fixer.insertTextBefore(toCheck, " ".repeat(-difference));
},
data: {
computed: "",
key: getKeyText(node)
}
});
if (align === "colon") checkAfterColon(node, expectedWhitespaceAfterColon, mode);
else checkBeforeColon(node, expectedWhitespaceBeforeColon, mode);
}
}
function checkIndividualNode(node, { singleLine }) {
const beforeColon = (singleLine ? options.singleLine ? options.singleLine.beforeColon : options.beforeColon : options.multiLine ? options.multiLine.beforeColon : options.beforeColon) ?? false;
const expectedWhitespaceBeforeColon = beforeColon ? 1 : 0;
const afterColon = (singleLine ? options.singleLine ? options.singleLine.afterColon : options.afterColon : options.multiLine ? options.multiLine.afterColon : options.afterColon) ?? true;
const expectedWhitespaceAfterColon = afterColon ? 1 : 0;
const mode = (singleLine ? options.singleLine ? options.singleLine.mode : options.mode : options.multiLine ? options.multiLine.mode : options.mode) ?? "strict";
if (isApplicable(node)) {
checkBeforeColon(node, expectedWhitespaceBeforeColon, mode);
checkAfterColon(node, expectedWhitespaceAfterColon, mode);
}
}
function validateBody(body) {
if (ignoredNodes.includes(body.type)) return;
const members = body.type === AST_NODE_TYPES.TSTypeLiteral ? body.members : body.body;
let alignGroups = [];
let unalignedElements = [];
if (options.align || options.multiLine?.align) {
let currentAlignGroup = [];
alignGroups.push(currentAlignGroup);
let prevNode;
for (const node of members) {
let prevAlignedNode = currentAlignGroup.at(-1);
if (prevAlignedNode !== prevNode) prevAlignedNode = void 0;
if (prevAlignedNode && continuesAlignGroup(prevAlignedNode, node)) currentAlignGroup.push(node);
else if (prevNode?.loc.start.line === node.loc.start.line) {
if (prevAlignedNode) {
unalignedElements.push(prevAlignedNode);
currentAlignGroup.pop();
}
unalignedElements.push(node);
} else {
currentAlignGroup = [node];
alignGroups.push(currentAlignGroup);
}
prevNode = node;
}
unalignedElements = unalignedElements.concat(...alignGroups.filter((group) => group.length === 1));
alignGroups = alignGroups.filter((group) => group.length >= 2);
} else unalignedElements = members;
for (const group of alignGroups) checkAlignGroup(group);
for (const node of unalignedElements) checkIndividualNode(node, { singleLine: isSingleLine(body) });
}
return {
...baseRules,
TSTypeLiteral: validateBody,
TSInterfaceBody: validateBody,
ClassBody: validateBody
};
}
});
export { key_spacing_default };

View File

@@ -0,0 +1,324 @@
import { AST_NODE_TYPES, KEYWORDS, ast_exports, createRule, isKeywordToken } from "../utils.js";
const PREV_TOKEN = /^[)\]}>]$/u;
const NEXT_TOKEN = /^(?:[([{<~!]|\+\+?|--?)$/u;
const PREV_TOKEN_M = /^[)\]}>*]$/u;
const NEXT_TOKEN_M = /^[{*]$/u;
const TEMPLATE_OPEN_PAREN = /\$\{$/u;
const TEMPLATE_CLOSE_PAREN = /^\}/u;
const CHECK_TYPE = /^(?:JSXElement|RegularExpression|String|Template|PrivateIdentifier)$/u;
var keyword_spacing_default = createRule({
name: "keyword-spacing",
meta: {
type: "layout",
docs: { description: "Enforce consistent spacing before and after keywords" },
fixable: "whitespace",
schema: [{
type: "object",
properties: {
before: {
type: "boolean",
default: true
},
after: {
type: "boolean",
default: true
},
overrides: {
type: "object",
properties: KEYWORDS.reduce((retv, key) => {
retv[key] = {
type: "object",
properties: {
before: { type: "boolean" },
after: { type: "boolean" }
},
additionalProperties: false
};
return retv;
}, {}),
additionalProperties: false
}
},
additionalProperties: false
}],
messages: {
expectedBefore: "Expected space(s) before \"{{value}}\".",
expectedAfter: "Expected space(s) after \"{{value}}\".",
unexpectedBefore: "Unexpected space(s) before \"{{value}}\".",
unexpectedAfter: "Unexpected space(s) after \"{{value}}\"."
}
},
defaultOptions: [{}],
create(context) {
const sourceCode = context.sourceCode;
const tokensToIgnore = /* @__PURE__ */ new WeakSet();
function isOpenParenOfTemplate(token) {
return token.type === "Template" && TEMPLATE_OPEN_PAREN.test(token.value);
}
function isCloseParenOfTemplate(token) {
return token.type === "Template" && TEMPLATE_CLOSE_PAREN.test(token.value);
}
function expectSpaceBefore(token, pattern) {
const prevToken = sourceCode.getTokenBefore(token);
if (prevToken && (CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) && !isOpenParenOfTemplate(prevToken) && !tokensToIgnore.has(prevToken) && (0, ast_exports.isTokenOnSameLine)(prevToken, token) && !sourceCode.isSpaceBetween(prevToken, token)) context.report({
loc: token.loc,
messageId: "expectedBefore",
data: token,
fix(fixer) {
return fixer.insertTextBefore(token, " ");
}
});
}
function unexpectSpaceBefore(token, pattern) {
const prevToken = sourceCode.getTokenBefore(token);
if (prevToken && (CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) && !isOpenParenOfTemplate(prevToken) && !tokensToIgnore.has(prevToken) && (0, ast_exports.isTokenOnSameLine)(prevToken, token) && sourceCode.isSpaceBetween(prevToken, token)) context.report({
loc: {
start: prevToken.loc.end,
end: token.loc.start
},
messageId: "unexpectedBefore",
data: token,
fix(fixer) {
return fixer.removeRange([prevToken.range[1], token.range[0]]);
}
});
}
function expectSpaceAfter(token, pattern) {
const nextToken = sourceCode.getTokenAfter(token);
if (nextToken && (CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) && !isCloseParenOfTemplate(nextToken) && !tokensToIgnore.has(nextToken) && (0, ast_exports.isTokenOnSameLine)(token, nextToken) && !sourceCode.isSpaceBetween(token, nextToken)) context.report({
loc: token.loc,
messageId: "expectedAfter",
data: token,
fix(fixer) {
return fixer.insertTextAfter(token, " ");
}
});
}
function unexpectSpaceAfter(token, pattern) {
const nextToken = sourceCode.getTokenAfter(token);
if (nextToken && (CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) && !isCloseParenOfTemplate(nextToken) && !tokensToIgnore.has(nextToken) && (0, ast_exports.isTokenOnSameLine)(token, nextToken) && sourceCode.isSpaceBetween(token, nextToken)) context.report({
loc: {
start: token.loc.end,
end: nextToken.loc.start
},
messageId: "unexpectedAfter",
data: token,
fix(fixer) {
return fixer.removeRange([token.range[1], nextToken.range[0]]);
}
});
}
function parseOptions(options = {}) {
const before = options.before !== false;
const after = options.after !== false;
const defaultValue = {
before: before ? expectSpaceBefore : unexpectSpaceBefore,
after: after ? expectSpaceAfter : unexpectSpaceAfter
};
const overrides = options && options.overrides || {};
const retv = Object.create(null);
for (let i = 0; i < KEYWORDS.length; ++i) {
const key = KEYWORDS[i];
const override = overrides[key];
if (override) {
const thisBefore = "before" in override ? override.before : before;
const thisAfter = "after" in override ? override.after : after;
retv[key] = {
before: thisBefore ? expectSpaceBefore : unexpectSpaceBefore,
after: thisAfter ? expectSpaceAfter : unexpectSpaceAfter
};
} else retv[key] = defaultValue;
}
return retv;
}
const checkMethodMap = parseOptions(context.options[0]);
function checkSpacingBefore(token, pattern) {
checkMethodMap[token.value].before(token, pattern || PREV_TOKEN);
}
function checkSpacingAfter(token, pattern) {
checkMethodMap[token.value].after(token, pattern || NEXT_TOKEN);
}
function checkSpacingAround(token) {
checkSpacingBefore(token);
checkSpacingAfter(token);
}
function checkSpacingAroundFirstToken(node) {
const firstToken = node && sourceCode.getFirstToken(node);
if (!firstToken) return;
if (!isKeywordToken(firstToken)) if (node.type === "VariableDeclaration") {
if (node.kind !== "using" && node.kind !== "await using" || firstToken.type !== "Identifier")
/* c8 ignore next 2 */ return;
} else return;
checkSpacingAround(firstToken);
}
function checkSpacingBeforeFirstToken(node) {
const firstToken = node && sourceCode.getFirstToken(node);
if (isKeywordToken(firstToken)) checkSpacingBefore(firstToken);
}
function checkSpacingAroundTokenBefore(node) {
if (node) {
const token = sourceCode.getTokenBefore(node, isKeywordToken);
if (token) checkSpacingAround(token);
}
}
function checkSpacingForFunction(node) {
const firstToken = node && sourceCode.getFirstToken(node);
if (firstToken && (isKeywordToken(firstToken) && firstToken.value === "function" || firstToken.value === "async")) checkSpacingBefore(firstToken);
}
function checkSpacingForClass(node) {
checkSpacingAroundFirstToken(node);
checkSpacingAroundTokenBefore(node.superClass);
}
function checkSpacingForModuleDeclaration(node) {
const firstToken = sourceCode.getFirstToken(node);
checkSpacingBefore(firstToken, PREV_TOKEN_M);
checkSpacingAfter(firstToken, NEXT_TOKEN_M);
if (node.type === "ExportDefaultDeclaration") checkSpacingAround(sourceCode.getTokenAfter(firstToken));
if (node.type === "ExportAllDeclaration" && node.exported) {
const asToken = sourceCode.getTokenBefore(node.exported);
checkSpacingBefore(asToken, PREV_TOKEN_M);
checkSpacingAfter(asToken, NEXT_TOKEN_M);
}
if ("source" in node && node.source) {
const fromToken = sourceCode.getTokenBefore(node.source);
checkSpacingBefore(fromToken, PREV_TOKEN_M);
checkSpacingAfter(fromToken, NEXT_TOKEN_M);
if (node.attributes) {
const withToken = sourceCode.getTokenAfter(node.source);
if (isKeywordToken(withToken)) checkSpacingAround(withToken);
}
}
if (node.type !== "ExportDefaultDeclaration") checkSpacingForTypeKeywordInImportExport(node);
}
function checkSpacingForTypeKeywordInImportExport(node) {
let kind;
switch (node.type) {
case AST_NODE_TYPES.ImportDeclaration:
kind = node.importKind;
break;
case AST_NODE_TYPES.ExportAllDeclaration:
case AST_NODE_TYPES.ExportNamedDeclaration:
kind = node.exportKind;
break;
}
if (kind !== "type") return;
const typeToken = sourceCode.getFirstToken(node, { skip: 1 });
if (!(0, ast_exports.isTypeKeyword)(typeToken)) return;
checkSpacingBefore(typeToken, PREV_TOKEN_M);
checkSpacingAfter(typeToken, NEXT_TOKEN_M);
}
function checkSpacingForProperty(node) {
if ("static" in node && node.static) checkSpacingAroundFirstToken(node);
if (node.kind === "get" || node.kind === "set" || ("method" in node && node.method || node.type === "MethodDefinition") && "async" in node.value && node.value.async || node.type === AST_NODE_TYPES.AccessorProperty) {
const token = sourceCode.getTokenBefore(node.key, (tok) => {
switch (tok.value) {
case "get":
case "set":
case "async":
case "accessor": return true;
default: return false;
}
});
if (!token) throw new Error("Failed to find token get, set, or async beside method name");
checkSpacingAround(token);
}
}
return {
DebuggerStatement: checkSpacingAroundFirstToken,
WithStatement: checkSpacingAroundFirstToken,
BreakStatement: checkSpacingAroundFirstToken,
ContinueStatement: checkSpacingAroundFirstToken,
ReturnStatement: checkSpacingAroundFirstToken,
ThrowStatement: checkSpacingAroundFirstToken,
TryStatement(node) {
checkSpacingAroundFirstToken(node);
if (node.handler) if (node.handler.param) checkSpacingBeforeFirstToken(node.handler);
else checkSpacingAroundFirstToken(node.handler);
checkSpacingAroundTokenBefore(node.finalizer);
},
IfStatement(node) {
checkSpacingAroundFirstToken(node);
checkSpacingAroundTokenBefore(node.alternate);
},
SwitchStatement: checkSpacingAroundFirstToken,
SwitchCase: checkSpacingAroundFirstToken,
DoWhileStatement(node) {
checkSpacingAroundFirstToken(node);
checkSpacingAroundTokenBefore(node.test);
},
ForInStatement(node) {
checkSpacingAroundFirstToken(node);
const inToken = sourceCode.getTokenBefore(node.right, ast_exports.isNotOpeningParenToken);
const previousToken = sourceCode.getTokenBefore(inToken);
if (previousToken.type !== "PrivateIdentifier") checkSpacingBefore(inToken);
checkSpacingAfter(inToken);
},
ForOfStatement(node) {
if (node.await) {
checkSpacingBefore(sourceCode.getFirstToken(node, 0));
checkSpacingAfter(sourceCode.getFirstToken(node, 1));
} else checkSpacingAroundFirstToken(node);
const ofToken = sourceCode.getTokenBefore(node.right, ast_exports.isNotOpeningParenToken);
const previousToken = sourceCode.getTokenBefore(ofToken);
if (previousToken.type !== "PrivateIdentifier") checkSpacingBefore(ofToken);
checkSpacingAfter(ofToken);
},
ForStatement: checkSpacingAroundFirstToken,
WhileStatement: checkSpacingAroundFirstToken,
ClassDeclaration: checkSpacingForClass,
ExportNamedDeclaration: checkSpacingForModuleDeclaration,
ExportDefaultDeclaration: checkSpacingForModuleDeclaration,
ExportAllDeclaration: checkSpacingForModuleDeclaration,
FunctionDeclaration: checkSpacingForFunction,
ImportDeclaration: checkSpacingForModuleDeclaration,
VariableDeclaration: checkSpacingAroundFirstToken,
ArrowFunctionExpression: checkSpacingForFunction,
AwaitExpression(node) {
checkSpacingBefore(sourceCode.getFirstToken(node));
},
ClassExpression: checkSpacingForClass,
FunctionExpression: checkSpacingForFunction,
NewExpression: checkSpacingBeforeFirstToken,
Super: checkSpacingBeforeFirstToken,
ThisExpression: checkSpacingBeforeFirstToken,
UnaryExpression: checkSpacingBeforeFirstToken,
YieldExpression: checkSpacingBeforeFirstToken,
ImportSpecifier(node) {
if (node.imported.range[0] !== node.local.range[0]) {
const asToken = sourceCode.getTokenBefore(node.local);
checkSpacingBefore(asToken, PREV_TOKEN_M);
}
},
ExportSpecifier(node) {
if (node.local.range[0] !== node.exported.range[0]) {
const asToken = sourceCode.getTokenBefore(node.exported);
checkSpacingBefore(asToken, PREV_TOKEN_M);
checkSpacingAfter(asToken, NEXT_TOKEN_M);
}
},
ImportNamespaceSpecifier(node) {
const asToken = sourceCode.getFirstToken(node, 1);
checkSpacingBefore(asToken, PREV_TOKEN_M);
},
MethodDefinition: checkSpacingForProperty,
PropertyDefinition: checkSpacingForProperty,
AccessorProperty: checkSpacingForProperty,
StaticBlock: checkSpacingAroundFirstToken,
Property: checkSpacingForProperty,
BinaryExpression(node) {
if (node.operator !== ">") return;
const operatorToken = sourceCode.getTokenBefore(node.right, ast_exports.isNotOpeningParenToken);
tokensToIgnore.add(operatorToken);
},
TSAsExpression(node) {
const asToken = sourceCode.getTokenAfter(node.expression, (token) => token.value === "as");
checkSpacingAround(asToken);
},
TSSatisfiesExpression(node) {
const satisfiesToken = sourceCode.getTokenAfter(node.expression, (token) => token.value === "satisfies");
checkSpacingAround(satisfiesToken);
}
};
}
});
export { keyword_spacing_default };

View File

@@ -0,0 +1,67 @@
import { COMMENTS_IGNORE_PATTERN, ast_exports, createRule, warnDeprecatedOptions } from "../utils.js";
var line_comment_position_default = createRule({
name: "line-comment-position",
meta: {
type: "layout",
docs: { description: "Enforce position of line comments" },
schema: [{ oneOf: [{
type: "string",
enum: ["above", "beside"]
}, {
type: "object",
properties: {
position: {
type: "string",
enum: ["above", "beside"]
},
ignorePattern: { type: "string" },
applyDefaultPatterns: { type: "boolean" },
applyDefaultIgnorePatterns: { type: "boolean" }
},
additionalProperties: false
}] }],
messages: {
above: "Expected comment to be above code.",
beside: "Expected comment to be beside code."
}
},
create(context) {
const options = context.options[0];
let above;
let ignorePattern;
let applyDefaultIgnorePatterns = true;
let customIgnoreRegExp;
if (!options || typeof options === "string") above = !options || options === "above";
else {
above = !options.position || options.position === "above";
ignorePattern = options.ignorePattern;
customIgnoreRegExp = new RegExp(ignorePattern, "u");
if (Object.hasOwn(options, "applyDefaultIgnorePatterns")) applyDefaultIgnorePatterns = options.applyDefaultIgnorePatterns;
else applyDefaultIgnorePatterns = options.applyDefaultPatterns !== false;
warnDeprecatedOptions(options, "applyDefaultPatterns", "applyDefaultIgnorePatterns", "line-comment-position");
}
const defaultIgnoreRegExp = COMMENTS_IGNORE_PATTERN;
const fallThroughRegExp = /^\s*falls?\s?through/u;
const sourceCode = context.sourceCode;
return { Program() {
const comments = sourceCode.getAllComments();
comments.forEach((node) => {
if (node.type !== "Line") return;
if (applyDefaultIgnorePatterns && (defaultIgnoreRegExp.test(node.value) || fallThroughRegExp.test(node.value))) return;
if (ignorePattern && customIgnoreRegExp.test(node.value)) return;
const previous = sourceCode.getTokenBefore(node, { includeComments: true });
const isOnSameLine = previous && (0, ast_exports.isTokenOnSameLine)(previous, node);
if (above) {
if (isOnSameLine) context.report({
node,
messageId: "above"
});
} else if (!isOnSameLine) context.report({
node,
messageId: "beside"
});
});
} };
}
});
export { line_comment_position_default };

View File

@@ -0,0 +1,51 @@
import { createGlobalLinebreakMatcher, createRule } from "../utils.js";
var linebreak_style_default = createRule({
name: "linebreak-style",
meta: {
type: "layout",
docs: { description: "Enforce consistent linebreak style" },
fixable: "whitespace",
schema: [{
type: "string",
enum: ["unix", "windows"]
}],
messages: {
expectedLF: "Expected linebreaks to be 'LF' but found 'CRLF'.",
expectedCRLF: "Expected linebreaks to be 'CRLF' but found 'LF'."
}
},
create(context) {
const sourceCode = context.sourceCode;
return { Program: function checkForLinebreakStyle(node) {
const linebreakStyle = context.options[0] || "unix";
const expectedLF = linebreakStyle === "unix";
const expectedLFChars = expectedLF ? "\n" : "\r\n";
const source = sourceCode.getText();
const pattern = createGlobalLinebreakMatcher();
let match;
let i = 0;
while ((match = pattern.exec(source)) !== null) {
i++;
if (match[0] === expectedLFChars) continue;
const index = match.index;
const range = [index, index + match[0].length];
context.report({
node,
loc: {
start: {
line: i,
column: sourceCode.lines[i - 1].length
},
end: {
line: i + 1,
column: 0
}
},
messageId: expectedLF ? "expectedLF" : "expectedCRLF",
fix: (fixer) => fixer.replaceTextRange(range, expectedLFChars)
});
}
} };
}
});
export { linebreak_style_default };

View File

@@ -0,0 +1,243 @@
import { AST_NODE_TYPES, AST_TOKEN_TYPES, COMMENTS_IGNORE_PATTERN, ast_exports, createRule, isHashbangComment } from "../utils.js";
function getEmptyLineNums(lines) {
const emptyLines = [];
lines.forEach((line, i) => {
if (!line.trim()) emptyLines.push(i + 1);
});
return emptyLines;
}
function getCommentLineNums(comments) {
const lines = [];
comments.forEach((token) => {
const start = token.loc.start.line;
const end = token.loc.end.line;
lines.push(start, end);
});
return lines;
}
var lines_around_comment_default = createRule({
name: "lines-around-comment",
meta: {
type: "layout",
docs: { description: "Require empty lines around comments" },
schema: [{
type: "object",
properties: {
beforeBlockComment: {
type: "boolean",
default: true
},
afterBlockComment: {
type: "boolean",
default: false
},
beforeLineComment: {
type: "boolean",
default: false
},
afterLineComment: {
type: "boolean",
default: false
},
allowBlockStart: {
type: "boolean",
default: false
},
allowBlockEnd: {
type: "boolean",
default: false
},
allowClassStart: { type: "boolean" },
allowClassEnd: { type: "boolean" },
allowObjectStart: { type: "boolean" },
allowObjectEnd: { type: "boolean" },
allowArrayStart: { type: "boolean" },
allowArrayEnd: { type: "boolean" },
allowInterfaceStart: { type: "boolean" },
allowInterfaceEnd: { type: "boolean" },
allowTypeStart: { type: "boolean" },
allowTypeEnd: { type: "boolean" },
allowEnumStart: { type: "boolean" },
allowEnumEnd: { type: "boolean" },
allowModuleStart: { type: "boolean" },
allowModuleEnd: { type: "boolean" },
ignorePattern: { type: "string" },
applyDefaultIgnorePatterns: { type: "boolean" },
afterHashbangComment: { type: "boolean" }
},
additionalProperties: false
}],
fixable: "whitespace",
messages: {
after: "Expected line after comment.",
before: "Expected line before comment."
}
},
defaultOptions: [{ beforeBlockComment: true }],
create(context, [_options]) {
const options = _options;
const defaultIgnoreRegExp = COMMENTS_IGNORE_PATTERN;
const customIgnoreRegExp = new RegExp(options.ignorePattern ?? "", "u");
const sourceCode = context.sourceCode;
const comments = sourceCode.getAllComments();
const lines = sourceCode.lines;
const numLines = lines.length + 1;
const commentLines = getCommentLineNums(comments);
const emptyLines = getEmptyLineNums(lines);
const commentAndEmptyLines = new Set(commentLines.concat(emptyLines));
function codeAroundComment(token) {
let currentToken = token;
do
currentToken = sourceCode.getTokenBefore(currentToken, { includeComments: true });
while (currentToken && (0, ast_exports.isCommentToken)(currentToken));
if (currentToken && (0, ast_exports.isTokenOnSameLine)(currentToken, token)) return true;
currentToken = token;
do
currentToken = sourceCode.getTokenAfter(currentToken, { includeComments: true });
while (currentToken && (0, ast_exports.isCommentToken)(currentToken));
if (currentToken && (0, ast_exports.isTokenOnSameLine)(token, currentToken)) return true;
return false;
}
function isParentNodeType(parent, nodeType) {
return parent.type === nodeType;
}
function getParentNodeOfToken(token) {
const node = sourceCode.getNodeByRangeIndex(token.range[0]);
if (node && node.type === "StaticBlock") {
const openingBrace = sourceCode.getFirstToken(node, { skip: 1 });
return openingBrace && token.range[0] >= openingBrace.range[0] ? node : null;
}
return node;
}
function isCommentAtParentStart(token, nodeType) {
const parent = getParentNodeOfToken(token);
if (parent && isParentNodeType(parent, nodeType)) {
let parentStartNodeOrToken = parent;
if (parent.type === "StaticBlock") parentStartNodeOrToken = sourceCode.getFirstToken(parent, { skip: 1 });
else if (parent.type === "SwitchStatement") parentStartNodeOrToken = sourceCode.getTokenAfter(parent.discriminant, { filter: ast_exports.isOpeningBraceToken });
return !!parentStartNodeOrToken && token.loc.start.line - parentStartNodeOrToken.loc.start.line === 1;
}
return false;
}
function isCommentAtParentEnd(token, nodeType) {
const parent = getParentNodeOfToken(token);
return !!parent && isParentNodeType(parent, nodeType) && parent.loc.end.line - token.loc.end.line === 1;
}
function isCommentAtBlockStart(token) {
return isCommentAtParentStart(token, AST_NODE_TYPES.ClassBody) || isCommentAtParentStart(token, AST_NODE_TYPES.BlockStatement) || isCommentAtParentStart(token, AST_NODE_TYPES.StaticBlock) || isCommentAtParentStart(token, AST_NODE_TYPES.SwitchCase) || isCommentAtParentStart(token, AST_NODE_TYPES.SwitchStatement);
}
function isCommentAtBlockEnd(token) {
return isCommentAtParentEnd(token, AST_NODE_TYPES.ClassBody) || isCommentAtParentEnd(token, AST_NODE_TYPES.BlockStatement) || isCommentAtParentEnd(token, AST_NODE_TYPES.StaticBlock) || isCommentAtParentEnd(token, AST_NODE_TYPES.SwitchCase) || isCommentAtParentEnd(token, AST_NODE_TYPES.SwitchStatement);
}
function isCommentAtClassStart(token) {
return isCommentAtParentStart(token, AST_NODE_TYPES.ClassBody);
}
function isCommentAtClassEnd(token) {
return isCommentAtParentEnd(token, AST_NODE_TYPES.ClassBody);
}
function isCommentAtObjectStart(token) {
return isCommentAtParentStart(token, AST_NODE_TYPES.ObjectExpression) || isCommentAtParentStart(token, AST_NODE_TYPES.ObjectPattern);
}
function isCommentAtObjectEnd(token) {
return isCommentAtParentEnd(token, AST_NODE_TYPES.ObjectExpression) || isCommentAtParentEnd(token, AST_NODE_TYPES.ObjectPattern);
}
function isCommentAtArrayStart(token) {
return isCommentAtParentStart(token, AST_NODE_TYPES.ArrayExpression) || isCommentAtParentStart(token, AST_NODE_TYPES.ArrayPattern);
}
function isCommentAtArrayEnd(token) {
return isCommentAtParentEnd(token, AST_NODE_TYPES.ArrayExpression) || isCommentAtParentEnd(token, AST_NODE_TYPES.ArrayPattern);
}
function isCommentAtInterfaceStart(token) {
return isCommentAtParentStart(token, AST_NODE_TYPES.TSInterfaceBody);
}
function isCommentAtInterfaceEnd(token) {
return isCommentAtParentEnd(token, AST_NODE_TYPES.TSInterfaceBody);
}
function isCommentAtTypeStart(token) {
return isCommentAtParentStart(token, AST_NODE_TYPES.TSTypeLiteral);
}
function isCommentAtTypeEnd(token) {
return isCommentAtParentEnd(token, AST_NODE_TYPES.TSTypeLiteral);
}
function isCommentAtEnumStart(token) {
return isCommentAtParentStart(token, AST_NODE_TYPES.TSEnumBody) || isCommentAtParentStart(token, AST_NODE_TYPES.TSEnumDeclaration);
}
function isCommentAtEnumEnd(token) {
return isCommentAtParentEnd(token, AST_NODE_TYPES.TSEnumBody) || isCommentAtParentEnd(token, AST_NODE_TYPES.TSEnumDeclaration);
}
function isCommentAtModuleStart(token) {
return isCommentAtParentStart(token, AST_NODE_TYPES.TSModuleBlock);
}
function isCommentAtModuleEnd(token) {
return isCommentAtParentEnd(token, AST_NODE_TYPES.TSModuleBlock);
}
function checkForEmptyLine(token, { before, after }) {
if (options.applyDefaultIgnorePatterns !== false && defaultIgnoreRegExp.test(token.value)) return;
if (options.ignorePattern && customIgnoreRegExp.test(token.value)) return;
const prevLineNum = token.loc.start.line - 1;
const nextLineNum = token.loc.end.line + 1;
if (prevLineNum < 1) before = false;
if (nextLineNum >= numLines) after = false;
if (codeAroundComment(token)) return;
const blockStartAllowed = Boolean(options.allowBlockStart) && isCommentAtBlockStart(token) && !(options.allowClassStart === false && isCommentAtClassStart(token));
const blockEndAllowed = Boolean(options.allowBlockEnd) && isCommentAtBlockEnd(token) && !(options.allowClassEnd === false && isCommentAtClassEnd(token));
const classStartAllowed = Boolean(options.allowClassStart) && isCommentAtClassStart(token);
const classEndAllowed = Boolean(options.allowClassEnd) && isCommentAtClassEnd(token);
const objectStartAllowed = Boolean(options.allowObjectStart) && isCommentAtObjectStart(token);
const objectEndAllowed = Boolean(options.allowObjectEnd) && isCommentAtObjectEnd(token);
const arrayStartAllowed = Boolean(options.allowArrayStart) && isCommentAtArrayStart(token);
const arrayEndAllowed = Boolean(options.allowArrayEnd) && isCommentAtArrayEnd(token);
const interfaceStartAllowed = Boolean(options.allowInterfaceStart) && isCommentAtInterfaceStart(token);
const interfaceEndAllowed = Boolean(options.allowInterfaceEnd) && isCommentAtInterfaceEnd(token);
const typeStartAllowed = Boolean(options.allowTypeStart) && isCommentAtTypeStart(token);
const typeEndAllowed = Boolean(options.allowTypeEnd) && isCommentAtTypeEnd(token);
const enumStartAllowed = Boolean(options.allowEnumStart) && isCommentAtEnumStart(token);
const enumEndAllowed = Boolean(options.allowEnumEnd) && isCommentAtEnumEnd(token);
const moduleStartAllowed = Boolean(options.allowModuleStart) && isCommentAtModuleStart(token);
const moduleEndAllowed = Boolean(options.allowModuleEnd) && isCommentAtModuleEnd(token);
const exceptionStartAllowed = blockStartAllowed || classStartAllowed || objectStartAllowed || arrayStartAllowed || interfaceStartAllowed || typeStartAllowed || enumStartAllowed || moduleStartAllowed;
const exceptionEndAllowed = blockEndAllowed || classEndAllowed || objectEndAllowed || arrayEndAllowed || interfaceEndAllowed || typeEndAllowed || enumEndAllowed || moduleEndAllowed;
const previousTokenOrComment = sourceCode.getTokenBefore(token, { includeComments: true });
const nextTokenOrComment = sourceCode.getTokenAfter(token, { includeComments: true });
if (!exceptionStartAllowed && before && !commentAndEmptyLines.has(prevLineNum) && !((0, ast_exports.isCommentToken)(previousTokenOrComment) && (0, ast_exports.isTokenOnSameLine)(previousTokenOrComment, token))) {
const lineStart = token.range[0] - token.loc.start.column;
const range = [lineStart, lineStart];
context.report({
node: token,
messageId: "before",
fix(fixer) {
return fixer.insertTextBeforeRange(range, "\n");
}
});
}
if (!exceptionEndAllowed && after && !commentAndEmptyLines.has(nextLineNum) && !((0, ast_exports.isCommentToken)(nextTokenOrComment) && (0, ast_exports.isTokenOnSameLine)(token, nextTokenOrComment))) context.report({
node: token,
messageId: "after",
fix(fixer) {
return fixer.insertTextAfter(token, "\n");
}
});
}
return { Program() {
comments.forEach((token) => {
if (token.type === AST_TOKEN_TYPES.Line) {
if (options.beforeLineComment || options.afterLineComment) checkForEmptyLine(token, {
after: options.afterLineComment,
before: options.beforeLineComment
});
} else if (token.type === AST_TOKEN_TYPES.Block) {
if (options.beforeBlockComment || options.afterBlockComment) checkForEmptyLine(token, {
after: options.afterBlockComment,
before: options.beforeBlockComment
});
} else if (isHashbangComment(token)) {
if (options.afterHashbangComment) checkForEmptyLine(token, {
after: options.afterHashbangComment,
before: false
});
}
});
} };
}
});
export { lines_around_comment_default };

View File

@@ -0,0 +1,162 @@
import { AST_NODE_TYPES, ast_exports, createRule } from "../utils.js";
const ClassMemberTypes = {
"*": { test: () => true },
"field": { test: (node) => node.type === "PropertyDefinition" },
"method": { test: (node) => node.type === "MethodDefinition" }
};
var lines_between_class_members_default = createRule({
name: "lines-between-class-members",
meta: {
type: "layout",
docs: { description: "Require or disallow an empty line between class members" },
fixable: "whitespace",
schema: [{ anyOf: [{
type: "object",
properties: { enforce: {
type: "array",
items: {
type: "object",
properties: {
blankLine: {
type: "string",
enum: ["always", "never"]
},
prev: {
type: "string",
enum: [
"method",
"field",
"*"
]
},
next: {
type: "string",
enum: [
"method",
"field",
"*"
]
}
},
additionalProperties: false,
required: [
"blankLine",
"prev",
"next"
]
},
minItems: 1
} },
additionalProperties: false,
required: ["enforce"]
}, {
type: "string",
enum: ["always", "never"]
}] }, {
type: "object",
properties: {
exceptAfterSingleLine: {
type: "boolean",
default: false
},
exceptAfterOverload: {
type: "boolean",
default: true
}
},
additionalProperties: false
}],
messages: {
never: "Unexpected blank line between class members.",
always: "Expected blank line between class members."
}
},
defaultOptions: ["always", {
exceptAfterOverload: true,
exceptAfterSingleLine: false
}],
create(context, [firstOption, secondOption]) {
const options = [];
options[0] = context.options[0] || "always";
options[1] = context.options[1] || { exceptAfterSingleLine: false };
const configureList = typeof options[0] === "object" ? options[0].enforce : [{
blankLine: options[0],
prev: "*",
next: "*"
}];
const sourceCode = context.sourceCode;
function getBoundaryTokens(curNode, nextNode) {
const lastToken = sourceCode.getLastToken(curNode);
const prevToken = sourceCode.getTokenBefore(lastToken);
const nextToken = sourceCode.getFirstToken(nextNode);
const isSemicolonLessStyle = (0, ast_exports.isSemicolonToken)(lastToken) && !(0, ast_exports.isTokenOnSameLine)(prevToken, lastToken) && (0, ast_exports.isTokenOnSameLine)(lastToken, nextToken);
return isSemicolonLessStyle ? {
curLast: prevToken,
nextFirst: lastToken
} : {
curLast: lastToken,
nextFirst: nextToken
};
}
function findLastConsecutiveTokenAfter(prevLastToken, nextFirstToken, maxLine) {
const after = sourceCode.getTokenAfter(prevLastToken, { includeComments: true });
if (after !== nextFirstToken && after.loc.start.line - prevLastToken.loc.end.line <= maxLine) return findLastConsecutiveTokenAfter(after, nextFirstToken, maxLine);
return prevLastToken;
}
function findFirstConsecutiveTokenBefore(nextFirstToken, prevLastToken, maxLine) {
const before = sourceCode.getTokenBefore(nextFirstToken, { includeComments: true });
if (before !== prevLastToken && nextFirstToken.loc.start.line - before.loc.end.line <= maxLine) return findFirstConsecutiveTokenBefore(before, prevLastToken, maxLine);
return nextFirstToken;
}
function hasTokenOrCommentBetween(before, after) {
return sourceCode.getTokensBetween(before, after, { includeComments: true }).length !== 0;
}
function match(node, type) {
return ClassMemberTypes[type].test(node);
}
function getPaddingType(prevNode, nextNode) {
for (let i = configureList.length - 1; i >= 0; --i) {
const configure = configureList[i];
const matched = match(prevNode, configure.prev) && match(nextNode, configure.next);
if (matched) return configure.blankLine;
}
return null;
}
const exceptAfterOverload = secondOption?.exceptAfterOverload && (firstOption === "always" || typeof firstOption !== "string" && firstOption?.enforce.some(({ blankLine, prev, next }) => blankLine === "always" && prev !== "field" && next !== "field"));
function isOverload(node) {
return (node.type === AST_NODE_TYPES.TSAbstractMethodDefinition || node.type === AST_NODE_TYPES.MethodDefinition) && node.value.type === AST_NODE_TYPES.TSEmptyBodyFunctionExpression;
}
return { ClassBody(node) {
const body = exceptAfterOverload ? node.body.filter((node$1) => !isOverload(node$1)) : node.body;
for (let i = 0; i < body.length - 1; i++) {
const curFirst = sourceCode.getFirstToken(body[i]);
const { curLast, nextFirst } = getBoundaryTokens(body[i], body[i + 1]);
const isMulti = !(0, ast_exports.isTokenOnSameLine)(curFirst, curLast);
const skip = !isMulti && options[1].exceptAfterSingleLine;
const beforePadding = findLastConsecutiveTokenAfter(curLast, nextFirst, 1);
const afterPadding = findFirstConsecutiveTokenBefore(nextFirst, curLast, 1);
const isPadded = afterPadding.loc.start.line - beforePadding.loc.end.line > 1;
const hasTokenInPadding = hasTokenOrCommentBetween(beforePadding, afterPadding);
const curLineLastToken = findLastConsecutiveTokenAfter(curLast, nextFirst, 0);
const paddingType = getPaddingType(body[i], body[i + 1]);
if (paddingType === "never" && isPadded) context.report({
node: body[i + 1],
messageId: "never",
fix(fixer) {
if (hasTokenInPadding) return null;
return fixer.replaceTextRange([beforePadding.range[1], afterPadding.range[0]], "\n");
}
});
else if (paddingType === "always" && !skip && !isPadded) context.report({
node: body[i + 1],
messageId: "always",
fix(fixer) {
if (hasTokenInPadding) return null;
return fixer.insertTextAfter(curLineLastToken, "\n");
}
});
}
} };
}
});
export { lines_between_class_members_default };

View File

@@ -0,0 +1,189 @@
import { createRule, isSingleLine } from "../utils.js";
const OPTIONS_SCHEMA = {
type: "object",
properties: {
code: {
type: "integer",
minimum: 0
},
comments: {
type: "integer",
minimum: 0
},
tabWidth: {
type: "integer",
minimum: 0
},
ignorePattern: { type: "string" },
ignoreComments: { type: "boolean" },
ignoreStrings: { type: "boolean" },
ignoreUrls: { type: "boolean" },
ignoreTemplateLiterals: { type: "boolean" },
ignoreRegExpLiterals: { type: "boolean" },
ignoreTrailingComments: { type: "boolean" }
},
additionalProperties: false
};
const OPTIONS_OR_INTEGER_SCHEMA = { anyOf: [OPTIONS_SCHEMA, {
type: "integer",
minimum: 0
}] };
var max_len_default = createRule({
name: "max-len",
meta: {
type: "layout",
docs: { description: "Enforce a maximum line length" },
schema: [
OPTIONS_OR_INTEGER_SCHEMA,
OPTIONS_OR_INTEGER_SCHEMA,
OPTIONS_SCHEMA
],
messages: {
max: "This line has a length of {{lineLength}}. Maximum allowed is {{maxLength}}.",
maxComment: "This line has a comment length of {{lineLength}}. Maximum allowed is {{maxCommentLength}}."
}
},
create(context) {
const URL_REGEXP = /[^:/?#]:\/\/[^?#]/u;
const sourceCode = context.sourceCode;
function computeLineLength(line, tabWidth$1) {
let extraCharacterCount = 0;
line.replace(/\t/gu, (_, offset) => {
const totalOffset = offset + extraCharacterCount;
const previousTabStopOffset = tabWidth$1 ? totalOffset % tabWidth$1 : 0;
const spaceCount = tabWidth$1 - previousTabStopOffset;
extraCharacterCount += spaceCount - 1;
return "";
});
return Array.from(line).length + extraCharacterCount;
}
const options = Object.assign({}, context.options[context.options.length - 1]);
if (typeof context.options[0] === "number") options.code = context.options[0];
if (typeof context.options[1] === "number") options.tabWidth = context.options[1];
const maxLength = typeof options.code === "number" ? options.code : 80;
const tabWidth = typeof options.tabWidth === "number" ? options.tabWidth : 4;
const ignoreComments = !!options.ignoreComments;
const ignoreStrings = !!options.ignoreStrings;
const ignoreTemplateLiterals = !!options.ignoreTemplateLiterals;
const ignoreRegExpLiterals = !!options.ignoreRegExpLiterals;
const ignoreTrailingComments = !!options.ignoreTrailingComments || !!options.ignoreComments;
const ignoreUrls = !!options.ignoreUrls;
const maxCommentLength = options.comments;
let ignorePattern = null;
if (options.ignorePattern) ignorePattern = new RegExp(options.ignorePattern, "u");
function isTrailingComment(line, lineNumber, comment) {
return comment && comment.loc.start.line === lineNumber && lineNumber <= comment.loc.end.line && (comment.loc.end.line > lineNumber || comment.loc.end.column === line.length);
}
function isFullLineComment(line, lineNumber, comment) {
const start = comment.loc.start;
const end = comment.loc.end;
const isFirstTokenOnLine = !line.slice(0, comment.loc.start.column).trim();
return comment && (start.line < lineNumber || start.line === lineNumber && isFirstTokenOnLine) && (end.line > lineNumber || end.line === lineNumber && end.column === line.length);
}
function isJSXEmptyExpressionInSingleLineContainer(node) {
if (!node || !node.parent || node.type !== "JSXEmptyExpression" || node.parent.type !== "JSXExpressionContainer") return false;
const parent = node.parent;
return isSingleLine(parent);
}
function stripTrailingComment(line, comment) {
return line.slice(0, comment.loc.start.column).replace(/\s+$/u, "");
}
function ensureArrayAndPush(object, key, value) {
if (!Array.isArray(object[key])) object[key] = [];
object[key].push(value);
}
function getAllStrings() {
return sourceCode.ast.tokens.filter((token) => token.type === "String" || token.type === "JSXText" && sourceCode.getNodeByRangeIndex(token.range[0] - 1).type === "JSXAttribute");
}
function getAllTemplateLiterals() {
return sourceCode.ast.tokens.filter((token) => token.type === "Template");
}
function getAllRegExpLiterals() {
return sourceCode.ast.tokens.filter((token) => token.type === "RegularExpression");
}
function groupArrayByLineNumber(arr) {
const obj = {};
for (let i = 0; i < arr.length; i++) {
const node = arr[i];
for (let j = node.loc.start.line; j <= node.loc.end.line; ++j) ensureArrayAndPush(obj, j, node);
}
return obj;
}
function getAllComments() {
const comments = [];
sourceCode.getAllComments().forEach((commentNode) => {
const containingNode = sourceCode.getNodeByRangeIndex(commentNode.range[0]);
if (isJSXEmptyExpressionInSingleLineContainer(containingNode)) {
if (comments[comments.length - 1] !== containingNode.parent) comments.push(containingNode.parent);
} else comments.push(commentNode);
});
return comments;
}
function checkProgramForMaxLength(node) {
const lines = sourceCode.lines;
const comments = ignoreComments || maxCommentLength || ignoreTrailingComments ? getAllComments() : [];
let commentsIndex = 0;
const strings = getAllStrings();
const stringsByLine = groupArrayByLineNumber(strings);
const templateLiterals = getAllTemplateLiterals();
const templateLiteralsByLine = groupArrayByLineNumber(templateLiterals);
const regExpLiterals = getAllRegExpLiterals();
const regExpLiteralsByLine = groupArrayByLineNumber(regExpLiterals);
lines.forEach((line, i) => {
const lineNumber = i + 1;
let lineIsComment = false;
let textToMeasure;
if (commentsIndex < comments.length) {
let comment = null;
do
comment = comments[++commentsIndex];
while (comment && comment.loc.start.line <= lineNumber);
comment = comments[--commentsIndex];
if (isFullLineComment(line, lineNumber, comment)) {
lineIsComment = true;
textToMeasure = line;
} else if (ignoreTrailingComments && isTrailingComment(line, lineNumber, comment)) {
textToMeasure = stripTrailingComment(line, comment);
let lastIndex = commentsIndex;
while (isTrailingComment(textToMeasure, lineNumber, comments[--lastIndex])) textToMeasure = stripTrailingComment(textToMeasure, comments[lastIndex]);
} else textToMeasure = line;
} else textToMeasure = line;
if (ignorePattern && ignorePattern.test(textToMeasure) || ignoreUrls && URL_REGEXP.test(textToMeasure) || ignoreStrings && stringsByLine[lineNumber] || ignoreTemplateLiterals && templateLiteralsByLine[lineNumber] || ignoreRegExpLiterals && regExpLiteralsByLine[lineNumber]) return;
const lineLength = computeLineLength(textToMeasure, tabWidth);
const commentLengthApplies = lineIsComment && maxCommentLength;
if (lineIsComment && ignoreComments) return;
const loc = {
start: {
line: lineNumber,
column: 0
},
end: {
line: lineNumber,
column: textToMeasure.length
}
};
if (commentLengthApplies) {
if (lineLength > maxCommentLength) context.report({
node,
loc,
messageId: "maxComment",
data: {
lineLength,
maxCommentLength
}
});
} else if (lineLength > maxLength) context.report({
node,
loc,
messageId: "max",
data: {
lineLength,
maxLength
}
});
});
}
return { Program: checkProgramForMaxLength };
}
});
export { max_len_default };

View File

@@ -0,0 +1,104 @@
import { ast_exports, createRule } from "../utils.js";
const listeningNodes = [
"BreakStatement",
"ClassDeclaration",
"ContinueStatement",
"DebuggerStatement",
"DoWhileStatement",
"ExpressionStatement",
"ForInStatement",
"ForOfStatement",
"ForStatement",
"FunctionDeclaration",
"IfStatement",
"ImportDeclaration",
"LabeledStatement",
"ReturnStatement",
"SwitchStatement",
"ThrowStatement",
"TryStatement",
"VariableDeclaration",
"WhileStatement",
"WithStatement",
"ExportNamedDeclaration",
"ExportDefaultDeclaration",
"ExportAllDeclaration"
];
var max_statements_per_line_default = createRule({
name: "max-statements-per-line",
meta: {
type: "layout",
docs: { description: "Enforce a maximum number of statements allowed per line" },
schema: [{
type: "object",
properties: {
max: {
type: "integer",
minimum: 1,
default: 1
},
ignoredNodes: {
type: "array",
items: {
type: "string",
enum: listeningNodes
}
}
},
additionalProperties: false
}],
messages: { exceed: "This line has {{numberOfStatementsOnThisLine}} {{statements}}. Maximum allowed is {{maxStatementsPerLine}}." }
},
create(context) {
const sourceCode = context.sourceCode;
const options = context.options[0] || {};
const maxStatementsPerLine = typeof options.max !== "undefined" ? options.max : 1;
const ignoredNodes = options.ignoredNodes || [];
let lastStatementLine = 0;
let numberOfStatementsOnThisLine = 0;
let firstExtraStatement = null;
const SINGLE_CHILD_ALLOWED = /^(?:(?:DoWhile|For|ForIn|ForOf|If|Labeled|While)Statement|Export(?:Default|Named)Declaration)$/u;
function reportFirstExtraStatementAndClear() {
if (firstExtraStatement) context.report({
node: firstExtraStatement,
messageId: "exceed",
data: {
numberOfStatementsOnThisLine,
maxStatementsPerLine,
statements: numberOfStatementsOnThisLine === 1 ? "statement" : "statements"
}
});
firstExtraStatement = null;
}
function getActualLastToken(node) {
return sourceCode.getLastToken(node, ast_exports.isNotSemicolonToken);
}
function enterStatement(node) {
const line = node.loc.start.line;
if (node.parent && SINGLE_CHILD_ALLOWED.test(node.parent.type) && (!("alternate" in node.parent) || node.parent.alternate !== node)) return;
if (line === lastStatementLine) numberOfStatementsOnThisLine += 1;
else {
reportFirstExtraStatementAndClear();
numberOfStatementsOnThisLine = 1;
lastStatementLine = line;
}
if (numberOfStatementsOnThisLine === maxStatementsPerLine + 1) firstExtraStatement = firstExtraStatement || node;
}
function leaveStatement(node) {
const line = getActualLastToken(node).loc.end.line;
if (line !== lastStatementLine) {
reportFirstExtraStatementAndClear();
numberOfStatementsOnThisLine = 1;
lastStatementLine = line;
}
}
const listeners = { "Program:exit": reportFirstExtraStatementAndClear };
for (const node of listeningNodes) {
if (ignoredNodes.includes(node)) continue;
listeners[node] = enterStatement;
listeners[`${node}:exit`] = leaveStatement;
}
return listeners;
}
});
export { max_statements_per_line_default };

View File

@@ -0,0 +1,190 @@
import { AST_NODE_TYPES, createRule, deepMerge, isSingleLine } from "../utils.js";
function isLastTokenEndOfLine(token, line) {
const positionInLine = token.loc.start.column;
return positionInLine === line.length - 1;
}
function isCommentsEndOfLine(token, comments, line) {
if (!comments) return false;
if (comments.loc.end.line > token.loc.end.line) return true;
const positionInLine = comments.loc.end.column;
return positionInLine === line.length;
}
function makeFixFunction({ optsNone, optsSemi, lastToken, commentsAfterLastToken, missingDelimiter, lastTokenLine, isSingleLine: isSingleLine$1 }) {
if (optsNone && !isLastTokenEndOfLine(lastToken, lastTokenLine) && !isCommentsEndOfLine(lastToken, commentsAfterLastToken, lastTokenLine) && !isSingleLine$1) return;
return (fixer) => {
if (optsNone) return fixer.remove(lastToken);
const token = optsSemi ? ";" : ",";
if (missingDelimiter) return fixer.insertTextAfter(lastToken, token);
return fixer.replaceText(lastToken, token);
};
}
const BASE_SCHEMA = {
type: "object",
properties: {
multiline: {
type: "object",
properties: {
delimiter: { $ref: "#/items/0/$defs/multiLineOption" },
requireLast: { type: "boolean" }
},
additionalProperties: false
},
singleline: {
type: "object",
properties: {
delimiter: { $ref: "#/items/0/$defs/singleLineOption" },
requireLast: { type: "boolean" }
},
additionalProperties: false
}
},
additionalProperties: false
};
var member_delimiter_style_default = createRule({
name: "member-delimiter-style",
meta: {
type: "layout",
docs: { description: "Require a specific member delimiter style for interfaces and type literals" },
fixable: "whitespace",
messages: {
unexpectedComma: "Unexpected separator (,).",
unexpectedSemi: "Unexpected separator (;).",
expectedComma: "Expected a comma.",
expectedSemi: "Expected a semicolon."
},
schema: [{
$defs: {
multiLineOption: {
type: "string",
enum: [
"none",
"semi",
"comma"
]
},
singleLineOption: {
type: "string",
enum: ["semi", "comma"]
},
delimiterConfig: BASE_SCHEMA
},
type: "object",
properties: {
...BASE_SCHEMA.properties,
overrides: {
type: "object",
properties: {
interface: { $ref: "#/items/0/$defs/delimiterConfig" },
typeLiteral: { $ref: "#/items/0/$defs/delimiterConfig" }
},
additionalProperties: false
},
multilineDetection: {
type: "string",
enum: ["brackets", "last-member"]
}
},
additionalProperties: false
}]
},
defaultOptions: [{
multiline: {
delimiter: "semi",
requireLast: true
},
singleline: {
delimiter: "semi",
requireLast: false
},
multilineDetection: "brackets"
}],
create(context, [options]) {
const sourceCode = context.sourceCode;
const baseOptions = options;
const overrides = baseOptions.overrides ?? {};
const interfaceOptions = deepMerge(baseOptions, overrides.interface);
const typeLiteralOptions = deepMerge(baseOptions, overrides.typeLiteral);
function checkLastToken(member, opts, isLast) {
function getOption(type) {
if (isLast && !opts.requireLast) return type === "none";
return opts.delimiter === type;
}
let messageId = null;
let missingDelimiter = false;
const lastToken = sourceCode.getLastToken(member, { includeComments: false });
if (!lastToken) return;
const commentsAfterLastToken = sourceCode.getCommentsAfter(lastToken).pop();
const sourceCodeLines = sourceCode.getLines();
const lastTokenLine = sourceCodeLines[lastToken?.loc.start.line - 1];
const optsSemi = getOption("semi");
const optsComma = getOption("comma");
const optsNone = getOption("none");
if (lastToken.value === ";") {
if (optsComma) messageId = "expectedComma";
else if (optsNone) {
missingDelimiter = true;
messageId = "unexpectedSemi";
}
} else if (lastToken.value === ",") {
if (optsSemi) messageId = "expectedSemi";
else if (optsNone) {
missingDelimiter = true;
messageId = "unexpectedComma";
}
} else if (optsSemi) {
missingDelimiter = true;
messageId = "expectedSemi";
} else if (optsComma) {
missingDelimiter = true;
messageId = "expectedComma";
}
if (messageId) context.report({
node: lastToken,
loc: {
start: {
line: lastToken.loc.end.line,
column: lastToken.loc.end.column
},
end: {
line: lastToken.loc.end.line,
column: lastToken.loc.end.column
}
},
messageId,
fix: makeFixFunction({
optsNone,
optsSemi,
lastToken,
commentsAfterLastToken,
missingDelimiter,
lastTokenLine,
isSingleLine: opts.type === "single-line"
})
});
}
function checkMemberSeparatorStyle(node) {
const members = node.type === AST_NODE_TYPES.TSInterfaceBody ? node.body : node.members;
let _isSingleLine = isSingleLine(node);
if (options.multilineDetection === "last-member" && !_isSingleLine && members.length > 0) {
const lastMember = members[members.length - 1];
if (lastMember.loc.end.line === node.loc.end.line) _isSingleLine = true;
}
const typeOpts = node.type === AST_NODE_TYPES.TSInterfaceBody ? interfaceOptions : typeLiteralOptions;
const opts = _isSingleLine ? {
...typeOpts.singleline,
type: "single-line"
} : {
...typeOpts.multiline,
type: "multi-line"
};
members.forEach((member, index) => {
checkLastToken(member, opts ?? {}, index === members.length - 1);
});
}
return {
TSInterfaceBody: checkMemberSeparatorStyle,
TSTypeLiteral: checkMemberSeparatorStyle
};
}
});
export { member_delimiter_style_default };

View File

@@ -0,0 +1,272 @@
import { COMMENTS_IGNORE_PATTERN, WHITE_SPACES_PATTERN, ast_exports, createRule, isHashbangComment, isSingleLine, isWhiteSpaces } from "../utils.js";
var multiline_comment_style_default = createRule({
name: "multiline-comment-style",
meta: {
type: "suggestion",
docs: { description: "Enforce a particular style for multiline comments" },
fixable: "whitespace",
schema: { anyOf: [{
type: "array",
items: [{
enum: ["starred-block", "bare-block"],
type: "string"
}],
additionalItems: false
}, {
type: "array",
items: [{
enum: ["separate-lines"],
type: "string"
}, {
type: "object",
properties: {
checkJSDoc: { type: "boolean" },
checkExclamation: { type: "boolean" }
},
additionalProperties: false
}],
additionalItems: false
}] },
messages: {
expectedBlock: "Expected a block comment instead of consecutive line comments.",
expectedBareBlock: "Expected a block comment without padding stars.",
startNewline: "Expected a linebreak after '/*'.",
endNewline: "Expected a linebreak before '*/'.",
missingStar: "Expected a '*' at the start of this line.",
alignment: "Expected this line to be aligned with the start of the comment.",
expectedLines: "Expected multiple line comments instead of a block comment."
}
},
create(context) {
const sourceCode = context.sourceCode;
const option = context.options[0] || "starred-block";
const params = context.options[1] || {};
const checkJSDoc = !!params.checkJSDoc;
const checkExclamation = !!params.checkExclamation;
function isStarredCommentLine(line) {
return /^\s*\*/u.test(line);
}
function isStarredBlockComment([firstComment]) {
if (firstComment.type !== "Block") return false;
const lines = firstComment.value.split(ast_exports.LINEBREAK_MATCHER);
return lines.length > 0 && lines.every((line, i) => i === 0 || i === lines.length - 1 ? isWhiteSpaces(line) : isStarredCommentLine(line));
}
function isJSDocComment([firstComment]) {
if (firstComment.type !== "Block") return false;
const lines = firstComment.value.split(ast_exports.LINEBREAK_MATCHER);
return /^\*\s*$/u.test(lines[0]) && lines.slice(1, -1).every((line) => /^\s* /u.test(line)) && isWhiteSpaces(lines.at(-1));
}
function isExclamationComment([firstComment]) {
if (firstComment.type !== "Block") return false;
const lines = firstComment.value.split(ast_exports.LINEBREAK_MATCHER);
return /^!\s*$/u.test(lines[0]) && lines.slice(1, -1).every((line) => /^\s* /u.test(line)) && isWhiteSpaces(lines.at(-1));
}
function processSeparateLineComments(commentGroup) {
const allLinesHaveLeadingSpace = commentGroup.every(({ value: line }) => line.trim().length === 0 || line.startsWith(" "));
return commentGroup.map(({ value }) => allLinesHaveLeadingSpace ? value.replace(/^ /u, "") : value);
}
function processStarredBlockComment(comment) {
const lines = comment.value.split(ast_exports.LINEBREAK_MATCHER).slice(1, -1).map((line) => line.replace(WHITE_SPACES_PATTERN, ""));
const allLinesHaveLeadingSpace = lines.every((line) => {
const lineWithoutPrefix = line.replace(/\s*\*/u, "");
return lineWithoutPrefix.trim().length === 0 || lineWithoutPrefix.startsWith(" ");
});
return lines.map((line) => line.replace(allLinesHaveLeadingSpace ? /\s*\* ?/u : /\s*\*/u, ""));
}
function processBareBlockComment(comment) {
const lines = comment.value.split(ast_exports.LINEBREAK_MATCHER).map((line) => line.replace(WHITE_SPACES_PATTERN, ""));
const leadingWhitespace = `${sourceCode.text.slice(comment.range[0] - comment.loc.start.column, comment.range[0])} `;
let offset = "";
for (const [i, line] of lines.entries()) {
if (!line.trim().length || i === 0) continue;
const [, lineOffset] = line.match(/^(\s*\*?\s*)/u);
if (lineOffset.length < leadingWhitespace.length) {
const newOffset = leadingWhitespace.slice(lineOffset.length - leadingWhitespace.length);
if (newOffset.length > offset.length) offset = newOffset;
}
}
return lines.map((line) => {
const match = line.match(/^(\s*\*?\s*)(.*)/u);
const [, lineOffset, lineContents] = match;
if (lineOffset.length > leadingWhitespace.length) return `${lineOffset.slice(leadingWhitespace.length - (offset.length + lineOffset.length))}${lineContents}`;
if (lineOffset.length < leadingWhitespace.length) return `${lineOffset.slice(leadingWhitespace.length)}${lineContents}`;
return lineContents;
});
}
function getCommentLines(commentGroup) {
const [firstComment] = commentGroup;
if (firstComment.type === "Line") return processSeparateLineComments(commentGroup);
if (isStarredBlockComment(commentGroup)) return processStarredBlockComment(firstComment);
return processBareBlockComment(firstComment);
}
function getInitialOffset(comment) {
return sourceCode.text.slice(comment.range[0] - comment.loc.start.column, comment.range[0]);
}
function convertToStarredBlock(firstComment, commentLinesList) {
const initialOffset = getInitialOffset(firstComment);
return `/*\n${commentLinesList.map((line) => `${initialOffset} * ${line}`).join("\n")}\n${initialOffset} */`;
}
function convertToSeparateLines(firstComment, commentLinesList) {
return commentLinesList.map((line) => `// ${line}`).join(`\n${getInitialOffset(firstComment)}`);
}
function convertToBlock(firstComment, commentLinesList) {
return `/* ${commentLinesList.join(`\n${getInitialOffset(firstComment)} `)} */`;
}
const commentGroupCheckers = {
"starred-block": function(commentGroup) {
const [firstComment] = commentGroup;
const commentLines = getCommentLines(commentGroup);
if (commentLines.some((value) => value.includes("*/"))) return;
if (commentGroup.length > 1) context.report({
loc: {
start: firstComment.loc.start,
end: commentGroup.at(-1).loc.end
},
messageId: "expectedBlock",
fix(fixer) {
const range = [firstComment.range[0], commentGroup.at(-1).range[1]];
return commentLines.some((value) => value.startsWith("/")) ? null : fixer.replaceTextRange(range, convertToStarredBlock(firstComment, commentLines));
}
});
else {
const lines = firstComment.value.split(ast_exports.LINEBREAK_MATCHER);
const expectedLeadingWhitespace = getInitialOffset(firstComment);
const expectedLinePrefix = `${expectedLeadingWhitespace} *`;
if (!/^[*!]?\s*$/u.test(lines[0])) {
const start = /^[*!]/.test(firstComment.value) ? firstComment.range[0] + 1 : firstComment.range[0];
context.report({
loc: {
start: firstComment.loc.start,
end: {
line: firstComment.loc.start.line,
column: firstComment.loc.start.column + 2
}
},
messageId: "startNewline",
fix: (fixer) => fixer.insertTextAfterRange([start, start + 2], `\n${expectedLinePrefix}`)
});
}
if (!isWhiteSpaces(lines.at(-1))) context.report({
loc: {
start: {
line: firstComment.loc.end.line,
column: firstComment.loc.end.column - 2
},
end: firstComment.loc.end
},
messageId: "endNewline",
fix: (fixer) => fixer.replaceTextRange([firstComment.range[1] - 2, firstComment.range[1]], `\n${expectedLinePrefix}/`)
});
for (let lineNumber = firstComment.loc.start.line + 1; lineNumber <= firstComment.loc.end.line; lineNumber++) {
const lineText = sourceCode.lines[lineNumber - 1];
const errorType = isStarredCommentLine(lineText) ? "alignment" : "missingStar";
if (!lineText.startsWith(expectedLinePrefix)) context.report({
loc: {
start: {
line: lineNumber,
column: 0
},
end: {
line: lineNumber,
column: lineText.length
}
},
messageId: errorType,
fix(fixer) {
const lineStartIndex = sourceCode.getIndexFromLoc({
line: lineNumber,
column: 0
});
if (errorType === "alignment") {
const [, commentTextPrefix$1 = ""] = lineText.match(/^(\s*\*)/u) || [];
const commentTextStartIndex$1 = lineStartIndex + commentTextPrefix$1.length;
return fixer.replaceTextRange([lineStartIndex, commentTextStartIndex$1], expectedLinePrefix);
}
const [, commentTextPrefix = ""] = lineText.match(/^(\s*)/u) || [];
const commentTextStartIndex = lineStartIndex + commentTextPrefix.length;
let offset;
for (const [idx, line] of lines.entries()) {
if (!/\S+/u.test(line)) continue;
const lineTextToAlignWith = sourceCode.lines[firstComment.loc.start.line - 1 + idx];
const [, prefix = "", initialOffset = ""] = lineTextToAlignWith.match(/^(\s*(?:\/?\*)?(\s*))/u) || [];
offset = `${commentTextPrefix.slice(prefix.length)}${initialOffset}`;
if (/^\s*\//u.test(lineText) && offset.length === 0) offset += " ";
break;
}
return fixer.replaceTextRange([lineStartIndex, commentTextStartIndex], `${expectedLinePrefix}${offset}`);
}
});
}
}
},
"separate-lines": function(commentGroup) {
const [firstComment] = commentGroup;
const isJSDoc = isJSDocComment(commentGroup);
const isExclamation = isExclamationComment(commentGroup);
if (firstComment.type !== "Block" || !checkJSDoc && isJSDoc || !checkExclamation && isExclamation) return;
let commentLines = getCommentLines(commentGroup);
if (isJSDoc || isExclamation) commentLines = commentLines.slice(1, commentLines.length - 1);
const tokenAfter = sourceCode.getTokenAfter(firstComment, { includeComments: true });
if (tokenAfter && (0, ast_exports.isTokenOnSameLine)(firstComment, tokenAfter)) return;
context.report({
loc: {
start: firstComment.loc.start,
end: {
line: firstComment.loc.start.line,
column: firstComment.loc.start.column + 2
}
},
messageId: "expectedLines",
fix(fixer) {
return fixer.replaceText(firstComment, convertToSeparateLines(firstComment, commentLines));
}
});
},
"bare-block": function(commentGroup) {
if (isJSDocComment(commentGroup) || isExclamationComment(commentGroup)) return;
const [firstComment] = commentGroup;
const commentLines = getCommentLines(commentGroup);
if (firstComment.type === "Line" && commentLines.length > 1 && !commentLines.some((value) => value.includes("*/"))) context.report({
loc: {
start: firstComment.loc.start,
end: commentGroup.at(-1).loc.end
},
messageId: "expectedBlock",
fix(fixer) {
return fixer.replaceTextRange([firstComment.range[0], commentGroup.at(-1).range[1]], convertToBlock(firstComment, commentLines));
}
});
if (isStarredBlockComment(commentGroup)) context.report({
loc: {
start: firstComment.loc.start,
end: {
line: firstComment.loc.start.line,
column: firstComment.loc.start.column + 2
}
},
messageId: "expectedBareBlock",
fix(fixer) {
return fixer.replaceText(firstComment, convertToBlock(firstComment, commentLines));
}
});
}
};
return { Program() {
return sourceCode.getAllComments().filter((comment) => {
if (isHashbangComment(comment) || COMMENTS_IGNORE_PATTERN.test(comment.value)) return false;
const tokenBefore = sourceCode.getTokenBefore(comment, { includeComments: true });
return !tokenBefore || tokenBefore.loc.end.line < comment.loc.start.line;
}).reduce((commentGroups, comment, index, commentList) => {
const tokenBefore = sourceCode.getTokenBefore(comment, { includeComments: true });
if (comment.type === "Line" && index && commentList[index - 1].type === "Line" && tokenBefore && tokenBefore.loc.end.line === comment.loc.start.line - 1 && tokenBefore === commentList[index - 1]) commentGroups.at(-1).push(comment);
else commentGroups.push([comment]);
return commentGroups;
}, []).forEach((commentGroup) => {
if (commentGroup.length === 1 && isSingleLine(commentGroup[0])) return;
const check = commentGroupCheckers[option];
check(commentGroup);
});
} };
}
});
export { multiline_comment_style_default };

View File

@@ -0,0 +1,108 @@
import { ast_exports, createRule, isSingleLine } from "../utils.js";
var multiline_ternary_default = createRule({
name: "multiline-ternary",
meta: {
type: "layout",
docs: { description: "Enforce newlines between operands of ternary expressions" },
schema: [{
type: "string",
enum: [
"always",
"always-multiline",
"never"
]
}, {
type: "object",
properties: { ignoreJSX: {
type: "boolean",
default: false
} },
additionalProperties: false
}],
messages: {
expectedTestCons: "Expected newline between test and consequent of ternary expression.",
expectedConsAlt: "Expected newline between consequent and alternate of ternary expression.",
unexpectedTestCons: "Unexpected newline between test and consequent of ternary expression.",
unexpectedConsAlt: "Unexpected newline between consequent and alternate of ternary expression."
},
fixable: "whitespace"
},
create(context) {
const sourceCode = context.sourceCode;
const multiline = context.options[0] !== "never";
const allowSingleLine = context.options[0] === "always-multiline";
const IGNORE_JSX = context.options[1] && context.options[1].ignoreJSX;
return { ConditionalExpression(node) {
const questionToken = sourceCode.getTokenAfter(node.test, ast_exports.isNotClosingParenToken);
const colonToken = sourceCode.getTokenAfter(node.consequent, ast_exports.isNotClosingParenToken);
const firstTokenOfTest = sourceCode.getFirstToken(node);
const lastTokenOfTest = sourceCode.getTokenBefore(questionToken);
const firstTokenOfConsequent = sourceCode.getTokenAfter(questionToken);
const lastTokenOfConsequent = sourceCode.getTokenBefore(colonToken);
const firstTokenOfAlternate = sourceCode.getTokenAfter(colonToken);
const areTestAndConsequentOnSameLine = (0, ast_exports.isTokenOnSameLine)(lastTokenOfTest, firstTokenOfConsequent);
const areConsequentAndAlternateOnSameLine = (0, ast_exports.isTokenOnSameLine)(lastTokenOfConsequent, firstTokenOfAlternate);
const hasComments = !!sourceCode.getCommentsInside(node).length;
if (IGNORE_JSX) {
if (node.parent.type === "JSXElement" || node.parent.type === "JSXFragment" || node.parent.type === "JSXExpressionContainer") return null;
}
if (!multiline) {
if (!areTestAndConsequentOnSameLine) context.report({
node: node.test,
loc: {
start: firstTokenOfTest.loc.start,
end: lastTokenOfTest.loc.end
},
messageId: "unexpectedTestCons",
fix(fixer) {
if (hasComments) return null;
const fixers = [];
const areTestAndQuestionOnSameLine = (0, ast_exports.isTokenOnSameLine)(lastTokenOfTest, questionToken);
const areQuestionAndConsOnSameLine = (0, ast_exports.isTokenOnSameLine)(questionToken, firstTokenOfConsequent);
if (!areTestAndQuestionOnSameLine) fixers.push(fixer.removeRange([lastTokenOfTest.range[1], questionToken.range[0]]));
if (!areQuestionAndConsOnSameLine) fixers.push(fixer.removeRange([questionToken.range[1], firstTokenOfConsequent.range[0]]));
return fixers;
}
});
if (!areConsequentAndAlternateOnSameLine) context.report({
node: node.consequent,
loc: {
start: firstTokenOfConsequent.loc.start,
end: lastTokenOfConsequent.loc.end
},
messageId: "unexpectedConsAlt",
fix(fixer) {
if (hasComments) return null;
const fixers = [];
const areConsAndColonOnSameLine = (0, ast_exports.isTokenOnSameLine)(lastTokenOfConsequent, colonToken);
const areColonAndAltOnSameLine = (0, ast_exports.isTokenOnSameLine)(colonToken, firstTokenOfAlternate);
if (!areConsAndColonOnSameLine) fixers.push(fixer.removeRange([lastTokenOfConsequent.range[1], colonToken.range[0]]));
if (!areColonAndAltOnSameLine) fixers.push(fixer.removeRange([colonToken.range[1], firstTokenOfAlternate.range[0]]));
return fixers;
}
});
} else {
if (allowSingleLine && isSingleLine(node)) return;
if (areTestAndConsequentOnSameLine) context.report({
node: node.test,
loc: {
start: firstTokenOfTest.loc.start,
end: lastTokenOfTest.loc.end
},
messageId: "expectedTestCons",
fix: (fixer) => hasComments ? null : fixer.replaceTextRange([lastTokenOfTest.range[1], questionToken.range[0]], "\n")
});
if (areConsequentAndAlternateOnSameLine) context.report({
node: node.consequent,
loc: {
start: firstTokenOfConsequent.loc.start,
end: lastTokenOfConsequent.loc.end
},
messageId: "expectedConsAlt",
fix: (fixer) => hasComments ? null : fixer.replaceTextRange([lastTokenOfConsequent.range[1], colonToken.range[0]], "\n")
});
}
} };
}
});
export { multiline_ternary_default };

View File

@@ -0,0 +1,46 @@
import { ast_exports, createRule } from "../utils.js";
var new_parens_default = createRule({
name: "new-parens",
meta: {
type: "layout",
docs: { description: "Enforce or disallow parentheses when invoking a constructor with no arguments" },
fixable: "code",
schema: [{
type: "string",
enum: ["always", "never"]
}],
messages: {
missing: "Missing '()' invoking a constructor.",
unnecessary: "Unnecessary '()' invoking a constructor with no arguments."
}
},
create(context) {
const options = context.options;
const always = options[0] !== "never";
const sourceCode = context.sourceCode;
return { NewExpression(node) {
if (node.arguments.length !== 0) return;
const lastToken = sourceCode.getLastToken(node);
const hasLastParen = lastToken && (0, ast_exports.isClosingParenToken)(lastToken);
const tokenBeforeLastToken = sourceCode.getTokenBefore(lastToken);
const hasParens = hasLastParen && (0, ast_exports.isOpeningParenToken)(tokenBeforeLastToken) && node.callee.range[1] < node.range[1];
if (always) {
if (!hasParens) context.report({
node,
messageId: "missing",
fix: (fixer) => fixer.insertTextAfter(node, "()")
});
} else if (hasParens) context.report({
node,
messageId: "unnecessary",
fix: (fixer) => [
fixer.remove(tokenBeforeLastToken),
fixer.remove(lastToken),
fixer.insertTextBefore(node, "("),
fixer.insertTextAfter(node, ")")
]
});
} };
}
});
export { new_parens_default };

View File

@@ -0,0 +1,67 @@
import { ast_exports, createRule, skipChainExpression } from "../utils.js";
var newline_per_chained_call_default = createRule({
name: "newline-per-chained-call",
meta: {
type: "layout",
docs: { description: "Require a newline after each call in a method chain" },
fixable: "whitespace",
schema: [{
type: "object",
properties: { ignoreChainWithDepth: {
type: "integer",
minimum: 1,
maximum: 10,
default: 2
} },
additionalProperties: false
}],
messages: { expected: "Expected line break before `{{callee}}`." }
},
create(context) {
const options = context.options[0] || {};
const ignoreChainWithDepth = options.ignoreChainWithDepth || 2;
const sourceCode = context.sourceCode;
function getPrefix(node) {
if (node.computed) {
if (node.optional) return "?.[";
return "[";
}
if (node.optional) return "?.";
return ".";
}
function getPropertyText(node) {
const prefix = getPrefix(node);
const lines = sourceCode.getText(node.property).split(ast_exports.LINEBREAK_MATCHER);
const suffix = node.computed && lines.length === 1 ? "]" : "";
return prefix + lines[0] + suffix;
}
return { "CallExpression:exit": function(node) {
const callee = skipChainExpression(node.callee);
if (callee.type !== "MemberExpression") return;
let parent = skipChainExpression(callee.object);
let depth = 1;
while (parent && "callee" in parent && parent.callee) {
depth += 1;
const parentCallee = skipChainExpression(parent.callee);
if (!("object" in parentCallee)) break;
parent = skipChainExpression(parentCallee.object);
}
if (depth > ignoreChainWithDepth && (0, ast_exports.isTokenOnSameLine)(callee.object, callee.property)) {
const firstTokenAfterObject = sourceCode.getTokenAfter(callee.object, ast_exports.isNotClosingParenToken);
context.report({
node: callee.property,
loc: {
start: firstTokenAfterObject.loc.start,
end: callee.loc.end
},
messageId: "expected",
data: { callee: getPropertyText(callee) },
fix(fixer) {
return fixer.insertTextBefore(firstTokenAfterObject, "\n");
}
});
}
} };
}
});
export { newline_per_chained_call_default };

View File

@@ -0,0 +1,45 @@
import { createRule, isParenthesised } from "../utils.js";
function isConditional(node) {
return node.type === "ConditionalExpression";
}
var no_confusing_arrow_default = createRule({
name: "no-confusing-arrow",
meta: {
type: "layout",
docs: { description: "Disallow arrow functions where they could be confused with comparisons" },
fixable: "code",
schema: [{
type: "object",
properties: {
allowParens: {
type: "boolean",
default: true
},
onlyOneSimpleParam: {
type: "boolean",
default: false
}
},
additionalProperties: false
}],
messages: { confusing: "Arrow function used ambiguously with a conditional expression." }
},
create(context) {
const config = context.options[0] || {};
const allowParens = config.allowParens || config.allowParens === void 0;
const onlyOneSimpleParam = config.onlyOneSimpleParam;
const sourceCode = context.sourceCode;
function checkArrowFunc(node) {
const body = node.body;
if (isConditional(body) && !(allowParens && isParenthesised(sourceCode, body)) && !(onlyOneSimpleParam && !(node.params.length === 1 && node.params[0].type === "Identifier"))) context.report({
node,
messageId: "confusing",
fix(fixer) {
return allowParens ? fixer.replaceText(node.body, `(${sourceCode.getText(node.body)})`) : null;
}
});
}
return { ArrowFunctionExpression: checkArrowFunc };
}
});
export { no_confusing_arrow_default };

View File

@@ -0,0 +1,728 @@
import { AST_NODE_TYPES, ast_exports, canTokensBeAdjacent, createRule, getPrecedence, getStaticPropertyName, isDecimalInteger, isKeywordToken, isMixedLogicalAndCoalesceExpressions, isRegExpLiteral, isSingleLine, isTopLevelExpressionStatement, skipChainExpression, warnDeprecatedOptions } from "../utils.js";
const isTypeAssertion = (0, ast_exports.isNodeOfTypes)([
AST_NODE_TYPES.TSAsExpression,
AST_NODE_TYPES.TSNonNullExpression,
AST_NODE_TYPES.TSSatisfiesExpression,
AST_NODE_TYPES.TSTypeAssertion
]);
var no_extra_parens_default = createRule({
name: "no-extra-parens",
meta: {
type: "layout",
docs: { description: "Disallow unnecessary parentheses" },
fixable: "code",
schema: { anyOf: [{
type: "array",
items: [{
type: "string",
enum: ["functions"]
}],
minItems: 0,
maxItems: 1
}, {
type: "array",
items: [{
type: "string",
enum: ["all"]
}, {
type: "object",
properties: {
conditionalAssign: { type: "boolean" },
ternaryOperandBinaryExpressions: { type: "boolean" },
nestedBinaryExpressions: { type: "boolean" },
returnAssign: { type: "boolean" },
ignoreJSX: {
type: "string",
enum: [
"none",
"all",
"single-line",
"multi-line"
]
},
enforceForArrowConditionals: { type: "boolean" },
enforceForSequenceExpressions: { type: "boolean" },
enforceForNewInMemberExpressions: { type: "boolean" },
enforceForFunctionPrototypeMethods: { type: "boolean" },
allowParensAfterCommentPattern: { type: "string" },
nestedConditionalExpressions: { type: "boolean" },
allowNodesInSpreadElement: {
type: "object",
properties: {
ConditionalExpression: { type: "boolean" },
LogicalExpression: { type: "boolean" },
AwaitExpression: { type: "boolean" }
},
additionalProperties: false
},
ignoredNodes: {
type: "array",
items: {
type: "string",
not: {
type: "string",
pattern: ":exit$"
}
}
}
},
additionalProperties: false
}],
minItems: 0,
maxItems: 2
}] },
messages: { unexpected: "Unnecessary parentheses around expression." }
},
defaultOptions: ["all"],
create(context, [nodes, options]) {
const sourceCode = context.sourceCode;
const tokensToIgnore = /* @__PURE__ */ new WeakSet();
const precedence = getPrecedence;
const ALL_NODES = nodes !== "functions";
const EXCEPT_COND_ASSIGN = ALL_NODES && options?.conditionalAssign === false;
const EXCEPT_COND_TERNARY = ALL_NODES && options?.ternaryOperandBinaryExpressions === false;
const IGNORE_NESTED_BINARY = ALL_NODES && options?.nestedBinaryExpressions === false;
const EXCEPT_RETURN_ASSIGN = ALL_NODES && options?.returnAssign === false;
const IGNORE_JSX = ALL_NODES && options?.ignoreJSX;
const IGNORE_ARROW_CONDITIONALS = ALL_NODES && options?.enforceForArrowConditionals === false;
const IGNORE_SEQUENCE_EXPRESSIONS = ALL_NODES && options?.enforceForSequenceExpressions === false;
const IGNORE_NEW_IN_MEMBER_EXPR = ALL_NODES && options?.enforceForNewInMemberExpressions === false;
const IGNORE_FUNCTION_PROTOTYPE_METHODS = ALL_NODES && options?.enforceForFunctionPrototypeMethods === false;
const ALLOW_PARENS_AFTER_COMMENT_PATTERN = ALL_NODES && options?.allowParensAfterCommentPattern;
const ALLOW_NESTED_TERNARY = ALL_NODES && options?.nestedConditionalExpressions === false;
const ALLOW_NODES_IN_SPREAD = ALL_NODES && options && new Set(Object.entries(options.allowNodesInSpreadElement || {}).filter(([_, value]) => value).map(([key]) => key));
warnDeprecatedOptions(options, [
"enforceForArrowConditionals",
"enforceForNewInMemberExpressions",
"allowNodesInSpreadElement"
], "ignoredNodes", "no-extra-parens");
const PRECEDENCE_OF_ASSIGNMENT_EXPR = precedence({ type: "AssignmentExpression" });
const PRECEDENCE_OF_UPDATE_EXPR = precedence({ type: "UpdateExpression" });
let reportsBuffer;
function pathToAncestor(node, ancestor) {
const path = [node];
let currentNode = node;
while (currentNode !== ancestor) {
currentNode = currentNode.parent;
/* c8 ignore start */
if (currentNode === null || currentNode === void 0) throw new Error("Nodes are not in the ancestor-descendant relationship.");
/* c8 ignore stop */
path.push(currentNode);
}
return path;
}
function pathToDescendant(node, descendant) {
return pathToAncestor(descendant, node).reverse();
}
function isSafelyEnclosingInExpression(node, child) {
switch (node.type) {
case "ArrayExpression":
case "ArrayPattern":
case "BlockStatement":
case "ObjectExpression":
case "ObjectPattern":
case "TemplateLiteral": return true;
case "ArrowFunctionExpression":
case "FunctionExpression": return node.params.includes(child);
case "CallExpression":
case "NewExpression": return node.arguments.includes(child);
case "MemberExpression": return node.computed && node.property === child;
case "ConditionalExpression": return node.consequent === child;
default: return false;
}
}
function startNewReportsBuffering() {
reportsBuffer = {
upper: reportsBuffer,
inExpressionNodes: [],
reports: []
};
}
function endCurrentReportsBuffering() {
const { upper, inExpressionNodes, reports } = reportsBuffer ?? {};
if (upper) {
upper.inExpressionNodes.push(...inExpressionNodes ?? []);
upper.reports.push(...reports ?? []);
} else reports?.forEach(({ finishReport }) => finishReport());
reportsBuffer = upper;
}
function isInCurrentReportsBuffer(node) {
return reportsBuffer?.reports.some((r) => r.node === node);
}
function removeFromCurrentReportsBuffer(node) {
if (reportsBuffer) reportsBuffer.reports = reportsBuffer.reports.filter((r) => r.node !== node);
}
function isImmediateFunctionPrototypeMethodCall(node) {
const callNode = skipChainExpression(node);
if (callNode.type !== "CallExpression") return false;
const callee = skipChainExpression(callNode.callee);
return callee.type === "MemberExpression" && callee.object.type === "FunctionExpression" && ["call", "apply"].includes(getStaticPropertyName(callee));
}
function ruleApplies(node) {
if (node.type === "JSXElement" || node.type === "JSXFragment") switch (IGNORE_JSX) {
case "all": return false;
case "multi-line": return isSingleLine(node);
case "single-line": return !isSingleLine(node);
case "none": break;
}
if (node.type === "SequenceExpression" && IGNORE_SEQUENCE_EXPRESSIONS) return false;
if (isImmediateFunctionPrototypeMethodCall(node) && IGNORE_FUNCTION_PROTOTYPE_METHODS) return false;
return ALL_NODES || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
}
function isParenthesised(node) {
return (0, ast_exports.isParenthesized)(1, node, sourceCode);
}
function isParenthesisedTwice(node) {
return (0, ast_exports.isParenthesized)(2, node, sourceCode);
}
function hasExcessParens(node) {
return ruleApplies(node) && isParenthesised(node);
}
function hasDoubleExcessParens(node) {
return ruleApplies(node) && isParenthesisedTwice(node);
}
function hasExcessParensWithPrecedence(node, precedenceLowerLimit) {
if (ruleApplies(node) && isParenthesised(node)) {
if (precedence(node) >= precedenceLowerLimit || isParenthesisedTwice(node)) return true;
}
return false;
}
function isCondAssignException(node) {
return EXCEPT_COND_ASSIGN && node.test && node.test.type === "AssignmentExpression";
}
function isInReturnStatement(node) {
for (let currentNode = node; currentNode; currentNode = currentNode.parent) if (currentNode.type === "ReturnStatement" || currentNode.type === "ArrowFunctionExpression" && currentNode.body.type !== "BlockStatement") return true;
return false;
}
function isNewExpressionWithParens(newExpression) {
const lastToken = sourceCode.getLastToken(newExpression);
const penultimateToken = sourceCode.getTokenBefore(lastToken);
return newExpression.arguments.length > 0 || (0, ast_exports.isOpeningParenToken)(penultimateToken) && (0, ast_exports.isClosingParenToken)(lastToken) && newExpression.callee.range[1] < newExpression.range[1];
}
function isMemberExpInNewCallee(node) {
if (node.type === "MemberExpression") return node.parent.type === "NewExpression" && node.parent.callee === node ? true : "object" in node.parent && node.parent.object === node && isMemberExpInNewCallee(node.parent);
return false;
}
function doesMemberExpressionContainCallExpression(node) {
let currentNode = node.object;
let currentNodeType = node.object.type;
while (currentNodeType === "MemberExpression") {
if (!("object" in currentNode)) break;
currentNode = currentNode.object;
currentNodeType = currentNode.type;
}
return currentNodeType === "CallExpression";
}
function containsAssignment(node) {
if (node.type === "AssignmentExpression") return true;
if (node.type === "ConditionalExpression" && (node.consequent.type === "AssignmentExpression" || node.alternate.type === "AssignmentExpression")) return true;
if ("left" in node && (node.left && node.left.type === "AssignmentExpression" || node.right && node.right.type === "AssignmentExpression")) return true;
return false;
}
function isReturnAssignException(node) {
if (!EXCEPT_RETURN_ASSIGN || !isInReturnStatement(node)) return false;
if (node.type === "ReturnStatement") return node.argument && containsAssignment(node.argument);
if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") return containsAssignment(node.body);
return containsAssignment(node);
}
function hasExcessParensNoLineTerminator(token, node) {
if ((0, ast_exports.isTokenOnSameLine)(token, node)) return hasExcessParens(node);
return hasDoubleExcessParens(node);
}
function requiresLeadingSpace(node) {
const leftParenToken = sourceCode.getTokenBefore(node);
const tokenBeforeLeftParen = sourceCode.getTokenBefore(leftParenToken, { includeComments: true });
const tokenAfterLeftParen = sourceCode.getTokenAfter(leftParenToken, { includeComments: true });
return tokenBeforeLeftParen && tokenBeforeLeftParen.range[1] === leftParenToken.range[0] && leftParenToken.range[1] === tokenAfterLeftParen.range[0] && !canTokensBeAdjacent(tokenBeforeLeftParen, tokenAfterLeftParen);
}
function requiresTrailingSpace(node) {
const nextTwoTokens = sourceCode.getTokensAfter(node, { count: 2 });
const rightParenToken = nextTwoTokens[0];
const tokenAfterRightParen = nextTwoTokens[1];
const tokenBeforeRightParen = sourceCode.getLastToken(node);
return rightParenToken && tokenAfterRightParen && !sourceCode.isSpaceBetween(rightParenToken, tokenAfterRightParen) && !canTokensBeAdjacent(tokenBeforeRightParen, tokenAfterRightParen);
}
function isIIFE(node) {
const maybeCallNode = skipChainExpression(node);
return maybeCallNode.type === "CallExpression" && maybeCallNode.callee.type === "FunctionExpression";
}
function canBeAssignmentTarget(node) {
return !!(node && (node.type === "Identifier" || node.type === "MemberExpression"));
}
function isAnonymousFunctionAssignmentException({ left, operator, right }) {
if (left.type === "Identifier" && [
"=",
"&&=",
"||=",
"??="
].includes(operator)) {
const rhsType = right.type;
if (rhsType === "ArrowFunctionExpression") return true;
if ((rhsType === "FunctionExpression" || rhsType === "ClassExpression") && !right.id) return true;
}
return false;
}
function isFixable(node) {
if (node.type !== "Literal" || typeof node.value !== "string") return true;
if (isParenthesisedTwice(node)) return true;
return !isTopLevelExpressionStatement(node.parent);
}
function report(node) {
const leftParenToken = sourceCode.getTokenBefore(node);
const rightParenToken = sourceCode.getTokenAfter(node);
if (!isParenthesisedTwice(node)) {
if (tokensToIgnore.has(sourceCode.getFirstToken(node))) return;
if (isIIFE(node) && !("callee" in node && isParenthesised(node.callee))) return;
if (ALLOW_PARENS_AFTER_COMMENT_PATTERN) {
const commentsBeforeLeftParenToken = sourceCode.getCommentsBefore(leftParenToken);
const totalCommentsBeforeLeftParenTokenCount = commentsBeforeLeftParenToken.length;
const ignorePattern = new RegExp(ALLOW_PARENS_AFTER_COMMENT_PATTERN, "u");
if (totalCommentsBeforeLeftParenTokenCount > 0 && ignorePattern.test(commentsBeforeLeftParenToken[totalCommentsBeforeLeftParenTokenCount - 1].value)) return;
}
}
function finishReport() {
context.report({
node,
loc: leftParenToken.loc,
messageId: "unexpected",
fix: isFixable(node) ? (fixer) => {
const parenthesizedSource = sourceCode.text.slice(leftParenToken.range[1], rightParenToken.range[0]);
return fixer.replaceTextRange([leftParenToken.range[0], rightParenToken.range[1]], (requiresLeadingSpace(node) ? " " : "") + parenthesizedSource + (requiresTrailingSpace(node) ? " " : ""));
} : null
});
}
if (reportsBuffer) {
reportsBuffer.reports.push({
node,
finishReport
});
return;
}
finishReport();
}
function checkArgumentWithPrecedence(node) {
if ("argument" in node && node.argument && hasExcessParensWithPrecedence(node.argument, precedence(node))) report(node.argument);
}
function checkBinaryLogical(node) {
const isLeftTypeAssertion = isTypeAssertion(node.left);
const isRightTypeAssertion = isTypeAssertion(node.right);
if (isLeftTypeAssertion && isRightTypeAssertion) return;
const rule = (n) => {
const prec = precedence(n);
const leftPrecedence = precedence(n.left);
const rightPrecedence = precedence(n.right);
const isExponentiation = n.operator === "**";
const shouldSkipLeft = IGNORE_NESTED_BINARY && (n.left.type === "BinaryExpression" || n.left.type === "LogicalExpression");
const shouldSkipRight = IGNORE_NESTED_BINARY && (n.right.type === "BinaryExpression" || n.right.type === "LogicalExpression");
if (!shouldSkipLeft && hasExcessParens(n.left)) {
if (!(["AwaitExpression", "UnaryExpression"].includes(n.left.type) && isExponentiation) && !isMixedLogicalAndCoalesceExpressions(n.left, n) && !(n.parent.type === "ReturnStatement" && n.parent.loc.start.line !== n.left.loc.start.line && !isParenthesised(n)) && (leftPrecedence > prec || leftPrecedence === prec && !isExponentiation) || isParenthesisedTwice(n.left)) report(n.left);
}
if (!shouldSkipRight && hasExcessParens(n.right)) {
if (!isMixedLogicalAndCoalesceExpressions(n.right, n) && (rightPrecedence > prec || rightPrecedence === prec && isExponentiation) || isParenthesisedTwice(n.right)) report(n.right);
}
};
if (isLeftTypeAssertion) return rule({
...node,
left: {
...node.left,
type: AST_NODE_TYPES.SequenceExpression
}
});
if (isRightTypeAssertion) return rule({
...node,
right: {
...node.right,
type: AST_NODE_TYPES.SequenceExpression
}
});
return rule(node);
}
function checkCallNew(node) {
const rule = (node$1) => {
const callee = node$1.callee;
if (hasExcessParensWithPrecedence(callee, precedence(node$1))) {
if (hasDoubleExcessParens(callee) || !(isIIFE(node$1) || callee.type === "NewExpression" && !isNewExpressionWithParens(callee) && !(node$1.type === "NewExpression" && !isNewExpressionWithParens(node$1)) || node$1.type === "NewExpression" && callee.type === "MemberExpression" && doesMemberExpressionContainCallExpression(callee) || (!("optional" in node$1) || !node$1.optional) && callee.type === "ChainExpression")) report(node$1.callee);
}
node$1.arguments.forEach((arg) => {
if (hasExcessParensWithPrecedence(arg, PRECEDENCE_OF_ASSIGNMENT_EXPR)) report(arg);
});
};
if (isTypeAssertion(node.callee)) return rule({
...node,
callee: {
...node.callee,
type: AST_NODE_TYPES.SequenceExpression
}
});
if (node.typeArguments && node.arguments.length === 1 && sourceCode.getTokenAfter(node.callee, ast_exports.isOpeningParenToken) !== sourceCode.getTokenBefore(node.arguments[0], ast_exports.isOpeningParenToken)) return rule({
...node,
arguments: [{
...node.arguments[0],
type: AST_NODE_TYPES.SequenceExpression
}]
});
return rule(node);
}
function checkClass(node) {
if (!node.superClass) return;
const hasExtraParens = precedence(node.superClass) > PRECEDENCE_OF_UPDATE_EXPR ? hasExcessParens(node.superClass) : hasDoubleExcessParens(node.superClass);
if (hasExtraParens) report(node.superClass);
}
function checkExpressionOrExportStatement(node) {
const firstToken = isParenthesised(node) ? sourceCode.getTokenBefore(node) : sourceCode.getFirstToken(node);
const secondToken = sourceCode.getTokenAfter(firstToken, ast_exports.isNotOpeningParenToken);
const thirdToken = secondToken ? sourceCode.getTokenAfter(secondToken) : null;
const tokenAfterClosingParens = secondToken ? sourceCode.getTokenAfter(secondToken, ast_exports.isNotClosingParenToken) : null;
if ((0, ast_exports.isOpeningParenToken)(firstToken) && ((0, ast_exports.isOpeningBraceToken)(secondToken) || isKeywordToken(secondToken) && (secondToken.value === "function" || secondToken.value === "class" || secondToken.value === "let" && tokenAfterClosingParens && ((0, ast_exports.isOpeningBracketToken)(tokenAfterClosingParens) || tokenAfterClosingParens.type === "Identifier")) || secondToken && secondToken.type === "Identifier" && secondToken.value === "async" && isKeywordToken(thirdToken) && thirdToken.value === "function")) tokensToIgnore.add(secondToken);
const hasExtraParens = node.parent.type === "ExportDefaultDeclaration" ? hasExcessParensWithPrecedence(node, PRECEDENCE_OF_ASSIGNMENT_EXPR) : hasExcessParens(node);
if (hasExtraParens) report(node);
}
function checkUnaryUpdate(node) {
if (isTypeAssertion(node.argument)) return checkArgumentWithPrecedence({
...node,
argument: {
...node.argument,
type: AST_NODE_TYPES.SequenceExpression
}
});
return checkArgumentWithPrecedence(node);
}
function checkClassProperty(node) {
if (node.computed && hasExcessParensWithPrecedence(node.key, PRECEDENCE_OF_ASSIGNMENT_EXPR)) report(node.key);
if (node.value && hasExcessParensWithPrecedence(node.value, PRECEDENCE_OF_ASSIGNMENT_EXPR)) report(node.value);
}
function checkTSBinaryType(node) {
node.types.forEach((type) => {
const shouldReport = IGNORE_NESTED_BINARY && (0, ast_exports.isNodeOfTypes)([AST_NODE_TYPES.TSUnionType, AST_NODE_TYPES.TSIntersectionType])(type) ? isParenthesisedTwice(type) : hasExcessParensWithPrecedence(type, precedence(node));
if (shouldReport) report(type);
});
}
const baseListeners = {
ArrayExpression(node) {
node.elements.map((element) => isTypeAssertion(element) ? {
...element,
type: AST_NODE_TYPES.FunctionExpression
} : element).forEach((ele) => {
if (!!ele && hasExcessParensWithPrecedence(ele, PRECEDENCE_OF_ASSIGNMENT_EXPR)) report(ele);
});
},
ArrayPattern(node) {
node.elements.forEach((ele) => {
if (!!ele && canBeAssignmentTarget(ele) && hasExcessParens(ele)) report(ele);
});
},
ArrowFunctionExpression(node) {
if (isTypeAssertion(node.body)) return;
if (isReturnAssignException(node)) return;
if (node.body.type === "ConditionalExpression" && IGNORE_ARROW_CONDITIONALS) return;
if (node.body.type === "BlockStatement") return;
const firstBodyToken = sourceCode.getFirstToken(node.body, ast_exports.isNotOpeningParenToken);
const tokenBeforeFirst = sourceCode.getTokenBefore(firstBodyToken);
if ((0, ast_exports.isOpeningParenToken)(tokenBeforeFirst) && (0, ast_exports.isOpeningBraceToken)(firstBodyToken)) tokensToIgnore.add(firstBodyToken);
if (hasExcessParensWithPrecedence(node.body, PRECEDENCE_OF_ASSIGNMENT_EXPR)) report(node.body);
},
AssignmentExpression(node) {
if (canBeAssignmentTarget(node.left) && hasExcessParens(node.left) && (!isAnonymousFunctionAssignmentException(node) || isParenthesisedTwice(node.left))) report(node.left);
if (!isReturnAssignException(node) && hasExcessParensWithPrecedence(node.right, precedence(node))) report(node.right);
},
AssignmentPattern(node) {
const { left, right } = node;
if (canBeAssignmentTarget(left) && hasExcessParens(left)) report(left);
if (right && hasExcessParensWithPrecedence(right, PRECEDENCE_OF_ASSIGNMENT_EXPR)) report(right);
},
AwaitExpression(node) {
if (isTypeAssertion(node.argument)) return checkArgumentWithPrecedence({
...node,
argument: {
...node.argument,
type: AST_NODE_TYPES.SequenceExpression
}
});
return checkArgumentWithPrecedence(node);
},
BinaryExpression(node) {
if (reportsBuffer && node.operator === "in") reportsBuffer.inExpressionNodes.push(node);
checkBinaryLogical(node);
},
"CallExpression": checkCallNew,
ClassDeclaration(node) {
if (node.superClass?.type === AST_NODE_TYPES.TSAsExpression) return checkClass({
...node,
superClass: {
...node.superClass,
type: AST_NODE_TYPES.SequenceExpression
}
});
return checkClass(node);
},
ClassExpression(node) {
if (node.superClass?.type === AST_NODE_TYPES.TSAsExpression) return checkClass({
...node,
superClass: {
...node.superClass,
type: AST_NODE_TYPES.SequenceExpression
}
});
return checkClass(node);
},
ConditionalExpression(node) {
const rule = (node$1) => {
if (isReturnAssignException(node$1)) return;
const availableTypes = new Set(["BinaryExpression", "LogicalExpression"]);
if (!(EXCEPT_COND_TERNARY && availableTypes.has(node$1.test.type)) && !(ALLOW_NESTED_TERNARY && ["ConditionalExpression"].includes(node$1.test.type)) && !isCondAssignException(node$1) && hasExcessParensWithPrecedence(node$1.test, precedence({
type: "LogicalExpression",
operator: "||"
}))) report(node$1.test);
if (!(EXCEPT_COND_TERNARY && availableTypes.has(node$1.consequent.type)) && !(ALLOW_NESTED_TERNARY && ["ConditionalExpression"].includes(node$1.consequent.type)) && hasExcessParensWithPrecedence(node$1.consequent, PRECEDENCE_OF_ASSIGNMENT_EXPR)) report(node$1.consequent);
if (!(EXCEPT_COND_TERNARY && availableTypes.has(node$1.alternate.type)) && !(ALLOW_NESTED_TERNARY && ["ConditionalExpression"].includes(node$1.alternate.type)) && hasExcessParensWithPrecedence(node$1.alternate, PRECEDENCE_OF_ASSIGNMENT_EXPR)) report(node$1.alternate);
};
if (isTypeAssertion(node.test)) return rule({
...node,
test: {
...node.test,
type: AST_NODE_TYPES.SequenceExpression
}
});
if (isTypeAssertion(node.consequent)) return rule({
...node,
consequent: {
...node.consequent,
type: AST_NODE_TYPES.SequenceExpression
}
});
if (isTypeAssertion(node.alternate)) return rule({
...node,
alternate: {
...node.alternate,
type: AST_NODE_TYPES.SequenceExpression
}
});
return rule(node);
},
DoWhileStatement(node) {
if (hasExcessParens(node.test) && !isCondAssignException(node)) report(node.test);
},
ExportDefaultDeclaration(node) {
checkExpressionOrExportStatement(node.declaration);
},
ExpressionStatement(node) {
checkExpressionOrExportStatement(node.expression);
},
ForInStatement(node) {
if (isTypeAssertion(node.right)) return;
if (node.left.type !== "VariableDeclaration") {
const firstLeftToken = sourceCode.getFirstToken(node.left, ast_exports.isNotOpeningParenToken);
if (firstLeftToken.value === "let" && (0, ast_exports.isOpeningBracketToken)(sourceCode.getTokenAfter(firstLeftToken, ast_exports.isNotClosingParenToken))) tokensToIgnore.add(firstLeftToken);
}
if (hasExcessParens(node.left)) report(node.left);
if (hasExcessParens(node.right)) report(node.right);
},
ForOfStatement(node) {
if (node.left.type !== "VariableDeclaration") {
const firstLeftToken = sourceCode.getFirstToken(node.left, ast_exports.isNotOpeningParenToken);
if (firstLeftToken.value === "let") tokensToIgnore.add(firstLeftToken);
}
if (hasExcessParens(node.left)) report(node.left);
if (!isTypeAssertion(node.right) && hasExcessParensWithPrecedence(node.right, PRECEDENCE_OF_ASSIGNMENT_EXPR)) report(node.right);
},
ForStatement(node) {
if (node.test && hasExcessParens(node.test) && !isCondAssignException(node) && !isTypeAssertion(node.test)) report(node.test);
if (node.update && hasExcessParens(node.update) && !isTypeAssertion(node.update)) report(node.update);
if (node.init && !isTypeAssertion(node.init)) {
if (node.init.type !== "VariableDeclaration") {
const firstToken = sourceCode.getFirstToken(node.init, ast_exports.isNotOpeningParenToken);
if (firstToken.value === "let" && (0, ast_exports.isOpeningBracketToken)(sourceCode.getTokenAfter(firstToken, ast_exports.isNotClosingParenToken))) tokensToIgnore.add(firstToken);
}
startNewReportsBuffering();
if (hasExcessParens(node.init)) report(node.init);
}
},
"ForStatement > *.init:exit": function(node) {
if (isTypeAssertion(node)) return;
if (reportsBuffer?.reports.length) reportsBuffer.inExpressionNodes.forEach((inExpressionNode) => {
const path = pathToDescendant(node, inExpressionNode);
let nodeToExclude = null;
for (let i = 0; i < path.length; i++) {
const pathNode = path[i];
if (i < path.length - 1) {
const nextPathNode = path[i + 1];
if (isSafelyEnclosingInExpression(pathNode, nextPathNode)) return;
}
if (isParenthesised(pathNode)) if (isInCurrentReportsBuffer(pathNode)) {
if (isParenthesisedTwice(pathNode)) return;
if (!nodeToExclude) nodeToExclude = pathNode;
} else return;
}
if (nodeToExclude) removeFromCurrentReportsBuffer(nodeToExclude);
});
endCurrentReportsBuffering();
},
IfStatement(node) {
if (hasExcessParens(node.test) && !isCondAssignException(node)) report(node.test);
},
ImportExpression(node) {
const { source } = node;
if (source.type === "SequenceExpression") {
if (hasDoubleExcessParens(source)) report(source);
} else if (hasExcessParens(source)) report(source);
},
"LogicalExpression": checkBinaryLogical,
MemberExpression(node) {
const rule = (node$1) => {
const shouldAllowWrapOnce = isMemberExpInNewCallee(node$1) && doesMemberExpressionContainCallExpression(node$1);
const nodeObjHasExcessParens = shouldAllowWrapOnce ? hasDoubleExcessParens(node$1.object) : hasExcessParens(node$1.object) && !(isImmediateFunctionPrototypeMethodCall(node$1.parent) && "callee" in node$1.parent && node$1.parent.callee === node$1 && IGNORE_FUNCTION_PROTOTYPE_METHODS);
if (nodeObjHasExcessParens && precedence(node$1.object) >= precedence(node$1) && (node$1.computed || !(isDecimalInteger(node$1.object) || isRegExpLiteral(node$1.object)))) report(node$1.object);
if (nodeObjHasExcessParens && node$1.object.type === "CallExpression") report(node$1.object);
if (nodeObjHasExcessParens && !IGNORE_NEW_IN_MEMBER_EXPR && node$1.object.type === "NewExpression" && isNewExpressionWithParens(node$1.object)) report(node$1.object);
if (nodeObjHasExcessParens && node$1.optional && node$1.object.type === "ChainExpression") report(node$1.object);
if (node$1.computed && hasExcessParens(node$1.property)) report(node$1.property);
};
if (isTypeAssertion(node.object)) return rule({
...node,
object: {
...node.object,
type: AST_NODE_TYPES.SequenceExpression
}
});
if (isTypeAssertion(node.property)) return rule({
...node,
property: {
...node.property,
type: AST_NODE_TYPES.FunctionExpression
}
});
return rule(node);
},
MethodDefinition(node) {
if (!node.computed) return;
if (hasExcessParensWithPrecedence(node.key, PRECEDENCE_OF_ASSIGNMENT_EXPR)) report(node.key);
},
"NewExpression": checkCallNew,
ObjectExpression(node) {
node.properties.forEach((property) => {
if (property.type === "Property" && property.value && hasExcessParensWithPrecedence(property.value, PRECEDENCE_OF_ASSIGNMENT_EXPR)) report(property.value);
});
},
ObjectPattern(node) {
node.properties.forEach(({ value }) => {
if (value && canBeAssignmentTarget(value) && hasExcessParens(value)) report(value);
});
},
Property(node) {
if (node.computed) {
const { key } = node;
if (key && hasExcessParensWithPrecedence(key, PRECEDENCE_OF_ASSIGNMENT_EXPR)) report(key);
}
},
"PropertyDefinition": checkClassProperty,
"AccessorProperty": checkClassProperty,
RestElement(node) {
const argument = node.argument;
if (canBeAssignmentTarget(argument) && hasExcessParens(argument)) report(argument);
},
ReturnStatement(node) {
const returnToken = sourceCode.getFirstToken(node);
if (isReturnAssignException(node)) return;
if (node.argument && returnToken && hasExcessParensNoLineTerminator(returnToken, node.argument) && !isRegExpLiteral(node.argument)) report(node.argument);
},
SequenceExpression(node) {
const precedenceOfNode = precedence(node);
node.expressions.forEach((expression) => {
if (hasExcessParensWithPrecedence(expression, precedenceOfNode)) report(expression);
});
},
SpreadElement(node) {
if (isTypeAssertion(node.argument)) return;
if (ALLOW_NODES_IN_SPREAD && ALLOW_NODES_IN_SPREAD.has(node.argument.type)) return;
if (!hasExcessParensWithPrecedence(node.argument, PRECEDENCE_OF_ASSIGNMENT_EXPR)) return;
report(node.argument);
},
SwitchCase(node) {
if (node.test && !isTypeAssertion(node.test) && hasExcessParens(node.test)) report(node.test);
},
SwitchStatement(node) {
if (hasExcessParens(node.discriminant)) report(node.discriminant);
},
TemplateLiteral(node) {
node.expressions.forEach((expression) => {
if (hasExcessParens(expression)) report(expression);
});
},
ThrowStatement(node) {
if (!node.argument || isTypeAssertion(node.argument)) return;
const throwToken = sourceCode.getFirstToken(node);
if (!throwToken) return;
if (hasExcessParensNoLineTerminator(throwToken, node.argument)) report(node.argument);
},
"UnaryExpression": checkUnaryUpdate,
UpdateExpression(node) {
if (isTypeAssertion(node.argument)) return checkUnaryUpdate(node);
if (node.prefix) checkArgumentWithPrecedence(node);
else {
const { argument } = node;
const operatorToken = sourceCode.getLastToken(node);
if ((0, ast_exports.isTokenOnSameLine)(argument, operatorToken)) checkArgumentWithPrecedence(node);
else if (hasDoubleExcessParens(argument)) report(argument);
}
},
VariableDeclarator(node) {
const rule = (node$1) => {
if (node$1.init && hasExcessParensWithPrecedence(node$1.init, PRECEDENCE_OF_ASSIGNMENT_EXPR) && !isRegExpLiteral(node$1.init)) report(node$1.init);
};
if (isTypeAssertion(node.init)) return rule({
...node,
type: AST_NODE_TYPES.VariableDeclarator,
init: {
...node.init,
type: AST_NODE_TYPES.FunctionExpression
}
});
return rule(node);
},
WhileStatement(node) {
if (hasExcessParens(node.test) && !isCondAssignException(node)) report(node.test);
},
WithStatement(node) {
if (hasExcessParens(node.object)) report(node.object);
},
YieldExpression(node) {
if (!node.argument || isTypeAssertion(node.argument)) return;
const yieldToken = sourceCode.getFirstToken(node);
if (precedence(node.argument) >= precedence(node) && yieldToken && hasExcessParensNoLineTerminator(yieldToken, node.argument) || hasDoubleExcessParens(node.argument)) report(node.argument);
},
TSArrayType(node) {
if (hasExcessParensWithPrecedence(node.elementType, precedence(node))) report(node.elementType);
},
"TSIntersectionType": checkTSBinaryType,
"TSUnionType": checkTSBinaryType,
TSTypeAnnotation(node) {
if (hasExcessParens(node.typeAnnotation)) report(node.typeAnnotation);
},
TSTypeAliasDeclaration(node) {
if (hasExcessParens(node.typeAnnotation)) report(node.typeAnnotation);
},
TSEnumMember(node) {
if (!node.initializer) return;
if (hasExcessParens(node.initializer)) report(node.initializer);
}
};
const listeners = {};
const ignoreNodes = /* @__PURE__ */ new Set();
const listenerCallQueue = [];
for (const key in baseListeners) listeners[key] = (node) => listenerCallQueue.push({
node,
listener: baseListeners[key]
});
return {
...listeners,
...options?.ignoredNodes?.reduce((listener, selector) => Object.assign(listener, { [selector]: (node) => ignoreNodes.add(node) }), {}),
"Program:exit": function() {
for (let i = 0; i < listenerCallQueue.length; i++) {
const { node, listener } = listenerCallQueue[i];
if (!ignoreNodes.has(node)) listener(node);
}
}
};
}
});
export { no_extra_parens_default };

View File

@@ -0,0 +1,69 @@
import { FixTracker, ast_exports, createRule, isTopLevelExpressionStatement } from "../utils.js";
var no_extra_semi_default = createRule({
name: "no-extra-semi",
meta: {
type: "layout",
docs: { description: "Disallow unnecessary semicolons" },
fixable: "code",
schema: [],
messages: { unexpected: "Unnecessary semicolon." }
},
defaultOptions: [],
create(context) {
const sourceCode = context.sourceCode;
function isFixable(nodeOrToken) {
const nextToken = sourceCode.getTokenAfter(nodeOrToken);
if (!nextToken || nextToken.type !== "String") return true;
const stringNode = sourceCode.getNodeByRangeIndex(nextToken.range[0]);
return !isTopLevelExpressionStatement(stringNode.parent);
}
function report(nodeOrToken) {
context.report({
node: nodeOrToken,
messageId: "unexpected",
fix: isFixable(nodeOrToken) ? (fixer) => new FixTracker(fixer, context.sourceCode).retainSurroundingTokens(nodeOrToken).remove(nodeOrToken) : null
});
}
function checkForPartOfClassBody(firstToken) {
for (let token = firstToken; token.type === "Punctuator" && !(0, ast_exports.isClosingBraceToken)(token); token = sourceCode.getTokenAfter(token)) if ((0, ast_exports.isSemicolonToken)(token)) report(token);
}
return {
EmptyStatement(node) {
const parent = node.parent;
const allowedParentTypes = [
"ForStatement",
"ForInStatement",
"ForOfStatement",
"WhileStatement",
"DoWhileStatement",
"IfStatement",
"LabeledStatement",
"WithStatement"
];
if (!allowedParentTypes.includes(parent.type)) report(node);
},
ClassBody(node) {
checkForPartOfClassBody(sourceCode.getFirstToken(node, 1));
},
MethodDefinition(node) {
checkForPartOfClassBody(sourceCode.getTokenAfter(node));
},
PropertyDefinition(node) {
checkForPartOfClassBody(sourceCode.getTokenAfter(node));
},
AccessorProperty(node) {
checkForPartOfClassBody(sourceCode.getTokenAfter(node));
},
StaticBlock(node) {
checkForPartOfClassBody(sourceCode.getTokenAfter(node));
},
TSAbstractMethodDefinition(node) {
checkForPartOfClassBody(sourceCode.getTokenAfter(node));
},
TSAbstractPropertyDefinition(node) {
checkForPartOfClassBody(sourceCode.getTokenAfter(node));
}
};
}
});
export { no_extra_semi_default };

View File

@@ -0,0 +1,36 @@
import { canTokensBeAdjacent, createRule } from "../utils.js";
var no_floating_decimal_default = createRule({
name: "no-floating-decimal",
meta: {
type: "layout",
docs: { description: "Disallow leading or trailing decimal points in numeric literals" },
schema: [],
fixable: "code",
messages: {
leading: "A leading decimal point can be confused with a dot.",
trailing: "A trailing decimal point can be confused with a dot."
}
},
create(context) {
const sourceCode = context.sourceCode;
return { Literal(node) {
if (typeof node.value === "number") {
if (node.raw.startsWith(".")) context.report({
node,
messageId: "leading",
fix(fixer) {
const tokenBefore = sourceCode.getTokenBefore(node);
const needsSpaceBefore = tokenBefore && tokenBefore.range[1] === node.range[0] && !canTokensBeAdjacent(tokenBefore, `0${node.raw}`);
return fixer.insertTextBefore(node, needsSpaceBefore ? " 0" : "0");
}
});
if (node.raw.indexOf(".") === node.raw.length - 1) context.report({
node,
messageId: "trailing",
fix: (fixer) => fixer.insertTextAfter(node, "0")
});
}
} };
}
});
export { no_floating_decimal_default };

View File

@@ -0,0 +1,131 @@
import { ast_exports, createRule, getPrecedence, isParenthesised } from "../utils.js";
const ARITHMETIC_OPERATORS = [
"+",
"-",
"*",
"/",
"%",
"**"
];
const BITWISE_OPERATORS = [
"&",
"|",
"^",
"~",
"<<",
">>",
">>>"
];
const COMPARISON_OPERATORS = [
"==",
"!=",
"===",
"!==",
">",
">=",
"<",
"<="
];
const LOGICAL_OPERATORS = ["&&", "||"];
const RELATIONAL_OPERATORS = ["in", "instanceof"];
const TERNARY_OPERATOR = ["?:"];
const COALESCE_OPERATOR = ["??"];
const ALL_OPERATORS = [].concat(ARITHMETIC_OPERATORS, BITWISE_OPERATORS, COMPARISON_OPERATORS, LOGICAL_OPERATORS, RELATIONAL_OPERATORS, TERNARY_OPERATOR, COALESCE_OPERATOR);
const DEFAULT_GROUPS = [
ARITHMETIC_OPERATORS,
BITWISE_OPERATORS,
COMPARISON_OPERATORS,
LOGICAL_OPERATORS,
RELATIONAL_OPERATORS
];
const TARGET_NODE_TYPE = /^(?:Binary|Logical|Conditional)Expression$/u;
function normalizeOptions(options = {}) {
const hasGroups = options.groups && options.groups.length > 0;
const groups = hasGroups ? options.groups : DEFAULT_GROUPS;
const allowSamePrecedence = options.allowSamePrecedence !== false;
return {
groups,
allowSamePrecedence
};
}
function includesBothInAGroup(groups, left, right) {
return groups.some((group) => group.includes(left) && group.includes(right));
}
function getChildNode(node) {
return node.type === "ConditionalExpression" ? node.test : node.left;
}
var no_mixed_operators_default = createRule({
name: "no-mixed-operators",
meta: {
type: "layout",
docs: { description: "Disallow mixed binary operators" },
schema: [{
type: "object",
properties: {
groups: {
type: "array",
items: {
type: "array",
items: {
type: "string",
enum: ALL_OPERATORS
},
minItems: 2,
uniqueItems: true
},
uniqueItems: true
},
allowSamePrecedence: {
type: "boolean",
default: true
}
},
additionalProperties: false
}],
messages: { unexpectedMixedOperator: "Unexpected mix of '{{leftOperator}}' and '{{rightOperator}}'. Use parentheses to clarify the intended order of operations." }
},
create(context) {
const sourceCode = context.sourceCode;
const options = normalizeOptions(context.options[0]);
function shouldIgnore(node) {
const a = node;
const b = node.parent;
return !includesBothInAGroup(options.groups ?? [], a.operator, b.type === "ConditionalExpression" ? "?:" : b.operator) || options.allowSamePrecedence && getPrecedence(a) === getPrecedence(b);
}
function isMixedWithParent(node) {
return node.operator !== node.parent.operator && !isParenthesised(sourceCode, node);
}
function getOperatorToken(node) {
return sourceCode.getTokenAfter(getChildNode(node), ast_exports.isNotClosingParenToken);
}
function reportBothOperators(node) {
const parent = node.parent;
const left = getChildNode(parent) === node ? node : parent;
const right = getChildNode(parent) !== node ? node : parent;
const data = {
leftOperator: left.operator || "?:",
rightOperator: right.operator || "?:"
};
context.report({
node: left,
loc: getOperatorToken(left).loc,
messageId: "unexpectedMixedOperator",
data
});
context.report({
node: right,
loc: getOperatorToken(right).loc,
messageId: "unexpectedMixedOperator",
data
});
}
function check(node) {
if (TARGET_NODE_TYPE.test(node.parent.type) && isMixedWithParent(node) && !shouldIgnore(node)) reportBothOperators(node);
}
return {
BinaryExpression: check,
LogicalExpression: check
};
}
});
export { no_mixed_operators_default };

View File

@@ -0,0 +1,59 @@
import { createRule } from "../utils.js";
var no_mixed_spaces_and_tabs_default = createRule({
name: "no-mixed-spaces-and-tabs",
meta: {
type: "layout",
docs: { description: "Disallow mixed spaces and tabs for indentation" },
schema: [{ oneOf: [{
type: "string",
enum: ["smart-tabs"]
}, { type: "boolean" }] }],
messages: { mixedSpacesAndTabs: "Mixed spaces and tabs." }
},
create(context) {
const sourceCode = context.sourceCode;
let smartTabs;
switch (context.options[0]) {
case true:
case "smart-tabs":
smartTabs = true;
break;
default: smartTabs = false;
}
return { "Program:exit": function(node) {
const lines = sourceCode.lines;
const comments = sourceCode.getAllComments();
const ignoredCommentLines = /* @__PURE__ */ new Set();
comments.forEach((comment) => {
for (let i = comment.loc.start.line + 1; i <= comment.loc.end.line; i++) ignoredCommentLines.add(i);
});
let regex = /^(?=( +|\t+))\1(?:\t| )/u;
if (smartTabs) regex = /^(?=(\t*))\1(?=( +))\2\t/u;
lines.forEach((line, i) => {
const match = regex.exec(line);
if (match) {
const lineNumber = i + 1;
const loc = {
start: {
line: lineNumber,
column: match[0].length - 2
},
end: {
line: lineNumber,
column: match[0].length
}
};
if (!ignoredCommentLines.has(lineNumber)) {
const containingNode = sourceCode.getNodeByRangeIndex(sourceCode.getIndexFromLoc(loc.start));
if (!(containingNode && ["Literal", "TemplateElement"].includes(containingNode.type))) context.report({
node,
loc,
messageId: "mixedSpacesAndTabs"
});
}
}
});
} };
}
});
export { no_mixed_spaces_and_tabs_default };

View File

@@ -0,0 +1,73 @@
import { ast_exports, createRule } from "../utils.js";
var no_multi_spaces_default = createRule({
name: "no-multi-spaces",
meta: {
type: "layout",
docs: { description: "Disallow multiple spaces" },
fixable: "whitespace",
schema: [{
type: "object",
properties: {
exceptions: {
type: "object",
patternProperties: { "^([A-Z][a-z]*)+$": { type: "boolean" } },
additionalProperties: false
},
ignoreEOLComments: {
type: "boolean",
default: false
},
includeTabs: {
type: "boolean",
default: true
}
},
additionalProperties: false
}],
messages: { multipleSpaces: "Multiple spaces found before '{{displayValue}}'." }
},
create(context) {
const sourceCode = context.sourceCode;
const options = context.options[0] || {};
const ignoreEOLComments = options.ignoreEOLComments;
const exceptions = Object.assign({
Property: true,
ImportAttribute: true
}, options.exceptions);
const hasExceptions = Object.keys(exceptions).some((key) => exceptions[key]);
const spacesRe = options.includeTabs === false ? / {2}/ : /[ \t]{2}/;
function formatReportedCommentValue(token) {
const valueLines = token.value.split("\n");
const value = valueLines[0];
const formattedValue = `${value.slice(0, 12)}...`;
return valueLines.length === 1 && value.length <= 12 ? value : formattedValue;
}
return { Program() {
sourceCode.tokensAndComments.forEach((leftToken, leftIndex, tokensAndComments) => {
if (leftIndex === tokensAndComments.length - 1) return;
const rightToken = tokensAndComments[leftIndex + 1];
if (!spacesRe.test(sourceCode.text.slice(leftToken.range[1], rightToken.range[0])) || leftToken.loc.end.line < rightToken.loc.start.line) return;
if (ignoreEOLComments && (0, ast_exports.isCommentToken)(rightToken) && (leftIndex === tokensAndComments.length - 2 || rightToken.loc.end.line < tokensAndComments[leftIndex + 2].loc.start.line)) return;
if (hasExceptions) {
const parentNode = sourceCode.getNodeByRangeIndex(rightToken.range[0] - 1);
if (parentNode && exceptions[parentNode.type]) return;
}
let displayValue;
if (rightToken.type === "Block") displayValue = `/*${formatReportedCommentValue(rightToken)}*/`;
else if (rightToken.type === "Line") displayValue = `//${formatReportedCommentValue(rightToken)}`;
else displayValue = rightToken.value;
context.report({
node: rightToken,
loc: {
start: leftToken.loc.end,
end: rightToken.loc.start
},
messageId: "multipleSpaces",
data: { displayValue },
fix: (fixer) => fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], " ")
});
});
} };
}
});
export { no_multi_spaces_default };

View File

@@ -0,0 +1,103 @@
import { createRule } from "../utils.js";
var no_multiple_empty_lines_default = createRule({
name: "no-multiple-empty-lines",
meta: {
type: "layout",
docs: { description: "Disallow multiple empty lines" },
fixable: "whitespace",
schema: [{
type: "object",
properties: {
max: {
type: "integer",
minimum: 0
},
maxEOF: {
type: "integer",
minimum: 0
},
maxBOF: {
type: "integer",
minimum: 0
}
},
required: ["max"],
additionalProperties: false
}],
messages: {
blankBeginningOfFile: "Too many blank lines at the beginning of file. Max of {{max}} allowed.",
blankEndOfFile: "Too many blank lines at the end of file. Max of {{max}} allowed.",
consecutiveBlank: "More than {{max}} blank {{pluralizedLines}} not allowed."
}
},
create(context) {
let max = 2;
let maxEOF = max;
let maxBOF = max;
if (context.options.length && context.options[0]) {
max = context.options[0].max;
maxEOF = typeof context.options[0].maxEOF !== "undefined" ? context.options[0].maxEOF : max;
maxBOF = typeof context.options[0].maxBOF !== "undefined" ? context.options[0].maxBOF : max;
}
const sourceCode = context.sourceCode;
const allLines = sourceCode.lines[sourceCode.lines.length - 1] === "" ? sourceCode.lines.slice(0, -1) : sourceCode.lines;
const templateLiteralLines = /* @__PURE__ */ new Set();
return {
TemplateLiteral(node) {
node.quasis.forEach((literalPart) => {
for (let ignoredLine = literalPart.loc.start.line; ignoredLine < literalPart.loc.end.line; ignoredLine++) templateLiteralLines.add(ignoredLine);
});
},
"Program:exit": function(node) {
return allLines.reduce((nonEmptyLineNumbers, line, index) => {
if (line.trim() || templateLiteralLines.has(index + 1)) nonEmptyLineNumbers.push(index + 1);
return nonEmptyLineNumbers;
}, []).concat(allLines.length + 1).reduce((lastLineNumber, lineNumber) => {
let messageId, maxAllowed;
if (lastLineNumber === 0) {
messageId = "blankBeginningOfFile";
maxAllowed = maxBOF;
} else if (lineNumber === allLines.length + 1) {
messageId = "blankEndOfFile";
maxAllowed = maxEOF;
} else {
messageId = "consecutiveBlank";
maxAllowed = max;
}
if (lineNumber - lastLineNumber - 1 > maxAllowed) context.report({
node,
loc: {
start: {
line: lastLineNumber + maxAllowed + 1,
column: 0
},
end: {
line: lineNumber,
column: 0
}
},
messageId,
data: {
max: maxAllowed,
pluralizedLines: maxAllowed === 1 ? "line" : "lines"
},
fix(fixer) {
const rangeStart = sourceCode.getIndexFromLoc({
line: lastLineNumber + 1,
column: 0
});
const lineNumberAfterRemovedLines = lineNumber - maxAllowed;
const rangeEnd = lineNumberAfterRemovedLines <= allLines.length ? sourceCode.getIndexFromLoc({
line: lineNumberAfterRemovedLines,
column: 0
}) : sourceCode.text.length;
return fixer.removeRange([rangeStart, rangeEnd]);
}
});
return lineNumber;
}, 0);
}
};
}
});
export { no_multiple_empty_lines_default };

View File

@@ -0,0 +1,46 @@
import { createRule } from "../utils.js";
const tabRegex = /\t+/gu;
const anyNonWhitespaceRegex = /\S/u;
var no_tabs_default = createRule({
name: "no-tabs",
meta: {
type: "layout",
docs: { description: "Disallow all tabs" },
schema: [{
type: "object",
properties: { allowIndentationTabs: {
type: "boolean",
default: false
} },
additionalProperties: false
}],
messages: { unexpectedTab: "Unexpected tab character." }
},
create(context) {
const sourceCode = context.sourceCode;
const allowIndentationTabs = context.options && context.options[0] && context.options[0].allowIndentationTabs;
return { Program(node) {
sourceCode.getLines().forEach((line, index) => {
let match;
while ((match = tabRegex.exec(line)) !== null) {
if (allowIndentationTabs && !anyNonWhitespaceRegex.test(line.slice(0, match.index))) continue;
context.report({
node,
loc: {
start: {
line: index + 1,
column: match.index
},
end: {
line: index + 1,
column: match.index + match[0].length
}
},
messageId: "unexpectedTab"
});
}
});
} };
}
});
export { no_tabs_default };

View File

@@ -0,0 +1,93 @@
import { createGlobalLinebreakMatcher, createRule } from "../utils.js";
var no_trailing_spaces_default = createRule({
name: "no-trailing-spaces",
meta: {
type: "layout",
docs: { description: "Disallow trailing whitespace at the end of lines" },
fixable: "whitespace",
schema: [{
type: "object",
properties: {
skipBlankLines: {
type: "boolean",
default: false
},
ignoreComments: {
type: "boolean",
default: false
}
},
additionalProperties: false
}],
messages: { trailingSpace: "Trailing spaces not allowed." }
},
create(context) {
const sourceCode = context.sourceCode;
const BLANK_CLASS = "[ \xA0 - ]";
const SKIP_BLANK = `^${BLANK_CLASS}*$`;
const NONBLANK = `${BLANK_CLASS}+$`;
const options = context.options[0] || {};
const skipBlankLines = options.skipBlankLines || false;
const ignoreComments = options.ignoreComments || false;
function report(node, location, fixRange) {
context.report({
node,
loc: location,
messageId: "trailingSpace",
fix(fixer) {
return fixer.removeRange(fixRange);
}
});
}
function getCommentLineNumbers(comments) {
const lines = /* @__PURE__ */ new Set();
comments.forEach((comment) => {
const endLine = comment.type === "Block" ? comment.loc.end.line - 1 : comment.loc.end.line;
for (let i = comment.loc.start.line; i <= endLine; i++) lines.add(i);
});
return lines;
}
return { Program: function checkTrailingSpaces(node) {
const re = new RegExp(NONBLANK, "u");
const skipMatch = new RegExp(SKIP_BLANK, "u");
const lines = sourceCode.lines;
const linebreaks = sourceCode.getText().match(createGlobalLinebreakMatcher());
const comments = sourceCode.getAllComments();
const commentLineNumbers = getCommentLineNumbers(comments);
let totalLength = 0;
for (let i = 0, ii = lines.length; i < ii; i++) {
const lineNumber = i + 1;
const linebreakLength = linebreaks && linebreaks[i] ? linebreaks[i].length : 1;
const lineLength = lines[i].length + linebreakLength;
const matches = re.exec(lines[i]);
if (matches) {
const location = {
start: {
line: lineNumber,
column: matches.index
},
end: {
line: lineNumber,
column: lineLength - linebreakLength
}
};
const rangeStart = totalLength + location.start.column;
const rangeEnd = totalLength + location.end.column;
const containingNode = sourceCode.getNodeByRangeIndex(rangeStart);
if (containingNode && containingNode.type === "TemplateElement" && rangeStart > containingNode.parent.range[0] && rangeEnd < containingNode.parent.range[1]) {
totalLength += lineLength;
continue;
}
if (skipBlankLines && skipMatch.test(lines[i])) {
totalLength += lineLength;
continue;
}
const fixRange = [rangeStart, rangeEnd];
if (!ignoreComments || !commentLineNumbers.has(lineNumber)) report(node, location, fixRange);
}
totalLength += lineLength;
}
} };
}
});
export { no_trailing_spaces_default };

View File

@@ -0,0 +1,43 @@
import { ast_exports, createRule, isDecimalInteger } from "../utils.js";
var no_whitespace_before_property_default = createRule({
name: "no-whitespace-before-property",
meta: {
type: "layout",
docs: { description: "Disallow whitespace before properties" },
fixable: "whitespace",
schema: [],
messages: { unexpectedWhitespace: "Unexpected whitespace before property {{propName}}." }
},
create(context) {
const sourceCode = context.sourceCode;
function reportError(node, leftToken, rightToken) {
context.report({
node,
messageId: "unexpectedWhitespace",
data: { propName: sourceCode.getText(node.property) },
fix(fixer) {
let replacementText = "";
if (!node.computed && !node.optional && isDecimalInteger(node.object)) return null;
if (sourceCode.commentsExistBetween(leftToken, rightToken)) return null;
if (node.optional) replacementText = "?.";
else if (!node.computed) replacementText = ".";
return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], replacementText);
}
});
}
return { MemberExpression(node) {
let rightToken;
let leftToken;
if (!(0, ast_exports.isTokenOnSameLine)(node.object, node.property)) return;
if (node.computed) {
rightToken = sourceCode.getTokenBefore(node.property, ast_exports.isOpeningBracketToken);
leftToken = sourceCode.getTokenBefore(rightToken, node.optional ? 1 : 0);
} else {
rightToken = sourceCode.getFirstToken(node.property);
leftToken = sourceCode.getTokenBefore(rightToken, 1);
}
if (sourceCode.isSpaceBetween(leftToken, rightToken)) reportError(node, leftToken, rightToken);
} };
}
});
export { no_whitespace_before_property_default };

View File

@@ -0,0 +1,73 @@
import { ast_exports, createRule } from "../utils.js";
const POSITION_SCHEMA = {
type: "string",
enum: [
"beside",
"below",
"any"
]
};
var nonblock_statement_body_position_default = createRule({
name: "nonblock-statement-body-position",
meta: {
type: "layout",
docs: { description: "Enforce the location of single-line statements" },
fixable: "whitespace",
schema: [POSITION_SCHEMA, {
type: "object",
properties: { overrides: {
type: "object",
properties: {
if: POSITION_SCHEMA,
else: POSITION_SCHEMA,
while: POSITION_SCHEMA,
do: POSITION_SCHEMA,
for: POSITION_SCHEMA
},
additionalProperties: false
} },
additionalProperties: false
}],
messages: {
expectNoLinebreak: "Expected no linebreak before this statement.",
expectLinebreak: "Expected a linebreak before this statement."
}
},
create(context) {
const sourceCode = context.sourceCode;
function getOption(keywordName) {
return context.options[1] && context.options[1].overrides && context.options[1].overrides[keywordName] || context.options[0] || "beside";
}
function validateStatement(node, keywordName) {
const option = getOption(keywordName);
if (node.type === "BlockStatement" || option === "any") return;
const tokenBefore = sourceCode.getTokenBefore(node);
const onSameLine = (0, ast_exports.isTokenOnSameLine)(tokenBefore, node);
if (onSameLine && option === "below") context.report({
node,
messageId: "expectLinebreak",
fix: (fixer) => fixer.insertTextBefore(node, "\n")
});
else if (!onSameLine && option === "beside") context.report({
node,
messageId: "expectNoLinebreak",
fix(fixer) {
if (sourceCode.getText().slice(tokenBefore.range[1], node.range[0]).trim()) return null;
return fixer.replaceTextRange([tokenBefore.range[1], node.range[0]], " ");
}
});
}
return {
IfStatement(node) {
validateStatement(node.consequent, "if");
if (node.alternate && node.alternate.type !== "IfStatement") validateStatement(node.alternate, "else");
},
WhileStatement: (node) => validateStatement(node.body, "while"),
DoWhileStatement: (node) => validateStatement(node.body, "do"),
ForStatement: (node) => validateStatement(node.body, "for"),
ForInStatement: (node) => validateStatement(node.body, "for"),
ForOfStatement: (node) => validateStatement(node.body, "for")
};
}
});
export { nonblock_statement_body_position_default };

View File

@@ -0,0 +1,183 @@
import { ast_exports, createRule } from "../utils.js";
const OPTION_VALUE = { oneOf: [{
type: "string",
enum: ["always", "never"]
}, {
type: "object",
properties: {
multiline: { type: "boolean" },
minProperties: {
type: "integer",
minimum: 0
},
consistent: { type: "boolean" }
},
additionalProperties: false,
minProperties: 1
}] };
const defaultOptionValue = {
multiline: false,
minProperties: Number.POSITIVE_INFINITY,
consistent: true
};
var object_curly_newline_default = createRule({
name: "object-curly-newline",
meta: {
type: "layout",
docs: { description: "Enforce consistent line breaks after opening and before closing braces" },
fixable: "whitespace",
schema: [{ oneOf: [OPTION_VALUE, {
type: "object",
properties: {
ObjectExpression: OPTION_VALUE,
ObjectPattern: OPTION_VALUE,
ImportDeclaration: OPTION_VALUE,
ExportDeclaration: OPTION_VALUE,
TSTypeLiteral: OPTION_VALUE,
TSInterfaceBody: OPTION_VALUE,
TSEnumBody: OPTION_VALUE
},
additionalProperties: false,
minProperties: 1
}] }],
messages: {
unexpectedLinebreakBeforeClosingBrace: "Unexpected line break before this closing brace.",
unexpectedLinebreakAfterOpeningBrace: "Unexpected line break after this opening brace.",
expectedLinebreakBeforeClosingBrace: "Expected a line break before this closing brace.",
expectedLinebreakAfterOpeningBrace: "Expected a line break after this opening brace."
}
},
defaultOptions: [{
ObjectExpression: defaultOptionValue,
ObjectPattern: defaultOptionValue,
ImportDeclaration: defaultOptionValue,
ExportDeclaration: defaultOptionValue,
TSTypeLiteral: defaultOptionValue,
TSInterfaceBody: defaultOptionValue
}],
create(context) {
const sourceCode = context.sourceCode;
function normalizeOptionValue(value) {
let multiline = false;
let minProperties = Number.POSITIVE_INFINITY;
let consistent = false;
if (value) if (value === "always") minProperties = 0;
else if (value === "never") minProperties = Number.POSITIVE_INFINITY;
else {
multiline = Boolean(value.multiline);
minProperties = value.minProperties || Number.POSITIVE_INFINITY;
consistent = Boolean(value.consistent);
}
else consistent = true;
return {
multiline,
minProperties,
consistent
};
}
function isObject(value) {
return typeof value === "object" && value !== null;
}
function isNodeSpecificOption(option) {
return isObject(option) || typeof option === "string";
}
function normalizeOptions(options) {
if (isObject(options) && Object.values(options).some(isNodeSpecificOption)) return {
ObjectExpression: normalizeOptionValue(options.ObjectExpression),
ObjectPattern: normalizeOptionValue(options.ObjectPattern),
ImportDeclaration: normalizeOptionValue(options.ImportDeclaration),
ExportNamedDeclaration: normalizeOptionValue(options.ExportDeclaration),
TSTypeLiteral: normalizeOptionValue(options.TSTypeLiteral),
TSInterfaceBody: normalizeOptionValue(options.TSInterfaceBody),
TSEnumBody: normalizeOptionValue(options.TSEnumBody)
};
const value = normalizeOptionValue(options);
return {
ObjectExpression: value,
ObjectPattern: value,
ImportDeclaration: value,
ExportNamedDeclaration: value,
TSTypeLiteral: value,
TSInterfaceBody: value,
TSEnumBody: value
};
}
const normalizedOptions = normalizeOptions(context.options[0]);
function areLineBreaksRequired(node, options, first, last) {
let objectProperties;
if (node.type === "ObjectExpression" || node.type === "ObjectPattern") objectProperties = node.properties;
else if (node.type === "TSTypeLiteral") objectProperties = node.members;
else if (node.type === "TSInterfaceBody") objectProperties = node.body;
else if (node.type === "TSEnumBody") objectProperties = node.members;
else objectProperties = node.specifiers.filter((s) => s.type === "ImportSpecifier" || s.type === "ExportSpecifier");
return objectProperties.length >= options.minProperties || options.multiline && objectProperties.length > 0 && !(0, ast_exports.isTokenOnSameLine)(last, first);
}
function check(node) {
const options = normalizedOptions[node.type];
if (node.type === "ImportDeclaration" && !node.specifiers.some((specifier) => specifier.type === "ImportSpecifier") || node.type === "ExportNamedDeclaration" && !node.specifiers.some((specifier) => specifier.type === "ExportSpecifier")) return;
const openBrace = sourceCode.getFirstToken(node, (token) => token.value === "{");
let closeBrace;
if (node.type === "ObjectPattern" && node.typeAnnotation) closeBrace = sourceCode.getTokenBefore(node.typeAnnotation);
else closeBrace = sourceCode.getLastToken(node, (token) => token.value === "}");
let first = sourceCode.getTokenAfter(openBrace, { includeComments: true });
let last = sourceCode.getTokenBefore(closeBrace, { includeComments: true });
const needsLineBreaks = areLineBreaksRequired(node, options, first, last);
const hasCommentsFirstToken = (0, ast_exports.isCommentToken)(first);
const hasCommentsLastToken = (0, ast_exports.isCommentToken)(last);
first = sourceCode.getTokenAfter(openBrace);
last = sourceCode.getTokenBefore(closeBrace);
if (needsLineBreaks) {
if ((0, ast_exports.isTokenOnSameLine)(openBrace, first)) context.report({
messageId: "expectedLinebreakAfterOpeningBrace",
node,
loc: openBrace.loc,
fix(fixer) {
if (hasCommentsFirstToken) return null;
return fixer.insertTextAfter(openBrace, "\n");
}
});
if ((0, ast_exports.isTokenOnSameLine)(last, closeBrace)) context.report({
messageId: "expectedLinebreakBeforeClosingBrace",
node,
loc: closeBrace.loc,
fix(fixer) {
if (hasCommentsLastToken) return null;
return fixer.insertTextBefore(closeBrace, "\n");
}
});
} else {
const consistent = options.consistent;
const hasLineBreakBetweenOpenBraceAndFirst = !(0, ast_exports.isTokenOnSameLine)(openBrace, first);
const hasLineBreakBetweenCloseBraceAndLast = !(0, ast_exports.isTokenOnSameLine)(last, closeBrace);
if (!consistent && hasLineBreakBetweenOpenBraceAndFirst || consistent && hasLineBreakBetweenOpenBraceAndFirst && !hasLineBreakBetweenCloseBraceAndLast) context.report({
messageId: "unexpectedLinebreakAfterOpeningBrace",
node,
loc: openBrace.loc,
fix(fixer) {
if (hasCommentsFirstToken) return null;
return fixer.removeRange([openBrace.range[1], first.range[0]]);
}
});
if (!consistent && hasLineBreakBetweenCloseBraceAndLast || consistent && !hasLineBreakBetweenOpenBraceAndFirst && hasLineBreakBetweenCloseBraceAndLast) context.report({
messageId: "unexpectedLinebreakBeforeClosingBrace",
node,
loc: closeBrace.loc,
fix(fixer) {
if (hasCommentsLastToken) return null;
return fixer.removeRange([last.range[1], closeBrace.range[0]]);
}
});
}
}
return {
ObjectExpression: check,
ObjectPattern: check,
ImportDeclaration: check,
ExportNamedDeclaration: check,
TSTypeLiteral: check,
TSInterfaceBody: check,
TSEnumBody: check
};
}
});
export { object_curly_newline_default };

View File

@@ -0,0 +1,180 @@
import { AST_NODE_TYPES, AST_TOKEN_TYPES, ast_exports, createRule } from "../utils.js";
const SUPPORTED_NODES = [
"ObjectPattern",
"ObjectExpression",
"ImportDeclaration",
"ImportAttributes",
"ExportNamedDeclaration",
"ExportAllDeclaration",
"TSMappedType",
"TSTypeLiteral",
"TSInterfaceBody",
"TSEnumBody"
];
var object_curly_spacing_default = createRule({
name: "object-curly-spacing",
meta: {
type: "layout",
docs: { description: "Enforce consistent spacing inside braces" },
fixable: "whitespace",
schema: [{
type: "string",
enum: ["always", "never"]
}, {
type: "object",
properties: {
arraysInObjects: { type: "boolean" },
objectsInObjects: { type: "boolean" },
overrides: {
type: "object",
properties: Object.fromEntries(SUPPORTED_NODES.map((node) => [node, {
type: "string",
enum: ["always", "never"]
}])),
additionalProperties: false
}
},
additionalProperties: false
}],
messages: {
requireSpaceBefore: "A space is required before '{{token}}'.",
requireSpaceAfter: "A space is required after '{{token}}'.",
unexpectedSpaceBefore: "There should be no space before '{{token}}'.",
unexpectedSpaceAfter: "There should be no space after '{{token}}'."
}
},
defaultOptions: ["never"],
create(context) {
const [firstOption, secondOption] = context.options;
const spaced = firstOption === "always";
const sourceCode = context.sourceCode;
function isOptionSet(option) {
return secondOption ? secondOption[option] === !spaced : false;
}
const options = {
spaced,
arraysInObjectsException: isOptionSet("arraysInObjects"),
objectsInObjectsException: isOptionSet("objectsInObjects"),
overrides: secondOption?.overrides ?? {}
};
function reportNoBeginningSpace(node, token) {
const nextToken = sourceCode.getTokenAfter(token, { includeComments: true });
context.report({
node,
loc: {
start: token.loc.end,
end: nextToken.loc.start
},
messageId: "unexpectedSpaceAfter",
data: { token: token.value },
fix(fixer) {
return fixer.removeRange([token.range[1], nextToken.range[0]]);
}
});
}
function reportNoEndingSpace(node, token) {
const previousToken = sourceCode.getTokenBefore(token, { includeComments: true });
context.report({
node,
loc: {
start: previousToken.loc.end,
end: token.loc.start
},
messageId: "unexpectedSpaceBefore",
data: { token: token.value },
fix(fixer) {
return fixer.removeRange([previousToken.range[1], token.range[0]]);
}
});
}
function reportRequiredBeginningSpace(node, token) {
context.report({
node,
loc: token.loc,
messageId: "requireSpaceAfter",
data: { token: token.value },
fix(fixer) {
return fixer.insertTextAfter(token, " ");
}
});
}
function reportRequiredEndingSpace(node, token) {
context.report({
node,
loc: token.loc,
messageId: "requireSpaceBefore",
data: { token: token.value },
fix(fixer) {
return fixer.insertTextBefore(token, " ");
}
});
}
function validateBraceSpacing(node, openingToken, closingToken, nodeType = node.type) {
const tokenAfterOpening = sourceCode.getTokenAfter(openingToken, { includeComments: true });
const spaced$1 = options.overrides[nodeType] ? options.overrides[nodeType] === "always" : options.spaced;
if ((0, ast_exports.isTokenOnSameLine)(openingToken, tokenAfterOpening)) {
const firstSpaced = sourceCode.isSpaceBetween(openingToken, tokenAfterOpening);
const secondType = sourceCode.getNodeByRangeIndex(tokenAfterOpening.range[0]).type;
const openingCurlyBraceMustBeSpaced = options.arraysInObjectsException && [AST_NODE_TYPES.TSMappedType, AST_NODE_TYPES.TSIndexSignature].includes(secondType) ? !spaced$1 : spaced$1;
if (openingCurlyBraceMustBeSpaced && !firstSpaced) reportRequiredBeginningSpace(node, openingToken);
if (!openingCurlyBraceMustBeSpaced && firstSpaced && tokenAfterOpening.type !== AST_TOKEN_TYPES.Line) reportNoBeginningSpace(node, openingToken);
}
const tokenBeforeClosing = sourceCode.getTokenBefore(closingToken, { includeComments: true });
if ((0, ast_exports.isTokenOnSameLine)(tokenBeforeClosing, closingToken)) {
const shouldCheckPenultimate = options.arraysInObjectsException && (0, ast_exports.isClosingBracketToken)(tokenBeforeClosing) || options.objectsInObjectsException && (0, ast_exports.isClosingBraceToken)(tokenBeforeClosing);
const penultimateType = shouldCheckPenultimate ? sourceCode.getNodeByRangeIndex(tokenBeforeClosing.range[0]).type : void 0;
const closingCurlyBraceMustBeSpaced = options.arraysInObjectsException && [AST_NODE_TYPES.ArrayExpression, AST_NODE_TYPES.TSTupleType].includes(penultimateType) || options.objectsInObjectsException && penultimateType !== void 0 && [
AST_NODE_TYPES.ObjectExpression,
AST_NODE_TYPES.ObjectPattern,
AST_NODE_TYPES.TSMappedType,
AST_NODE_TYPES.TSTypeLiteral
].includes(penultimateType) ? !spaced$1 : spaced$1;
const lastSpaced = sourceCode.isSpaceBetween(tokenBeforeClosing, closingToken);
if (closingCurlyBraceMustBeSpaced && !lastSpaced) reportRequiredEndingSpace(node, closingToken);
if (!closingCurlyBraceMustBeSpaced && lastSpaced) reportNoEndingSpace(node, closingToken);
}
}
function checkForObjectLike(node, properties, nodeType = node.type) {
if (properties.length === 0) return;
const openingToken = sourceCode.getTokenBefore(properties[0], ast_exports.isOpeningBraceToken);
const closeToken = sourceCode.getTokenAfter(properties.at(-1), ast_exports.isClosingBraceToken);
validateBraceSpacing(node, openingToken, closeToken, nodeType);
}
return {
ObjectPattern(node) {
checkForObjectLike(node, node.properties);
},
ObjectExpression(node) {
checkForObjectLike(node, node.properties);
},
ImportDeclaration(node) {
if (node.attributes) checkForObjectLike(node, node.attributes, "ImportAttributes");
const firstSpecifierIndex = node.specifiers.findIndex((specifier) => specifier.type === "ImportSpecifier");
if (firstSpecifierIndex === -1) return;
checkForObjectLike(node, node.specifiers.slice(firstSpecifierIndex));
},
ExportNamedDeclaration(node) {
checkForObjectLike(node, node.specifiers);
if (node.attributes) checkForObjectLike(node, node.attributes, "ImportAttributes");
},
ExportAllDeclaration(node) {
if (node.attributes) checkForObjectLike(node, node.attributes, "ImportAttributes");
},
TSMappedType(node) {
const openingToken = sourceCode.getFirstToken(node);
const closeToken = sourceCode.getLastToken(node);
validateBraceSpacing(node, openingToken, closeToken);
},
TSTypeLiteral(node) {
checkForObjectLike(node, node.members);
},
TSInterfaceBody(node) {
checkForObjectLike(node, node.body);
},
TSEnumBody(node) {
checkForObjectLike(node, node.members);
}
};
}
});
export { object_curly_spacing_default };

View File

@@ -0,0 +1,63 @@
import { ast_exports, createRule } from "../utils.js";
var object_property_newline_default = createRule({
name: "object-property-newline",
meta: {
type: "layout",
docs: { description: "Enforce placing object properties on separate lines" },
schema: [{
type: "object",
properties: { allowAllPropertiesOnSameLine: {
type: "boolean",
default: false
} },
additionalProperties: false
}],
fixable: "whitespace",
messages: {
propertiesOnNewlineAll: "Object properties must go on a new line if they aren't all on the same line.",
propertiesOnNewline: "Object properties must go on a new line."
}
},
defaultOptions: [{ allowAllPropertiesOnSameLine: false }],
create(context) {
const allowSameLine = context.options[0] && context.options[0].allowAllPropertiesOnSameLine;
const messageId = allowSameLine ? "propertiesOnNewlineAll" : "propertiesOnNewline";
const sourceCode = context.sourceCode;
function check(node, children) {
if (allowSameLine) {
if (children.length > 1) {
const firstTokenOfFirstProperty = sourceCode.getFirstToken(children[0]);
const lastTokenOfLastProperty = sourceCode.getLastToken(children[children.length - 1]);
if ((0, ast_exports.isTokenOnSameLine)(firstTokenOfFirstProperty, lastTokenOfLastProperty)) return;
}
}
for (let i = 1; i < children.length; i++) {
const lastTokenOfPreviousProperty = sourceCode.getLastToken(children[i - 1]);
const firstTokenOfCurrentProperty = sourceCode.getFirstToken(children[i]);
if ((0, ast_exports.isTokenOnSameLine)(lastTokenOfPreviousProperty, firstTokenOfCurrentProperty)) context.report({
node,
loc: firstTokenOfCurrentProperty.loc,
messageId,
fix(fixer) {
const comma = sourceCode.getTokenBefore(firstTokenOfCurrentProperty);
const rangeAfterComma = [comma.range[1], firstTokenOfCurrentProperty.range[0]];
if (sourceCode.text.slice(rangeAfterComma[0], rangeAfterComma[1]).trim()) return null;
return fixer.replaceTextRange(rangeAfterComma, "\n");
}
});
}
}
return {
ObjectExpression(node) {
check(node, node.properties);
},
TSTypeLiteral(node) {
check(node, node.members);
},
TSInterfaceBody(node) {
check(node, node.body);
}
};
}
});
export { object_property_newline_default };

View File

@@ -0,0 +1,47 @@
import { ast_exports, createRule } from "../utils.js";
var one_var_declaration_per_line_default = createRule({
name: "one-var-declaration-per-line",
meta: {
type: "layout",
docs: { description: "Require or disallow newlines around variable declarations" },
schema: [{
type: "string",
enum: ["always", "initializations"]
}],
fixable: "whitespace",
messages: { expectVarOnNewline: "Expected variable declaration to be on a new line." }
},
create(context) {
const { sourceCode } = context;
const always = context.options[0] === "always";
function isForTypeSpecifier(keyword) {
return keyword === "ForStatement" || keyword === "ForInStatement" || keyword === "ForOfStatement";
}
function checkForNewLine(node) {
if (isForTypeSpecifier(node.parent.type)) return;
const declarations = node.declarations;
let prev;
declarations.forEach((current) => {
if (prev && (0, ast_exports.isTokenOnSameLine)(prev, current)) {
if (always || prev.init || current.init) {
let fix = (fixer) => fixer.insertTextBefore(current, "\n");
const tokenBeforeDeclarator = sourceCode.getTokenBefore(current, { includeComments: false });
if (tokenBeforeDeclarator) {
const betweenText = sourceCode.text.slice(tokenBeforeDeclarator.range[1], current.range[0]);
fix = (fixer) => fixer.replaceTextRange([tokenBeforeDeclarator.range[1], current.range[0]], `${betweenText}\n`);
}
context.report({
node,
messageId: "expectVarOnNewline",
loc: current.loc,
fix
});
}
}
prev = current;
});
}
return { VariableDeclaration: checkForNewLine };
}
});
export { one_var_declaration_per_line_default };

View File

@@ -0,0 +1,154 @@
import { ast_exports, createGlobalLinebreakMatcher, createRule } from "../utils.js";
var operator_linebreak_default = createRule({
name: "operator-linebreak",
meta: {
type: "layout",
docs: { description: "Enforce consistent linebreak style for operators" },
schema: [{ oneOf: [{
type: "string",
enum: [
"after",
"before",
"none"
]
}, { type: "null" }] }, {
type: "object",
properties: { overrides: {
type: "object",
additionalProperties: {
type: "string",
enum: [
"after",
"before",
"none",
"ignore"
]
}
} },
additionalProperties: false
}],
fixable: "code",
messages: {
operatorAtBeginning: "'{{operator}}' should be placed at the beginning of the line.",
operatorAtEnd: "'{{operator}}' should be placed at the end of the line.",
badLinebreak: "Bad line breaking before and after '{{operator}}'.",
noLinebreak: "There should be no line break before or after '{{operator}}'."
}
},
create(context) {
const usedDefaultGlobal = !context.options[0];
const globalStyle = context.options[0] || "after";
const options = context.options[1] || {};
const styleOverrides = options.overrides ? Object.assign({}, options.overrides) : {};
if (usedDefaultGlobal && !styleOverrides["?"]) styleOverrides["?"] = "before";
if (usedDefaultGlobal && !styleOverrides[":"]) styleOverrides[":"] = "before";
const sourceCode = context.sourceCode;
function getFixer(operatorToken, desiredStyle) {
return (fixer) => {
const tokenBefore = sourceCode.getTokenBefore(operatorToken);
const tokenAfter = sourceCode.getTokenAfter(operatorToken);
const textBefore = sourceCode.text.slice(tokenBefore.range[1], operatorToken.range[0]);
const textAfter = sourceCode.text.slice(operatorToken.range[1], tokenAfter.range[0]);
const hasLinebreakBefore = !(0, ast_exports.isTokenOnSameLine)(tokenBefore, operatorToken);
const hasLinebreakAfter = !(0, ast_exports.isTokenOnSameLine)(operatorToken, tokenAfter);
let newTextBefore, newTextAfter;
if (hasLinebreakBefore !== hasLinebreakAfter && desiredStyle !== "none") {
if (sourceCode.getTokenBefore(operatorToken, { includeComments: true }) !== tokenBefore && sourceCode.getTokenAfter(operatorToken, { includeComments: true }) !== tokenAfter) return null;
newTextBefore = textAfter;
newTextAfter = textBefore;
} else {
const LINEBREAK_REGEX = createGlobalLinebreakMatcher();
newTextBefore = desiredStyle === "before" || textBefore.trim() ? textBefore : textBefore.replace(LINEBREAK_REGEX, "");
newTextAfter = desiredStyle === "after" || textAfter.trim() ? textAfter : textAfter.replace(LINEBREAK_REGEX, "");
if (newTextBefore === textBefore && newTextAfter === textAfter) return null;
}
if (newTextAfter === "" && tokenAfter.type === "Punctuator" && "+-".includes(operatorToken.value) && tokenAfter.value === operatorToken.value) newTextAfter += " ";
return fixer.replaceTextRange([tokenBefore.range[1], tokenAfter.range[0]], newTextBefore + operatorToken.value + newTextAfter);
};
}
function validateNode(node, rightSide, operator) {
const operatorToken = sourceCode.getTokenBefore(rightSide, (token) => token.value === operator);
const leftToken = sourceCode.getTokenBefore(operatorToken);
const rightToken = sourceCode.getTokenAfter(operatorToken);
const operatorStyleOverride = styleOverrides[operator];
const style = operatorStyleOverride || globalStyle;
const fix = getFixer(operatorToken, style);
if ((0, ast_exports.isTokenOnSameLine)(leftToken, operatorToken) && (0, ast_exports.isTokenOnSameLine)(operatorToken, rightToken)) {} else if (operatorStyleOverride !== "ignore" && !(0, ast_exports.isTokenOnSameLine)(leftToken, operatorToken) && !(0, ast_exports.isTokenOnSameLine)(operatorToken, rightToken)) context.report({
node,
loc: operatorToken.loc,
messageId: "badLinebreak",
data: { operator },
fix
});
else if (style === "before" && (0, ast_exports.isTokenOnSameLine)(leftToken, operatorToken)) context.report({
node,
loc: operatorToken.loc,
messageId: "operatorAtBeginning",
data: { operator },
fix
});
else if (style === "after" && (0, ast_exports.isTokenOnSameLine)(operatorToken, rightToken)) context.report({
node,
loc: operatorToken.loc,
messageId: "operatorAtEnd",
data: { operator },
fix
});
else if (style === "none") context.report({
node,
loc: operatorToken.loc,
messageId: "noLinebreak",
data: { operator },
fix
});
}
function validateBinaryExpression(node) {
validateNode(node, node.right, node.operator);
}
return {
BinaryExpression: validateBinaryExpression,
LogicalExpression: validateBinaryExpression,
AssignmentExpression: validateBinaryExpression,
VariableDeclarator(node) {
if (node.init) validateNode(node, node.init, "=");
},
PropertyDefinition(node) {
if (node.value) validateNode(node, node.value, "=");
},
AccessorProperty(node) {
if (node.value) validateNode(node, node.value, "=");
},
ConditionalExpression(node) {
validateNode(node, node.consequent, "?");
validateNode(node, node.alternate, ":");
},
TSImportEqualsDeclaration(node) {
validateNode(node, node.moduleReference, "=");
},
TSTypeAliasDeclaration(node) {
validateNode(node, node.typeAnnotation, "=");
},
TSConditionalType(node) {
validateNode(node, node.trueType, "?");
validateNode(node, node.falseType, ":");
},
TSIntersectionType(node) {
const { types } = node;
for (let idx = 0; idx < types.length - 1; idx++) validateNode(types[idx], types[idx + 1], "&");
},
TSUnionType(node) {
const { types } = node;
for (let idx = 0; idx < types.length - 1; idx++) validateNode(types[idx], types[idx + 1], "|");
},
TSTypeParameter(node) {
if (!node.default) return;
validateNode(node, node.default, "=");
},
TSEnumMember(node) {
if (!node.initializer) return;
validateNode(node, node.initializer, "=");
}
};
}
});
export { operator_linebreak_default };

View File

@@ -0,0 +1,171 @@
import { ast_exports, createRule } from "../utils.js";
const OPTION_ENUMS = [
"always",
"never",
"start",
"end"
];
var padded_blocks_default = createRule({
name: "padded-blocks",
meta: {
type: "layout",
docs: { description: "Require or disallow padding within blocks" },
fixable: "whitespace",
schema: [{ oneOf: [{
type: "string",
enum: OPTION_ENUMS
}, {
type: "object",
properties: {
blocks: {
type: "string",
enum: OPTION_ENUMS
},
switches: {
type: "string",
enum: OPTION_ENUMS
},
classes: {
type: "string",
enum: OPTION_ENUMS
}
},
additionalProperties: false,
minProperties: 1
}] }, {
type: "object",
properties: { allowSingleLineBlocks: { type: "boolean" } },
additionalProperties: false
}],
messages: {
missingPadBlock: "Block must be padded by blank lines.",
extraPadBlock: "Block must not be padded by blank lines."
}
},
create(context) {
const options = {};
const typeOptions = context.options[0] || "always";
const exceptOptions = context.options[1] || {};
if (typeof typeOptions === "string") {
options.blocks = typeOptions;
options.switches = typeOptions;
options.classes = typeOptions;
} else Object.assign(options, typeOptions);
exceptOptions.allowSingleLineBlocks ??= false;
const sourceCode = context.sourceCode;
function getOpenBrace(node) {
if (node.type === "SwitchStatement") return sourceCode.getTokenBefore(node.cases[0]);
if (node.type === "StaticBlock") return sourceCode.getFirstToken(node, { skip: 1 });
return sourceCode.getFirstToken(node);
}
function isComment(node) {
return node.type === "Line" || node.type === "Block";
}
function isPaddingBetweenTokens(first, second) {
return second.loc.start.line - first.loc.end.line >= 2;
}
function getFirstBlockToken(token) {
let prev;
let first = token;
do {
prev = first;
first = sourceCode.getTokenAfter(first, { includeComments: true });
} while (isComment(first) && (0, ast_exports.isTokenOnSameLine)(prev, first));
return first;
}
function getLastBlockToken(token) {
let last = token;
let next;
do {
next = last;
last = sourceCode.getTokenBefore(last, { includeComments: true });
} while (isComment(last) && (0, ast_exports.isTokenOnSameLine)(last, next));
return last;
}
function requirePaddingFor(node) {
switch (node.type) {
case "BlockStatement":
case "StaticBlock": return options.blocks;
case "SwitchStatement": return options.switches;
case "ClassBody": return options.classes;
default: throw new Error("unreachable");
}
}
function checkPadding(node) {
const openBrace = getOpenBrace(node);
const firstBlockToken = getFirstBlockToken(openBrace);
const tokenBeforeFirst = sourceCode.getTokenBefore(firstBlockToken, { includeComments: true });
const closeBrace = sourceCode.getLastToken(node);
const lastBlockToken = getLastBlockToken(closeBrace);
const tokenAfterLast = sourceCode.getTokenAfter(lastBlockToken, { includeComments: true });
const blockHasTopPadding = isPaddingBetweenTokens(tokenBeforeFirst, firstBlockToken);
const blockHasBottomPadding = isPaddingBetweenTokens(lastBlockToken, tokenAfterLast);
if (exceptOptions.allowSingleLineBlocks && (0, ast_exports.isTokenOnSameLine)(tokenBeforeFirst, tokenAfterLast)) return;
const requiredPadding = requirePaddingFor(node);
if (blockHasTopPadding) {
if (requiredPadding === "never" || requiredPadding === "end") context.report({
node,
loc: {
start: tokenBeforeFirst.loc.start,
end: firstBlockToken.loc.start
},
fix(fixer) {
return fixer.replaceTextRange([tokenBeforeFirst.range[1], firstBlockToken.range[0] - firstBlockToken.loc.start.column], "\n");
},
messageId: "extraPadBlock"
});
} else if (requiredPadding === "always" || requiredPadding === "start") context.report({
node,
loc: {
start: tokenBeforeFirst.loc.start,
end: firstBlockToken.loc.start
},
fix(fixer) {
return fixer.insertTextAfter(tokenBeforeFirst, "\n");
},
messageId: "missingPadBlock"
});
if (blockHasBottomPadding) {
if (requiredPadding === "never" || requiredPadding === "start") context.report({
node,
loc: {
end: tokenAfterLast.loc.start,
start: lastBlockToken.loc.end
},
messageId: "extraPadBlock",
fix(fixer) {
return fixer.replaceTextRange([lastBlockToken.range[1], tokenAfterLast.range[0] - tokenAfterLast.loc.start.column], "\n");
}
});
} else if (requiredPadding === "always" || requiredPadding === "end") context.report({
node,
loc: {
end: tokenAfterLast.loc.start,
start: lastBlockToken.loc.end
},
fix(fixer) {
return fixer.insertTextBefore(tokenAfterLast, "\n");
},
messageId: "missingPadBlock"
});
}
const rule = {};
if (Object.prototype.hasOwnProperty.call(options, "switches")) rule.SwitchStatement = function(node) {
if (node.cases.length === 0) return;
checkPadding(node);
};
if (Object.prototype.hasOwnProperty.call(options, "blocks")) {
rule.BlockStatement = function(node) {
if (node.body.length === 0) return;
checkPadding(node);
};
rule.StaticBlock = rule.BlockStatement;
}
if (Object.prototype.hasOwnProperty.call(options, "classes")) rule.ClassBody = function(node) {
if (node.body.length === 0) return;
checkPadding(node);
};
return rule;
}
});
export { padded_blocks_default };

View File

@@ -0,0 +1,323 @@
import { AST_NODE_TYPES, LINEBREAKS, ast_exports, createRule, isSingleLine, isTopLevelExpressionStatement, skipChainExpression } from "../utils.js";
const CJS_EXPORT = /^(?:module\s*\.\s*)?exports(?:\s*\.|\s*\[|$)/u;
const CJS_IMPORT = /^require\(/u;
const LT = `[${Array.from(LINEBREAKS).join("")}]`;
const PADDING_LINE_SEQUENCE = new RegExp(String.raw`^(\s*?${LT})\s*${LT}(\s*;?)$`, "u");
function newKeywordTester(type, keyword) {
return { test(node, sourceCode) {
const isSameKeyword = sourceCode.getFirstToken(node)?.value === keyword;
const isSameType = Array.isArray(type) ? type.includes(node.type) : type === node.type;
return isSameKeyword && isSameType;
} };
}
function newNodeTypeTester(type) {
return { test: (node) => node.type === type };
}
function isIIFEStatement(node) {
if (node.type === AST_NODE_TYPES.ExpressionStatement) {
let expression = skipChainExpression(node.expression);
if (expression.type === AST_NODE_TYPES.UnaryExpression) expression = skipChainExpression(expression.argument);
if (expression.type === AST_NODE_TYPES.CallExpression) {
let node$1 = expression.callee;
while (node$1.type === AST_NODE_TYPES.SequenceExpression) node$1 = node$1.expressions[node$1.expressions.length - 1];
return (0, ast_exports.isFunction)(node$1);
}
}
return false;
}
function isCJSRequire(node) {
if (node.type === AST_NODE_TYPES.VariableDeclaration) {
const declaration = node.declarations[0];
if (declaration?.init) {
let call = declaration?.init;
while (call.type === AST_NODE_TYPES.MemberExpression) call = call.object;
if (call.type === AST_NODE_TYPES.CallExpression && call.callee.type === AST_NODE_TYPES.Identifier) return call.callee.name === "require";
}
}
return false;
}
function isBlockLikeStatement(node, sourceCode) {
if (node.type === AST_NODE_TYPES.DoWhileStatement && node.body.type === AST_NODE_TYPES.BlockStatement) return true;
if (isIIFEStatement(node)) return true;
const lastToken = sourceCode.getLastToken(node, ast_exports.isNotSemicolonToken);
const belongingNode = lastToken && (0, ast_exports.isClosingBraceToken)(lastToken) ? sourceCode.getNodeByRangeIndex(lastToken.range[0]) : null;
return !!belongingNode && (belongingNode.type === AST_NODE_TYPES.BlockStatement || belongingNode.type === AST_NODE_TYPES.SwitchStatement);
}
function isDirective(node, sourceCode) {
return isTopLevelExpressionStatement(node) && node.expression.type === AST_NODE_TYPES.Literal && typeof node.expression.value === "string" && !(0, ast_exports.isParenthesized)(node.expression, sourceCode);
}
function isDirectivePrologue(node, sourceCode) {
if (isDirective(node, sourceCode) && node.parent && "body" in node.parent && Array.isArray(node.parent.body)) {
for (const sibling of node.parent.body) {
if (sibling === node) break;
if (!isDirective(sibling, sourceCode)) return false;
}
return true;
}
return false;
}
function isCJSExport(node) {
if (node.type === AST_NODE_TYPES.ExpressionStatement) {
const expression = node.expression;
if (expression.type === AST_NODE_TYPES.AssignmentExpression) {
let left = expression.left;
if (left.type === AST_NODE_TYPES.MemberExpression) {
while (left.object.type === AST_NODE_TYPES.MemberExpression) left = left.object;
return left.object.type === AST_NODE_TYPES.Identifier && (left.object.name === "exports" || left.object.name === "module" && left.property.type === AST_NODE_TYPES.Identifier && left.property.name === "exports");
}
}
}
return false;
}
function isExpression(node, sourceCode) {
return node.type === AST_NODE_TYPES.ExpressionStatement && !isDirectivePrologue(node, sourceCode);
}
function getActualLastToken(node, sourceCode) {
const semiToken = sourceCode.getLastToken(node);
const prevToken = sourceCode.getTokenBefore(semiToken);
const nextToken = sourceCode.getTokenAfter(semiToken);
const isSemicolonLessStyle = prevToken && nextToken && prevToken.range[0] >= node.range[0] && (0, ast_exports.isSemicolonToken)(semiToken) && !(0, ast_exports.isTokenOnSameLine)(prevToken, semiToken) && (0, ast_exports.isTokenOnSameLine)(semiToken, nextToken);
return isSemicolonLessStyle ? prevToken : semiToken;
}
function replacerToRemovePaddingLines(_, trailingSpaces, indentSpaces) {
return trailingSpaces + indentSpaces;
}
function verifyForAny() {}
function verifyForNever(context, _, nextNode, paddingLines) {
if (paddingLines.length === 0) return;
context.report({
node: nextNode,
messageId: "unexpectedBlankLine",
fix(fixer) {
if (paddingLines.length >= 2) return null;
const prevToken = paddingLines[0][0];
const nextToken = paddingLines[0][1];
const start = prevToken.range[1];
const end = nextToken.range[0];
const text = context.getSourceCode().text.slice(start, end).replace(PADDING_LINE_SEQUENCE, replacerToRemovePaddingLines);
return fixer.replaceTextRange([start, end], text);
}
});
}
function verifyForAlways(context, prevNode, nextNode, paddingLines) {
if (paddingLines.length > 0) return;
context.report({
node: nextNode,
messageId: "expectedBlankLine",
fix(fixer) {
const sourceCode = context.sourceCode;
let prevToken = getActualLastToken(prevNode, sourceCode);
const nextToken = sourceCode.getFirstTokenBetween(prevToken, nextNode, {
includeComments: true,
filter(token) {
if ((0, ast_exports.isTokenOnSameLine)(prevToken, token)) {
prevToken = token;
return false;
}
return true;
}
}) || nextNode;
const insertText = (0, ast_exports.isTokenOnSameLine)(prevToken, nextToken) ? "\n\n" : "\n";
return fixer.insertTextAfter(prevToken, insertText);
}
});
}
const PaddingTypes = {
any: { verify: verifyForAny },
never: { verify: verifyForNever },
always: { verify: verifyForAlways }
};
const MaybeMultilineStatementType = {
"block-like": { test: isBlockLikeStatement },
"expression": { test: isExpression },
"return": newKeywordTester(AST_NODE_TYPES.ReturnStatement, "return"),
"export": newKeywordTester([
AST_NODE_TYPES.ExportAllDeclaration,
AST_NODE_TYPES.ExportDefaultDeclaration,
AST_NODE_TYPES.ExportNamedDeclaration
], "export"),
"var": newKeywordTester(AST_NODE_TYPES.VariableDeclaration, "var"),
"let": newKeywordTester(AST_NODE_TYPES.VariableDeclaration, "let"),
"const": newKeywordTester(AST_NODE_TYPES.VariableDeclaration, "const"),
"using": { test: (node) => node.type === "VariableDeclaration" && (node.kind === "using" || node.kind === "await using") },
"type": newKeywordTester(AST_NODE_TYPES.TSTypeAliasDeclaration, "type")
};
const StatementTypes = {
"*": { test: () => true },
"exports": { test: isCJSExport },
"require": { test: isCJSRequire },
"directive": { test: isDirectivePrologue },
"iife": { test: isIIFEStatement },
"block": newNodeTypeTester(AST_NODE_TYPES.BlockStatement),
"empty": newNodeTypeTester(AST_NODE_TYPES.EmptyStatement),
"function": newNodeTypeTester(AST_NODE_TYPES.FunctionDeclaration),
"ts-method": newNodeTypeTester(AST_NODE_TYPES.TSMethodSignature),
"break": newKeywordTester(AST_NODE_TYPES.BreakStatement, "break"),
"case": newKeywordTester(AST_NODE_TYPES.SwitchCase, "case"),
"class": newKeywordTester(AST_NODE_TYPES.ClassDeclaration, "class"),
"continue": newKeywordTester(AST_NODE_TYPES.ContinueStatement, "continue"),
"debugger": newKeywordTester(AST_NODE_TYPES.DebuggerStatement, "debugger"),
"default": newKeywordTester([AST_NODE_TYPES.SwitchCase, AST_NODE_TYPES.ExportDefaultDeclaration], "default"),
"do": newKeywordTester(AST_NODE_TYPES.DoWhileStatement, "do"),
"for": newKeywordTester([
AST_NODE_TYPES.ForStatement,
AST_NODE_TYPES.ForInStatement,
AST_NODE_TYPES.ForOfStatement
], "for"),
"if": newKeywordTester(AST_NODE_TYPES.IfStatement, "if"),
"import": newKeywordTester(AST_NODE_TYPES.ImportDeclaration, "import"),
"switch": newKeywordTester(AST_NODE_TYPES.SwitchStatement, "switch"),
"throw": newKeywordTester(AST_NODE_TYPES.ThrowStatement, "throw"),
"try": newKeywordTester(AST_NODE_TYPES.TryStatement, "try"),
"while": newKeywordTester([AST_NODE_TYPES.WhileStatement, AST_NODE_TYPES.DoWhileStatement], "while"),
"with": newKeywordTester(AST_NODE_TYPES.WithStatement, "with"),
"cjs-export": { test: (node, sourceCode) => node.type === "ExpressionStatement" && node.expression.type === "AssignmentExpression" && CJS_EXPORT.test(sourceCode.getText(node.expression.left)) },
"cjs-import": { test: (node, sourceCode) => node.type === "VariableDeclaration" && node.declarations.length > 0 && Boolean(node.declarations[0].init) && CJS_IMPORT.test(sourceCode.getText(node.declarations[0].init)) },
"enum": newKeywordTester(AST_NODE_TYPES.TSEnumDeclaration, "enum"),
"interface": newKeywordTester(AST_NODE_TYPES.TSInterfaceDeclaration, "interface"),
"function-overload": newNodeTypeTester(AST_NODE_TYPES.TSDeclareFunction),
...Object.fromEntries(Object.entries(MaybeMultilineStatementType).flatMap(([key, value]) => [
[key, value],
[`singleline-${key}`, {
...value,
test: (node, sourceCode) => value.test(node, sourceCode) && isSingleLine(node)
}],
[`multiline-${key}`, {
...value,
test: (node, sourceCode) => value.test(node, sourceCode) && !isSingleLine(node)
}]
]))
};
var padding_line_between_statements_default = createRule({
name: "padding-line-between-statements",
meta: {
type: "layout",
docs: { description: "Require or disallow padding lines between statements" },
fixable: "whitespace",
hasSuggestions: false,
schema: {
$defs: {
paddingType: {
type: "string",
enum: Object.keys(PaddingTypes)
},
statementType: {
type: "string",
enum: Object.keys(StatementTypes)
},
statementOption: { anyOf: [{ $ref: "#/$defs/statementType" }, {
type: "array",
items: { $ref: "#/$defs/statementType" },
minItems: 1,
uniqueItems: true,
additionalItems: false
}] }
},
type: "array",
additionalItems: false,
items: {
type: "object",
properties: {
blankLine: { $ref: "#/$defs/paddingType" },
prev: { $ref: "#/$defs/statementOption" },
next: { $ref: "#/$defs/statementOption" }
},
additionalProperties: false,
required: [
"blankLine",
"prev",
"next"
]
}
},
messages: {
unexpectedBlankLine: "Unexpected blank line before this statement.",
expectedBlankLine: "Expected blank line before this statement."
}
},
defaultOptions: [],
create(context) {
const sourceCode = context.sourceCode;
const configureList = context.options || [];
let scopeInfo = null;
function enterScope() {
scopeInfo = {
upper: scopeInfo,
prevNode: null
};
}
function exitScope() {
if (scopeInfo) scopeInfo = scopeInfo.upper;
}
function match(node, type) {
let innerStatementNode = node;
while (innerStatementNode.type === AST_NODE_TYPES.LabeledStatement) innerStatementNode = innerStatementNode.body;
if (Array.isArray(type)) return type.some(match.bind(null, innerStatementNode));
return StatementTypes[type].test(innerStatementNode, sourceCode);
}
function getPaddingType(prevNode, nextNode) {
for (let i = configureList.length - 1; i >= 0; --i) {
const configure = configureList[i];
if (match(prevNode, configure.prev) && match(nextNode, configure.next)) return PaddingTypes[configure.blankLine];
}
return PaddingTypes.any;
}
function getPaddingLineSequences(prevNode, nextNode) {
const pairs = [];
let prevToken = getActualLastToken(prevNode, sourceCode);
if (nextNode.loc.start.line - prevToken.loc.end.line >= 2) do {
const token = sourceCode.getTokenAfter(prevToken, { includeComments: true });
if (token.loc.start.line - prevToken.loc.end.line >= 2) pairs.push([prevToken, token]);
prevToken = token;
} while (prevToken.range[0] < nextNode.range[0]);
return pairs;
}
function verify(node) {
if (!node.parent || ![
AST_NODE_TYPES.BlockStatement,
AST_NODE_TYPES.Program,
AST_NODE_TYPES.StaticBlock,
AST_NODE_TYPES.SwitchCase,
AST_NODE_TYPES.SwitchStatement,
AST_NODE_TYPES.TSInterfaceBody,
AST_NODE_TYPES.TSModuleBlock,
AST_NODE_TYPES.TSTypeLiteral
].includes(node.parent.type)) return;
const prevNode = scopeInfo.prevNode;
if (prevNode) {
const type = getPaddingType(prevNode, node);
const paddingLines = getPaddingLineSequences(prevNode, node);
type.verify(context, prevNode, node, paddingLines);
}
scopeInfo.prevNode = node;
}
function verifyThenEnterScope(node) {
verify(node);
enterScope();
}
return {
"Program": enterScope,
"Program:exit": exitScope,
"BlockStatement": enterScope,
"BlockStatement:exit": exitScope,
"SwitchStatement": enterScope,
"SwitchStatement:exit": exitScope,
"SwitchCase": verifyThenEnterScope,
"SwitchCase:exit": exitScope,
"StaticBlock": enterScope,
"StaticBlock:exit": exitScope,
"TSInterfaceBody": enterScope,
"TSInterfaceBody:exit": exitScope,
"TSModuleBlock": enterScope,
"TSModuleBlock:exit": exitScope,
"TSTypeLiteral": enterScope,
"TSTypeLiteral:exit": exitScope,
"TSDeclareFunction": verifyThenEnterScope,
"TSDeclareFunction:exit": exitScope,
"TSMethodSignature": verifyThenEnterScope,
"TSMethodSignature:exit": exitScope,
":statement": verify
};
}
});
export { padding_line_between_statements_default };

View File

@@ -0,0 +1,231 @@
import { ES3_KEYWORDS, createRule, isNumericLiteral, isStringLiteral } from "../utils.js";
import { tokenize } from "espree";
var quote_props_default = createRule({
name: "quote-props",
meta: {
type: "layout",
docs: { description: "Require quotes around object literal, type literal, interfaces and enums property names" },
fixable: "code",
schema: { anyOf: [{
type: "array",
items: [{
type: "string",
enum: [
"always",
"as-needed",
"consistent",
"consistent-as-needed"
]
}],
minItems: 0,
maxItems: 1
}, {
type: "array",
items: [{
type: "string",
enum: [
"always",
"as-needed",
"consistent",
"consistent-as-needed"
]
}, {
type: "object",
properties: {
keywords: { type: "boolean" },
unnecessary: { type: "boolean" },
numbers: { type: "boolean" }
},
additionalProperties: false
}],
minItems: 0,
maxItems: 2
}] },
messages: {
requireQuotesDueToReservedWord: "Properties should be quoted as '{{property}}' is a reserved word.",
inconsistentlyQuotedProperty: "Inconsistently quoted property '{{key}}' found.",
unnecessarilyQuotedProperty: "Unnecessarily quoted property '{{property}}' found.",
unquotedReservedProperty: "Unquoted reserved word '{{property}}' used as key.",
unquotedNumericProperty: "Unquoted number literal '{{property}}' used as key.",
unquotedPropertyFound: "Unquoted property '{{property}}' found.",
redundantQuoting: "Properties shouldn't be quoted as all quotes are redundant."
}
},
defaultOptions: ["always"],
create(context) {
const MODE = context.options[0];
const KEYWORDS = context.options[1] && context.options[1].keywords;
const CHECK_UNNECESSARY = !context.options[1] || context.options[1].unnecessary !== false;
const NUMBERS = context.options[1] && context.options[1].numbers;
const sourceCode = context.sourceCode;
function isKeyword(tokenStr) {
return ES3_KEYWORDS.includes(tokenStr);
}
function areQuotesRedundant(rawKey, tokens, skipNumberLiterals = false) {
return tokens.length === 1 && tokens[0].start === 0 && tokens[0].end === rawKey.length && ([
"Identifier",
"Keyword",
"Null",
"Boolean"
].includes(tokens[0].type) || tokens[0].type === "Numeric" && !skipNumberLiterals && String(+tokens[0].value) === tokens[0].value);
}
function getUnquotedKey(key) {
return key.type === "Identifier" ? key.name : key.value;
}
function getQuotedKey(key) {
if (isStringLiteral(key)) return sourceCode.getText(key);
return `"${key.type === "Identifier" ? key.name : key.value}"`;
}
function checkUnnecessaryQuotes(node) {
if (node.type === "Property" && (node.method || node.computed || node.shorthand)) return;
if (node.type !== "ImportAttribute" && node.computed) return;
const key = node.type === "TSEnumMember" ? node.id : node.key;
if (key.type === "Literal" && typeof key.value === "string") {
let tokens;
try {
tokens = tokenize(key.value);
} catch {
return;
}
if (tokens.length !== 1) return;
const isKeywordToken = isKeyword(tokens[0].value);
if (isKeywordToken && KEYWORDS) return;
if (CHECK_UNNECESSARY && areQuotesRedundant(key.value, tokens, NUMBERS)) context.report({
node,
messageId: "unnecessarilyQuotedProperty",
data: { property: key.value },
fix: (fixer) => fixer.replaceText(key, getUnquotedKey(key))
});
} else if (KEYWORDS && key.type === "Identifier" && isKeyword(key.name)) context.report({
node,
messageId: "unquotedReservedProperty",
data: { property: key.name },
fix: (fixer) => fixer.replaceText(key, getQuotedKey(key))
});
else if (NUMBERS && isNumericLiteral(key)) context.report({
node,
messageId: "unquotedNumericProperty",
data: { property: key.value },
fix: (fixer) => fixer.replaceText(key, getQuotedKey(key))
});
}
function checkOmittedQuotes(node) {
if (node.type === "Property" && (node.method || node.computed || node.shorthand)) return;
if (node.type !== "ImportAttribute" && node.computed) return;
const key = node.type === "TSEnumMember" ? node.id : node.key;
if (key.type === "Literal" && typeof key.value === "string") return;
context.report({
node,
messageId: "unquotedPropertyFound",
data: { property: key.name || key.value },
fix: (fixer) => fixer.replaceText(key, getQuotedKey(key))
});
}
function checkConsistencyForObject(properties, checkQuotesRedundancy) {
checkConsistency(properties.filter((property) => property.type !== "SpreadElement" && property.key && !property.method && !property.computed && !property.shorthand), checkQuotesRedundancy);
}
function checkImportAttributes(attributes) {
if (!attributes) return;
if (MODE === "consistent") checkConsistency(attributes, false);
if (MODE === "consistent-as-needed") checkConsistency(attributes, true);
}
function checkConsistency(properties, checkQuotesRedundancy) {
const quotedProps = [];
const unquotedProps = [];
let keywordKeyName = null;
let necessaryQuotes = false;
properties.forEach((property) => {
const key = property.key;
if (key.type === "Literal" && typeof key.value === "string") {
quotedProps.push(property);
if (checkQuotesRedundancy) {
let tokens;
try {
tokens = tokenize(key.value);
} catch {
necessaryQuotes = true;
return;
}
necessaryQuotes = necessaryQuotes || !areQuotesRedundant(key.value, tokens) || KEYWORDS && isKeyword(tokens[0].value);
}
} else if (KEYWORDS && checkQuotesRedundancy && key.type === "Identifier" && isKeyword(key.name)) {
unquotedProps.push(property);
necessaryQuotes = true;
keywordKeyName = key.name;
} else unquotedProps.push(property);
});
if (checkQuotesRedundancy && quotedProps.length && !necessaryQuotes) quotedProps.forEach((property) => {
const key = property.key;
context.report({
node: property,
messageId: "redundantQuoting",
fix: (fixer) => fixer.replaceText(key, getUnquotedKey(key))
});
});
else if (unquotedProps.length && keywordKeyName) unquotedProps.forEach((property) => {
context.report({
node: property,
messageId: "requireQuotesDueToReservedWord",
data: { property: keywordKeyName },
fix: (fixer) => fixer.replaceText(property.key, getQuotedKey(property.key))
});
});
else if (quotedProps.length && unquotedProps.length) unquotedProps.forEach((property) => {
context.report({
node: property,
messageId: "inconsistentlyQuotedProperty",
data: { key: property.key.name || property.key.value },
fix: (fixer) => fixer.replaceText(property.key, getQuotedKey(property.key))
});
});
}
return {
Property(node) {
if (MODE === "always" || !MODE) checkOmittedQuotes(node);
if (MODE === "as-needed") checkUnnecessaryQuotes(node);
},
ObjectExpression(node) {
if (MODE === "consistent") checkConsistencyForObject(node.properties, false);
if (MODE === "consistent-as-needed") checkConsistencyForObject(node.properties, true);
},
ImportAttribute(node) {
if (MODE === "always" || !MODE) checkOmittedQuotes(node);
if (MODE === "as-needed") checkUnnecessaryQuotes(node);
},
ImportDeclaration(node) {
checkImportAttributes(node.attributes);
},
ExportAllDeclaration(node) {
checkImportAttributes(node.attributes);
},
ExportNamedDeclaration(node) {
checkImportAttributes(node.attributes);
},
TSPropertySignature(node) {
if (MODE === "always" || !MODE) checkOmittedQuotes(node);
if (MODE === "as-needed") checkUnnecessaryQuotes(node);
},
TSEnumMember(node) {
if (MODE === "always" || !MODE) checkOmittedQuotes(node);
if (MODE === "as-needed") checkUnnecessaryQuotes(node);
},
TSTypeLiteral(node) {
if (MODE === "consistent") checkConsistencyForObject(node.members, false);
if (MODE === "consistent-as-needed") checkConsistencyForObject(node.members, true);
},
TSInterfaceBody(node) {
if (MODE === "consistent") checkConsistencyForObject(node.body, false);
if (MODE === "consistent-as-needed") checkConsistencyForObject(node.body, true);
},
TSEnumDeclaration(node) {
const members = (node.body?.members || node.members).map((member) => ({
...member,
key: member.id
}));
if (MODE === "consistent") checkConsistencyForObject(members, false);
if (MODE === "consistent-as-needed") checkConsistencyForObject(members, true);
}
};
}
});
export { quote_props_default };

View File

@@ -0,0 +1,188 @@
import { AST_NODE_TYPES, LINEBREAKS, createRule, hasOctalOrNonOctalDecimalEscapeSequence, isParenthesised, isSurroundedBy, isTopLevelExpressionStatement, warnDeprecation } from "../utils.js";
function switchQuote(str) {
const newQuote = this.quote;
const oldQuote = str[0];
if (newQuote === oldQuote) return str;
return newQuote + str.slice(1, -1).replace(/\\(\$\{|\r\n?|\n|.)|["'`]|\$\{|(\r\n?|\n)/gu, (match, escaped, newline) => {
if (escaped === oldQuote || oldQuote === "`" && escaped === "${") return escaped;
if (match === newQuote || newQuote === "`" && match === "${") return `\\${match}`;
if (newline && oldQuote === "`") return "\\n";
return match;
}) + newQuote;
}
const QUOTE_SETTINGS = {
double: {
quote: "\"",
alternateQuote: "'",
description: "doublequote",
convert: switchQuote
},
single: {
quote: "'",
alternateQuote: "\"",
description: "singlequote",
convert: switchQuote
},
backtick: {
quote: "`",
alternateQuote: "\"",
description: "backtick",
convert: switchQuote
}
};
const UNESCAPED_LINEBREAK_PATTERN = new RegExp(String.raw`(^|[^\\])(\\\\)*[${Array.from(LINEBREAKS).join("")}]`, "u");
const AVOID_ESCAPE = "avoid-escape";
var quotes_default = createRule({
name: "quotes",
meta: {
type: "layout",
docs: { description: "Enforce the consistent use of either backticks, double, or single quotes" },
fixable: "code",
schema: [{
type: "string",
enum: [
"single",
"double",
"backtick"
]
}, { anyOf: [{
type: "string",
enum: ["avoid-escape"]
}, {
type: "object",
properties: {
avoidEscape: { type: "boolean" },
allowTemplateLiterals: { anyOf: [{ type: "boolean" }, {
type: "string",
enum: [
"never",
"avoidEscape",
"always"
]
}] },
ignoreStringLiterals: { type: "boolean" }
},
additionalProperties: false
}] }],
messages: { wrongQuotes: "Strings must use {{description}}." }
},
defaultOptions: ["double", {
allowTemplateLiterals: "never",
avoidEscape: false,
ignoreStringLiterals: false
}],
create(context) {
const quoteOption = context.options[0];
const settings = QUOTE_SETTINGS[quoteOption || "double"];
const options = context.options[1];
const sourceCode = context.sourceCode;
let avoidEscape = false;
let ignoreStringLiterals = false;
let allowTemplateLiteralsAlways = false;
let allowTemplateLiteralsToAvoidEscape = false;
if (typeof options === "object") {
avoidEscape = options.avoidEscape === true;
ignoreStringLiterals = options.ignoreStringLiterals === true;
if (typeof options.allowTemplateLiterals === "string") {
allowTemplateLiteralsAlways = options.allowTemplateLiterals === "always";
allowTemplateLiteralsToAvoidEscape = allowTemplateLiteralsAlways || options.allowTemplateLiterals === "avoidEscape";
} else if (typeof options.allowTemplateLiterals === "boolean") {
warnDeprecation("value(boolean) for \"allowTemplateLiterals\"", "\"always\"/\"never\"", "quotes");
allowTemplateLiteralsAlways = options.allowTemplateLiterals === true;
allowTemplateLiteralsToAvoidEscape = options.allowTemplateLiterals === true;
}
} else if (options === AVOID_ESCAPE) {
warnDeprecation(`option("${AVOID_ESCAPE}")`, "\"avoidEscape\"", "quotes");
avoidEscape = true;
}
/* v8 ignore stop */
function isJSXLiteral(node) {
if (!node.parent) return false;
return node.parent.type === "JSXAttribute" || node.parent.type === "JSXElement" || node.parent.type === "JSXFragment";
}
function isDirective(node) {
return node.type === "ExpressionStatement" && node.expression.type === "Literal" && typeof node.expression.value === "string" && !isParenthesised(sourceCode, node.expression);
}
function isExpressionInOrJustAfterDirectivePrologue(node) {
if (!node.parent) return false;
if (!isTopLevelExpressionStatement(node.parent)) return false;
const block = node.parent.parent;
if (!block || !("body" in block) || !Array.isArray(block.body)) return false;
for (let i = 0; i < block.body.length; ++i) {
const statement = block.body[i];
if (statement === node.parent) return true;
if (!isDirective(statement)) break;
}
return false;
}
function isAllowedAsNonBacktick(node) {
const parent = node.parent;
if (!parent) return false;
switch (parent.type) {
case AST_NODE_TYPES.ExpressionStatement: return !isParenthesised(sourceCode, node) && isExpressionInOrJustAfterDirectivePrologue(node);
case AST_NODE_TYPES.Property:
case AST_NODE_TYPES.MethodDefinition: return parent.key === node && !parent.computed;
case AST_NODE_TYPES.ImportDeclaration:
case AST_NODE_TYPES.ExportNamedDeclaration: return parent.source === node;
case AST_NODE_TYPES.ExportAllDeclaration: return parent.exported === node || parent.source === node;
case AST_NODE_TYPES.ImportSpecifier: return parent.imported === node;
case AST_NODE_TYPES.ExportSpecifier: return parent.local === node || parent.exported === node;
case AST_NODE_TYPES.ImportAttribute: return parent.value === node;
case AST_NODE_TYPES.TSAbstractMethodDefinition:
case AST_NODE_TYPES.TSMethodSignature:
case AST_NODE_TYPES.TSPropertySignature:
case AST_NODE_TYPES.TSModuleDeclaration:
case AST_NODE_TYPES.TSExternalModuleReference: return true;
case AST_NODE_TYPES.TSEnumMember: return node === parent.id;
case AST_NODE_TYPES.TSAbstractPropertyDefinition:
case AST_NODE_TYPES.PropertyDefinition:
case AST_NODE_TYPES.AccessorProperty: return parent.key === node && !parent.computed;
case AST_NODE_TYPES.TSLiteralType: return parent.parent?.type === AST_NODE_TYPES.TSImportType;
default: return false;
}
}
function isUsingFeatureOfTemplateLiteral(node) {
const hasTag = node.parent.type === "TaggedTemplateExpression" && node === node.parent.quasi;
if (hasTag) return true;
const hasStringInterpolation = node.expressions.length > 0;
if (hasStringInterpolation) return true;
const isMultilineString = node.quasis.length >= 1 && UNESCAPED_LINEBREAK_PATTERN.test(node.quasis[0].value.raw);
if (isMultilineString) return true;
return false;
}
return {
Literal(node) {
if (ignoreStringLiterals) return;
const val = node.value;
const rawVal = node.raw;
if (settings && typeof val === "string") {
let isValid = quoteOption === "backtick" && isAllowedAsNonBacktick(node) || isJSXLiteral(node) || isSurroundedBy(rawVal, settings.quote);
if (!isValid && avoidEscape) isValid = isSurroundedBy(rawVal, settings.alternateQuote) && rawVal.includes(settings.quote);
if (!isValid) context.report({
node,
messageId: "wrongQuotes",
data: { description: settings.description },
fix(fixer) {
if (quoteOption === "backtick" && hasOctalOrNonOctalDecimalEscapeSequence(rawVal)) return null;
return fixer.replaceText(node, settings.convert(node.raw));
}
});
}
},
TemplateLiteral(node) {
if (allowTemplateLiteralsAlways || quoteOption === "backtick" || isUsingFeatureOfTemplateLiteral(node)) return;
if (allowTemplateLiteralsToAvoidEscape && avoidEscape && sourceCode.getText(node).includes(settings.quote)) return;
context.report({
node,
messageId: "wrongQuotes",
data: { description: settings.description },
fix(fixer) {
if (isTopLevelExpressionStatement(node.parent) && !isParenthesised(sourceCode, node)) return null;
return fixer.replaceText(node, settings.convert(sourceCode.getText(node)));
}
});
}
};
}
});
export { quotes_default };

View File

@@ -0,0 +1,64 @@
import { createRule } from "../utils.js";
var rest_spread_spacing_default = createRule({
name: "rest-spread-spacing",
meta: {
type: "layout",
docs: { description: "Enforce spacing between rest and spread operators and their expressions" },
fixable: "whitespace",
schema: [{
type: "string",
enum: ["always", "never"]
}],
messages: {
unexpectedWhitespace: "Unexpected whitespace after {{type}} operator.",
expectedWhitespace: "Expected whitespace after {{type}} operator."
}
},
create(context) {
const sourceCode = context.sourceCode;
const alwaysSpace = context.options[0] === "always";
function checkWhiteSpace(node) {
const operator = sourceCode.getFirstToken(node);
const nextToken = sourceCode.getTokenAfter(operator);
const hasWhitespace = sourceCode.isSpaceBetween(operator, nextToken);
let type;
switch (node.type) {
case "SpreadElement":
type = "spread";
if (node.parent.type === "ObjectExpression") type += " property";
break;
case "RestElement":
type = "rest";
if (node.parent.type === "ObjectPattern") type += " property";
break;
default: return;
}
if (alwaysSpace && !hasWhitespace) context.report({
node,
loc: operator.loc,
messageId: "expectedWhitespace",
data: { type },
fix(fixer) {
return fixer.replaceTextRange([operator.range[1], nextToken.range[0]], " ");
}
});
else if (!alwaysSpace && hasWhitespace) context.report({
node,
loc: {
start: operator.loc.end,
end: nextToken.loc.start
},
messageId: "unexpectedWhitespace",
data: { type },
fix(fixer) {
return fixer.removeRange([operator.range[1], nextToken.range[0]]);
}
});
}
return {
SpreadElement: checkWhiteSpace,
RestElement: checkWhiteSpace
};
}
});
export { rest_spread_spacing_default };

View File

@@ -0,0 +1,150 @@
import { AST_NODE_TYPES, ast_exports, createRule } from "../utils.js";
var semi_spacing_default = createRule({
name: "semi-spacing",
meta: {
type: "layout",
docs: { description: "Enforce consistent spacing before and after semicolons" },
fixable: "whitespace",
schema: [{
type: "object",
properties: {
before: {
type: "boolean",
default: false
},
after: {
type: "boolean",
default: true
}
},
additionalProperties: false
}],
messages: {
unexpectedWhitespaceBefore: "Unexpected whitespace before semicolon.",
unexpectedWhitespaceAfter: "Unexpected whitespace after semicolon.",
missingWhitespaceBefore: "Missing whitespace before semicolon.",
missingWhitespaceAfter: "Missing whitespace after semicolon."
}
},
create(context) {
const config = context.options[0];
const sourceCode = context.sourceCode;
let requireSpaceBefore = false;
let requireSpaceAfter = true;
if (typeof config === "object") {
requireSpaceBefore = config.before;
requireSpaceAfter = config.after;
}
function hasLeadingSpace(token) {
const tokenBefore = sourceCode.getTokenBefore(token);
return tokenBefore && (0, ast_exports.isTokenOnSameLine)(tokenBefore, token) && sourceCode.isSpaceBetween(tokenBefore, token);
}
function hasTrailingSpace(token) {
const tokenAfter = sourceCode.getTokenAfter(token);
return tokenAfter && (0, ast_exports.isTokenOnSameLine)(token, tokenAfter) && sourceCode.isSpaceBetween(token, tokenAfter);
}
function isLastTokenInCurrentLine(token) {
const tokenAfter = sourceCode.getTokenAfter(token);
return !(tokenAfter && (0, ast_exports.isTokenOnSameLine)(token, tokenAfter));
}
function isFirstTokenInCurrentLine(token) {
const tokenBefore = sourceCode.getTokenBefore(token);
return !(tokenBefore && (0, ast_exports.isTokenOnSameLine)(token, tokenBefore));
}
function isBeforeClosingParen(token) {
const nextToken = sourceCode.getTokenAfter(token);
return nextToken && (0, ast_exports.isClosingBraceToken)(nextToken) || (0, ast_exports.isClosingParenToken)(nextToken);
}
function checkSemicolonSpacing(token, node) {
if ((0, ast_exports.isSemicolonToken)(token)) {
if (hasLeadingSpace(token)) {
if (!requireSpaceBefore) {
const tokenBefore = sourceCode.getTokenBefore(token);
const loc = {
start: tokenBefore.loc.end,
end: token.loc.start
};
context.report({
node,
loc,
messageId: "unexpectedWhitespaceBefore",
fix(fixer) {
return fixer.removeRange([tokenBefore.range[1], token.range[0]]);
}
});
}
} else if (requireSpaceBefore) {
const loc = token.loc;
context.report({
node,
loc,
messageId: "missingWhitespaceBefore",
fix(fixer) {
return fixer.insertTextBefore(token, " ");
}
});
}
if (!isFirstTokenInCurrentLine(token) && !isLastTokenInCurrentLine(token) && !isBeforeClosingParen(token)) {
if (hasTrailingSpace(token)) {
if (!requireSpaceAfter) {
const tokenAfter = sourceCode.getTokenAfter(token);
const loc = {
start: token.loc.end,
end: tokenAfter.loc.start
};
context.report({
node,
loc,
messageId: "unexpectedWhitespaceAfter",
fix(fixer) {
return fixer.removeRange([token.range[1], tokenAfter.range[0]]);
}
});
}
} else if (requireSpaceAfter) {
const loc = token.loc;
context.report({
node,
loc,
messageId: "missingWhitespaceAfter",
fix(fixer) {
return fixer.insertTextAfter(token, " ");
}
});
}
}
}
}
function checkNode(node) {
const token = sourceCode.getLastToken(node);
checkSemicolonSpacing(token, node);
}
return {
VariableDeclaration: checkNode,
ExpressionStatement: checkNode,
BreakStatement: checkNode,
ContinueStatement: checkNode,
DebuggerStatement: checkNode,
DoWhileStatement: checkNode,
ReturnStatement: checkNode,
ThrowStatement: checkNode,
ImportDeclaration: checkNode,
ExportNamedDeclaration: checkNode,
ExportAllDeclaration: checkNode,
ExportDefaultDeclaration: checkNode,
ForStatement(node) {
if (node.init) checkSemicolonSpacing(sourceCode.getTokenAfter(node.init), node);
if (node.test) checkSemicolonSpacing(sourceCode.getTokenAfter(node.test), node);
},
PropertyDefinition: checkNode,
AccessorProperty: checkNode,
TSDeclareFunction: checkNode,
TSTypeAliasDeclaration: checkNode,
TSTypeAnnotation(node) {
const excludeNodeTypes = new Set([AST_NODE_TYPES.TSDeclareFunction]);
if (node.parent && !excludeNodeTypes.has(node.parent.type)) checkNode(node.parent);
}
};
}
});
export { semi_spacing_default };

View File

@@ -0,0 +1,80 @@
import { ast_exports, createRule } from "../utils.js";
const SELECTOR = [
"BreakStatement",
"ContinueStatement",
"DebuggerStatement",
"DoWhileStatement",
"ExportAllDeclaration",
"ExportDefaultDeclaration",
"ExportNamedDeclaration",
"ExpressionStatement",
"ImportDeclaration",
"ReturnStatement",
"ThrowStatement",
"VariableDeclaration",
"PropertyDefinition",
"AccessorProperty"
].join(",");
function getChildren(node) {
const t = node.type;
if (t === "BlockStatement" || t === "StaticBlock" || t === "Program" || t === "ClassBody") return node.body;
if (t === "SwitchCase") return node.consequent;
return null;
}
function isLastChild(node) {
if (!node.parent) return true;
const t = node.parent.type;
if (t === "IfStatement" && node.parent.consequent === node && node.parent.alternate) return true;
if (t === "DoWhileStatement") return true;
const nodeList = getChildren(node.parent);
return nodeList !== null && nodeList[nodeList.length - 1] === node;
}
var semi_style_default = createRule({
name: "semi-style",
meta: {
type: "layout",
docs: { description: "Enforce location of semicolons" },
schema: [{
type: "string",
enum: ["last", "first"]
}],
fixable: "whitespace",
messages: { expectedSemiColon: "Expected this semicolon to be at {{pos}}." }
},
create(context) {
const sourceCode = context.sourceCode;
const option = context.options[0] || "last";
function check(semiToken, expected) {
const prevToken = sourceCode.getTokenBefore(semiToken);
const nextToken = sourceCode.getTokenAfter(semiToken);
const prevIsSameLine = !prevToken || (0, ast_exports.isTokenOnSameLine)(prevToken, semiToken);
const nextIsSameLine = !nextToken || (0, ast_exports.isTokenOnSameLine)(semiToken, nextToken);
if (expected === "last" && !prevIsSameLine || expected === "first" && !nextIsSameLine) context.report({
loc: semiToken.loc,
messageId: "expectedSemiColon",
data: { pos: expected === "last" ? "the end of the previous line" : "the beginning of the next line" },
fix(fixer) {
if (prevToken && nextToken && sourceCode.commentsExistBetween(prevToken, nextToken)) return null;
const start = prevToken ? prevToken.range[1] : semiToken.range[0];
const end = nextToken ? nextToken.range[0] : semiToken.range[1];
const text = expected === "last" ? ";\n" : "\n;";
return fixer.replaceTextRange([start, end], text);
}
});
}
return {
[SELECTOR](node) {
if (option === "first" && isLastChild(node)) return;
const lastToken = sourceCode.getLastToken(node);
if ((0, ast_exports.isSemicolonToken)(lastToken)) check(lastToken, option);
},
ForStatement(node) {
const firstSemi = node.init && sourceCode.getTokenAfter(node.init, ast_exports.isSemicolonToken);
const secondSemi = node.test && sourceCode.getTokenAfter(node.test, ast_exports.isSemicolonToken);
if (firstSemi) check(firstSemi, "last");
if (secondSemi) check(secondSemi, "last");
}
};
}
});
export { semi_style_default };

View File

@@ -0,0 +1,207 @@
import { AST_NODE_TYPES, FixTracker, ast_exports, createRule, getNextLocation, isSingleLine } from "../utils.js";
var semi_default = createRule({
name: "semi",
meta: {
type: "layout",
docs: { description: "Require or disallow semicolons instead of ASI" },
fixable: "code",
schema: { anyOf: [{
type: "array",
items: [{
type: "string",
enum: ["never"]
}, {
type: "object",
properties: { beforeStatementContinuationChars: {
type: "string",
enum: [
"always",
"any",
"never"
]
} },
additionalProperties: false
}],
minItems: 0,
maxItems: 2
}, {
type: "array",
items: [{
type: "string",
enum: ["always"]
}, {
type: "object",
properties: {
omitLastInOneLineBlock: { type: "boolean" },
omitLastInOneLineClassBody: { type: "boolean" }
},
additionalProperties: false
}],
minItems: 0,
maxItems: 2
}] },
messages: {
missingSemi: "Missing semicolon.",
extraSemi: "Extra semicolon."
}
},
defaultOptions: ["always", {
omitLastInOneLineBlock: false,
beforeStatementContinuationChars: "any"
}],
create(context) {
const OPT_OUT_PATTERN = /^[-[(/+`]/u;
const unsafeClassFieldNames = new Set([
"get",
"set",
"static"
]);
const unsafeClassFieldFollowers = new Set([
"*",
"in",
"instanceof"
]);
const options = context.options[1];
const never = context.options[0] === "never";
const exceptOneLine = Boolean(options && "omitLastInOneLineBlock" in options && options.omitLastInOneLineBlock);
const exceptOneLineClassBody = Boolean(options && "omitLastInOneLineClassBody" in options && options.omitLastInOneLineClassBody);
const beforeStatementContinuationChars = options && "beforeStatementContinuationChars" in options && options.beforeStatementContinuationChars || "any";
const sourceCode = context.sourceCode;
function report(node, missing = false) {
const lastToken = sourceCode.getLastToken(node);
let messageId = "missingSemi";
let fix, loc;
if (!missing) {
loc = {
start: lastToken.loc.end,
end: getNextLocation(sourceCode, lastToken.loc.end)
};
fix = function(fixer) {
return fixer.insertTextAfter(lastToken, ";");
};
} else {
messageId = "extraSemi";
loc = lastToken.loc;
fix = function(fixer) {
return new FixTracker(fixer, sourceCode).retainSurroundingTokens(lastToken).remove(lastToken);
};
}
context.report({
node,
loc,
messageId,
fix
});
}
function isRedundantSemi(semiToken) {
const nextToken = sourceCode.getTokenAfter(semiToken);
return !nextToken || (0, ast_exports.isClosingBraceToken)(nextToken) || (0, ast_exports.isSemicolonToken)(nextToken);
}
function isEndOfArrowBlock(lastToken) {
if (!(0, ast_exports.isClosingBraceToken)(lastToken)) return false;
const node = sourceCode.getNodeByRangeIndex(lastToken.range[0]);
return node.type === "BlockStatement" && node.parent.type === "ArrowFunctionExpression";
}
function maybeClassFieldAsiHazard(node) {
if (node.type !== "PropertyDefinition") return false;
const needsNameCheck = !node.computed && node.key.type === "Identifier";
if (needsNameCheck && "name" in node.key && unsafeClassFieldNames.has(node.key.name)) {
const isStaticStatic = node.static && node.key.name === "static";
if (!isStaticStatic && !node.value) return true;
}
const followingToken = sourceCode.getTokenAfter(node);
return unsafeClassFieldFollowers.has(followingToken.value);
}
function isOnSameLineWithNextToken(node) {
const prevToken = sourceCode.getLastToken(node, 1);
const nextToken = sourceCode.getTokenAfter(node);
return !!nextToken && (0, ast_exports.isTokenOnSameLine)(prevToken, nextToken);
}
function maybeAsiHazardAfter(node) {
const t = node.type;
if (t === "DoWhileStatement" || t === "BreakStatement" || t === "ContinueStatement" || t === "DebuggerStatement" || t === "ImportDeclaration" || t === "ExportAllDeclaration") return false;
if (t === "ReturnStatement") return Boolean(node.argument);
if (t === "ExportNamedDeclaration") return Boolean(node.declaration);
const lastToken = sourceCode.getLastToken(node, 1);
if (isEndOfArrowBlock(lastToken)) return false;
return true;
}
function maybeAsiHazardBefore(token) {
return Boolean(token) && OPT_OUT_PATTERN.test(token.value) && token.value !== "++" && token.value !== "--";
}
function canRemoveSemicolon(node) {
const lastToken = sourceCode.getLastToken(node);
if (isRedundantSemi(lastToken)) return true;
if (maybeClassFieldAsiHazard(node)) return false;
if (isOnSameLineWithNextToken(node)) return false;
if (node.type !== "PropertyDefinition" && beforeStatementContinuationChars === "never" && !maybeAsiHazardAfter(node)) return true;
const nextToken = sourceCode.getTokenAfter(node);
if (!maybeAsiHazardBefore(nextToken)) return true;
return false;
}
function isLastInOneLinerBlock(node) {
const parent = node.parent;
const nextToken = sourceCode.getTokenAfter(node);
if (!nextToken || nextToken.value !== "}") return false;
if (parent.type === "BlockStatement") return isSingleLine(parent);
if (parent.type === "StaticBlock") {
const openingBrace = sourceCode.getFirstToken(parent, { skip: 1 });
return (0, ast_exports.isTokenOnSameLine)(parent, openingBrace);
}
return false;
}
function isLastInOneLinerClassBody(node) {
const parent = node.parent;
const nextToken = sourceCode.getTokenAfter(node);
if (!nextToken || nextToken.value !== "}") return false;
if (parent.type === "ClassBody") return isSingleLine(parent);
return false;
}
function checkForSemicolon(node) {
const lastToken = sourceCode.getLastToken(node);
const isSemi = (0, ast_exports.isSemicolonToken)(lastToken);
if (never) {
const nextToken = sourceCode.getTokenAfter(node);
if (isSemi && canRemoveSemicolon(node)) report(node, true);
else if (!isSemi && beforeStatementContinuationChars === "always" && node.type !== "PropertyDefinition" && maybeAsiHazardBefore(nextToken)) report(node);
} else {
const oneLinerBlock = exceptOneLine && isLastInOneLinerBlock(node);
const oneLinerClassBody = exceptOneLineClassBody && isLastInOneLinerClassBody(node);
const oneLinerBlockOrClassBody = oneLinerBlock || oneLinerClassBody;
if (isSemi && oneLinerBlockOrClassBody) report(node, true);
else if (!isSemi && !oneLinerBlockOrClassBody) report(node);
}
}
return {
VariableDeclaration(node) {
const parent = node.parent;
if ((parent.type !== "ForStatement" || parent.init !== node) && (!/^For(?:In|Of)Statement/u.test(parent.type) || parent.left !== node)) checkForSemicolon(node);
},
ExpressionStatement: checkForSemicolon,
ReturnStatement: checkForSemicolon,
ThrowStatement: checkForSemicolon,
DoWhileStatement: checkForSemicolon,
DebuggerStatement: checkForSemicolon,
BreakStatement: checkForSemicolon,
ContinueStatement: checkForSemicolon,
ImportDeclaration: checkForSemicolon,
ExportAllDeclaration: checkForSemicolon,
ExportNamedDeclaration(node) {
if (!node.declaration) checkForSemicolon(node);
},
ExportDefaultDeclaration(node) {
if (node.declaration.type === AST_NODE_TYPES.TSInterfaceDeclaration) return;
if (!/(?:Class|Function)Declaration/u.test(node.declaration.type)) checkForSemicolon(node);
},
PropertyDefinition: checkForSemicolon,
AccessorProperty: checkForSemicolon,
TSAbstractPropertyDefinition: checkForSemicolon,
TSDeclareFunction: checkForSemicolon,
TSExportAssignment: checkForSemicolon,
TSImportEqualsDeclaration: checkForSemicolon,
TSTypeAliasDeclaration: checkForSemicolon,
TSEmptyBodyFunctionExpression: checkForSemicolon
};
}
});
export { semi_default };

View File

@@ -0,0 +1,143 @@
import { ast_exports, createRule, getSwitchCaseColonToken, isKeywordToken } from "../utils.js";
var space_before_blocks_default = createRule({
name: "space-before-blocks",
meta: {
type: "layout",
docs: { description: "Enforce consistent spacing before blocks" },
fixable: "whitespace",
schema: [{ oneOf: [{
type: "string",
enum: ["always", "never"]
}, {
type: "object",
properties: {
keywords: {
type: "string",
enum: [
"always",
"never",
"off"
]
},
functions: {
type: "string",
enum: [
"always",
"never",
"off"
]
},
classes: {
type: "string",
enum: [
"always",
"never",
"off"
]
},
modules: {
type: "string",
enum: [
"always",
"never",
"off"
]
}
},
additionalProperties: false
}] }],
messages: {
unexpectedSpace: "Unexpected space before opening brace.",
missingSpace: "Missing space before opening brace."
}
},
defaultOptions: ["always"],
create(context, [config]) {
const sourceCode = context.sourceCode;
let alwaysFunctions = true;
let alwaysKeywords = true;
let alwaysClasses = true;
let alwaysModules = true;
let neverFunctions = false;
let neverKeywords = false;
let neverClasses = false;
let neverModules = false;
if (typeof config === "object") {
alwaysFunctions = config.functions === "always";
alwaysKeywords = config.keywords === "always";
alwaysClasses = config.classes === "always";
alwaysModules = config.modules === "always";
neverFunctions = config.functions === "never";
neverKeywords = config.keywords === "never";
neverClasses = config.classes === "never";
neverModules = config.modules === "never";
} else if (config === "never") {
alwaysFunctions = false;
alwaysKeywords = false;
alwaysClasses = false;
alwaysModules = false;
neverFunctions = true;
neverKeywords = true;
neverClasses = true;
neverModules = true;
}
function isFunctionBody(node) {
if (!("parent" in node)) return false;
const parent = node.parent;
return node.type === "BlockStatement" && (0, ast_exports.isFunction)(parent) && parent.body === node;
}
function isConflicted(precedingToken, node) {
return (0, ast_exports.isArrowToken)(precedingToken) || isKeywordToken(precedingToken) && !isFunctionBody(node) || (0, ast_exports.isColonToken)(precedingToken) && "parent" in node && node.parent && node.parent.type === "SwitchCase" && precedingToken === getSwitchCaseColonToken(node.parent, sourceCode);
}
function checkPrecedingSpace(node) {
const precedingToken = sourceCode.getTokenBefore(node);
if (precedingToken && !isConflicted(precedingToken, node) && (0, ast_exports.isTokenOnSameLine)(precedingToken, node)) {
const hasSpace = sourceCode.isSpaceBetween(precedingToken, node);
let requireSpace;
let requireNoSpace;
if (isFunctionBody(node)) {
requireSpace = alwaysFunctions;
requireNoSpace = neverFunctions;
} else if (node.type === "ClassBody" || node.type === "TSEnumBody" || node.type === "TSInterfaceBody") {
requireSpace = alwaysClasses;
requireNoSpace = neverClasses;
} else if (node.type === "TSModuleBlock") {
requireSpace = alwaysModules;
requireNoSpace = neverModules;
} else {
requireSpace = alwaysKeywords;
requireNoSpace = neverKeywords;
}
if (requireSpace && !hasSpace) context.report({
node,
messageId: "missingSpace",
fix(fixer) {
return fixer.insertTextBefore(node, " ");
}
});
else if (requireNoSpace && hasSpace) context.report({
node,
messageId: "unexpectedSpace",
fix(fixer) {
return fixer.removeRange([precedingToken.range[1], node.range[0]]);
}
});
}
}
return {
BlockStatement: checkPrecedingSpace,
ClassBody: checkPrecedingSpace,
SwitchStatement(node) {
const cases = node.cases;
let openingBrace;
if (cases.length > 0) openingBrace = sourceCode.getTokenBefore(cases[0]);
else openingBrace = sourceCode.getLastToken(node, 1);
checkPrecedingSpace(openingBrace);
},
TSEnumBody: checkPrecedingSpace,
TSInterfaceBody: checkPrecedingSpace,
TSModuleBlock: checkPrecedingSpace
};
}
});
export { space_before_blocks_default };

View File

@@ -0,0 +1,125 @@
import { AST_NODE_TYPES, ast_exports, createRule } from "../utils.js";
var space_before_function_paren_default = createRule({
name: "space-before-function-paren",
meta: {
type: "layout",
docs: { description: "Enforce consistent spacing before function parenthesis" },
fixable: "whitespace",
schema: [{ oneOf: [{
type: "string",
enum: ["always", "never"]
}, {
type: "object",
properties: {
anonymous: {
type: "string",
enum: [
"always",
"never",
"ignore"
]
},
named: {
type: "string",
enum: [
"always",
"never",
"ignore"
]
},
asyncArrow: {
type: "string",
enum: [
"always",
"never",
"ignore"
]
},
catch: {
type: "string",
enum: [
"always",
"never",
"ignore"
]
}
},
additionalProperties: false
}] }],
messages: {
unexpectedSpace: "Unexpected space before function parentheses.",
missingSpace: "Missing space before function parentheses."
}
},
defaultOptions: ["always"],
create(context, [firstOption]) {
const sourceCode = context.sourceCode;
const baseConfig = typeof firstOption === "string" ? firstOption : "always";
const overrideConfig = typeof firstOption === "object" ? firstOption : {};
function isNamedFunction(node) {
if (node.id != null) return true;
const parent = node.parent;
return parent.type === AST_NODE_TYPES.MethodDefinition || parent.type === AST_NODE_TYPES.TSAbstractMethodDefinition || parent.type === AST_NODE_TYPES.Property && (parent.kind === "get" || parent.kind === "set" || parent.method);
}
function getConfigForFunction(node) {
if (node.type === AST_NODE_TYPES.ArrowFunctionExpression) {
if (node.async && (0, ast_exports.isOpeningParenToken)(sourceCode.getFirstToken(node, { skip: 1 }))) return overrideConfig.asyncArrow ?? baseConfig;
} else if (isNamedFunction(node)) return overrideConfig.named ?? baseConfig;
else if (!node.generator) return overrideConfig.anonymous ?? baseConfig;
return "ignore";
}
function checkFunction(node) {
const functionConfig = getConfigForFunction(node);
if (functionConfig === "ignore") return;
if (functionConfig === "always" && node.typeParameters && !node.id) return;
let leftToken;
let rightToken;
if (node.typeParameters) {
leftToken = sourceCode.getLastToken(node.typeParameters);
rightToken = sourceCode.getTokenAfter(leftToken);
} else {
rightToken = sourceCode.getFirstToken(node, ast_exports.isOpeningParenToken);
leftToken = sourceCode.getTokenBefore(rightToken);
}
checkSpace(node, leftToken, rightToken, functionConfig);
}
function checkSpace(node, leftToken, rightToken, option) {
const hasSpacing = sourceCode.isSpaceBetween(leftToken, rightToken);
if (hasSpacing && option === "never") context.report({
node,
loc: {
start: leftToken.loc.end,
end: rightToken.loc.start
},
messageId: "unexpectedSpace",
fix: (fixer) => {
const comments = sourceCode.getCommentsBefore(rightToken);
if (comments.some((comment) => comment.type === "Line")) return null;
return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], comments.reduce((text, comment) => text + sourceCode.getText(comment), ""));
}
});
else if (!hasSpacing && option === "always") context.report({
node,
loc: rightToken.loc,
messageId: "missingSpace",
fix: (fixer) => fixer.insertTextAfter(leftToken, " ")
});
}
return {
ArrowFunctionExpression: checkFunction,
FunctionDeclaration: checkFunction,
FunctionExpression: checkFunction,
TSEmptyBodyFunctionExpression: checkFunction,
TSDeclareFunction: checkFunction,
CatchClause(node) {
if (!node.param) return;
const option = overrideConfig.catch ?? baseConfig;
if (option === "ignore") return;
const rightToken = sourceCode.getFirstToken(node, ast_exports.isOpeningParenToken);
const leftToken = sourceCode.getTokenBefore(rightToken);
checkSpace(node, leftToken, rightToken, option);
}
};
}
});
export { space_before_function_paren_default };

View File

@@ -0,0 +1,159 @@
import { ast_exports, createRule } from "../utils.js";
var space_in_parens_default = createRule({
name: "space-in-parens",
meta: {
type: "layout",
docs: { description: "Enforce consistent spacing inside parentheses" },
fixable: "whitespace",
schema: [{
type: "string",
enum: ["always", "never"]
}, {
type: "object",
properties: { exceptions: {
type: "array",
items: {
type: "string",
enum: [
"{}",
"[]",
"()",
"empty"
]
},
uniqueItems: true
} },
additionalProperties: false
}],
messages: {
missingOpeningSpace: "There must be a space after this paren.",
missingClosingSpace: "There must be a space before this paren.",
rejectedOpeningSpace: "There should be no space after this paren.",
rejectedClosingSpace: "There should be no space before this paren."
}
},
create(context) {
const ALWAYS = context.options[0] === "always";
const exceptionsArrayOptions = context.options[1] && context.options[1].exceptions || [];
const options = {
braceException: false,
bracketException: false,
parenException: false,
empty: false
};
let exceptions = {
openers: [],
closers: []
};
if (exceptionsArrayOptions.length) {
options.braceException = exceptionsArrayOptions.includes("{}");
options.bracketException = exceptionsArrayOptions.includes("[]");
options.parenException = exceptionsArrayOptions.includes("()");
options.empty = exceptionsArrayOptions.includes("empty");
}
function getExceptions() {
const openers = [];
const closers = [];
if (options.braceException) {
openers.push("{");
closers.push("}");
}
if (options.bracketException) {
openers.push("[");
closers.push("]");
}
if (options.parenException) {
openers.push("(");
closers.push(")");
}
if (options.empty) {
openers.push(")");
closers.push("(");
}
return {
openers,
closers
};
}
const sourceCode = context.sourceCode;
function isOpenerException(token) {
return exceptions.openers.includes(token.value);
}
function isCloserException(token) {
return exceptions.closers.includes(token.value);
}
function openerMissingSpace(openingParenToken, tokenAfterOpeningParen) {
if (sourceCode.isSpaceBetween(openingParenToken, tokenAfterOpeningParen)) return false;
if (!options.empty && (0, ast_exports.isClosingParenToken)(tokenAfterOpeningParen)) return false;
if (ALWAYS) return !isOpenerException(tokenAfterOpeningParen);
return isOpenerException(tokenAfterOpeningParen);
}
function openerRejectsSpace(openingParenToken, tokenAfterOpeningParen) {
if (!(0, ast_exports.isTokenOnSameLine)(openingParenToken, tokenAfterOpeningParen)) return false;
if (tokenAfterOpeningParen.type === "Line") return false;
if (!sourceCode.isSpaceBetween(openingParenToken, tokenAfterOpeningParen)) return false;
if (ALWAYS) return isOpenerException(tokenAfterOpeningParen);
return !isOpenerException(tokenAfterOpeningParen);
}
function closerMissingSpace(tokenBeforeClosingParen, closingParenToken) {
if (sourceCode.isSpaceBetween(tokenBeforeClosingParen, closingParenToken)) return false;
if (!options.empty && (0, ast_exports.isOpeningParenToken)(tokenBeforeClosingParen)) return false;
if (ALWAYS) return !isCloserException(tokenBeforeClosingParen);
return isCloserException(tokenBeforeClosingParen);
}
function closerRejectsSpace(tokenBeforeClosingParen, closingParenToken) {
if (!(0, ast_exports.isTokenOnSameLine)(tokenBeforeClosingParen, closingParenToken)) return false;
if (!sourceCode.isSpaceBetween(tokenBeforeClosingParen, closingParenToken)) return false;
if (ALWAYS) return isCloserException(tokenBeforeClosingParen);
return !isCloserException(tokenBeforeClosingParen);
}
return { Program: function checkParenSpaces(node) {
exceptions = getExceptions();
const tokens = sourceCode.tokensAndComments;
tokens.forEach((token, i) => {
const prevToken = tokens[i - 1];
const nextToken = tokens[i + 1];
if (!(0, ast_exports.isOpeningParenToken)(token) && !(0, ast_exports.isClosingParenToken)(token)) return;
if (token.value === "(" && openerMissingSpace(token, nextToken)) context.report({
node,
loc: token.loc,
messageId: "missingOpeningSpace",
fix(fixer) {
return fixer.insertTextAfter(token, " ");
}
});
if (token.value === "(" && openerRejectsSpace(token, nextToken)) context.report({
node,
loc: {
start: token.loc.end,
end: nextToken.loc.start
},
messageId: "rejectedOpeningSpace",
fix(fixer) {
return fixer.removeRange([token.range[1], nextToken.range[0]]);
}
});
if (token.value === ")" && closerMissingSpace(prevToken, token)) context.report({
node,
loc: token.loc,
messageId: "missingClosingSpace",
fix(fixer) {
return fixer.insertTextBefore(token, " ");
}
});
if (token.value === ")" && closerRejectsSpace(prevToken, token)) context.report({
node,
loc: {
start: prevToken.loc.end,
end: token.loc.start
},
messageId: "rejectedClosingSpace",
fix(fixer) {
return fixer.removeRange([prevToken.range[1], token.range[0]]);
}
});
});
} };
}
});
export { space_in_parens_default };

View File

@@ -0,0 +1,133 @@
import { AST_NODE_TYPES, AST_TOKEN_TYPES, ast_exports, createRule } from "../utils.js";
const UNIONS = ["|", "&"];
var space_infix_ops_default = createRule({
name: "space-infix-ops",
meta: {
type: "layout",
docs: { description: "Require spacing around infix operators" },
fixable: "whitespace",
schema: [{
type: "object",
properties: {
int32Hint: {
type: "boolean",
default: false
},
ignoreTypes: {
type: "boolean",
default: false
}
},
additionalProperties: false
}],
messages: { missingSpace: "Operator '{{operator}}' must be spaced." }
},
defaultOptions: [{
int32Hint: false,
ignoreTypes: false
}],
create(context) {
const int32Hint = context.options[0] ? context.options[0].int32Hint === true : false;
const ignoreTypes = context.options[0] ? context.options[0].ignoreTypes === true : false;
const sourceCode = context.sourceCode;
function report(node, operator) {
context.report({
node,
loc: operator.loc,
messageId: "missingSpace",
data: { operator: operator.value },
fix(fixer) {
const previousToken = sourceCode.getTokenBefore(operator);
const afterToken = sourceCode.getTokenAfter(operator);
let fixString = "";
if (operator.range[0] - previousToken.range[1] === 0) fixString = " ";
fixString += operator.value;
if (afterToken.range[0] - operator.range[1] === 0) fixString += " ";
return fixer.replaceText(operator, fixString);
}
});
}
function getFirstNonSpacedToken(left, right, op) {
const operator = sourceCode.getFirstTokenBetween(left, right, (token) => token.value === op);
const prev = sourceCode.getTokenBefore(operator);
const next = sourceCode.getTokenAfter(operator);
if (!sourceCode.isSpaceBetween(prev, operator) || !sourceCode.isSpaceBetween(operator, next)) return operator;
return null;
}
function checkBinary(node) {
const leftNode = "typeAnnotation" in node.left && node.left.typeAnnotation ? node.left.typeAnnotation : node.left;
const rightNode = node.right;
const operator = "operator" in node && node.operator ? node.operator : "=";
const nonSpacedNode = getFirstNonSpacedToken(leftNode, rightNode, operator);
if (nonSpacedNode) {
if (!(int32Hint && sourceCode.getText(node).endsWith("|0"))) report(node, nonSpacedNode);
}
}
function checkConditional(node) {
const nonSpacedConsequentNode = getFirstNonSpacedToken(node.test, node.consequent, "?");
const nonSpacedAlternateNode = getFirstNonSpacedToken(node.consequent, node.alternate, ":");
if (nonSpacedConsequentNode) report(node, nonSpacedConsequentNode);
if (nonSpacedAlternateNode) report(node, nonSpacedAlternateNode);
}
function checkVar(node) {
const leftNode = node.id.typeAnnotation ? node.id.typeAnnotation : node.id;
const rightNode = node.init;
if (rightNode) {
const nonSpacedNode = getFirstNonSpacedToken(leftNode, rightNode, "=");
if (nonSpacedNode) report(node, nonSpacedNode);
}
}
function isSpaceChar(token) {
return token.type === AST_TOKEN_TYPES.Punctuator && /^[=?:]$/.test(token.value);
}
function checkAndReportAssignmentSpace(node, leftNode, rightNode) {
if (!rightNode || !leftNode) return;
const operator = sourceCode.getFirstTokenBetween(leftNode, rightNode, isSpaceChar);
const prev = sourceCode.getTokenBefore(operator);
const next = sourceCode.getTokenAfter(operator);
if (!sourceCode.isSpaceBetween(prev, operator) || !sourceCode.isSpaceBetween(operator, next)) report(node, operator);
}
function checkForEnumAssignmentSpace(node) {
checkAndReportAssignmentSpace(node, node.id, node.initializer);
}
function checkForPropertyDefinitionAssignmentSpace(node) {
const leftNode = node.optional && !node.typeAnnotation ? sourceCode.getTokenAfter(node.key) : node.typeAnnotation ?? node.key;
checkAndReportAssignmentSpace(node, leftNode, node.value);
}
function checkForTypeAnnotationSpace(typeAnnotation) {
const types = typeAnnotation.types;
types.forEach((type) => {
const skipFunctionParenthesis = type.type === AST_NODE_TYPES.TSFunctionType ? ast_exports.isNotOpeningParenToken : 0;
const operator = sourceCode.getTokenBefore(type, skipFunctionParenthesis);
if (!ignoreTypes && operator != null && UNIONS.includes(operator.value)) {
const prev = sourceCode.getTokenBefore(operator);
const next = sourceCode.getTokenAfter(operator);
if (!sourceCode.isSpaceBetween(prev, operator) || !sourceCode.isSpaceBetween(operator, next)) report(typeAnnotation, operator);
}
});
}
function checkForTypeAliasAssignment(node) {
checkAndReportAssignmentSpace(node, node.typeParameters ?? node.id, node.typeAnnotation);
}
function checkForTypeConditional(node) {
checkAndReportAssignmentSpace(node, node.extendsType, node.trueType);
checkAndReportAssignmentSpace(node, node.trueType, node.falseType);
}
return {
AssignmentExpression: checkBinary,
AssignmentPattern: checkBinary,
BinaryExpression: checkBinary,
LogicalExpression: checkBinary,
ConditionalExpression: checkConditional,
VariableDeclarator: checkVar,
TSEnumMember: checkForEnumAssignmentSpace,
PropertyDefinition: checkForPropertyDefinitionAssignmentSpace,
AccessorProperty: checkForPropertyDefinitionAssignmentSpace,
TSTypeAliasDeclaration: checkForTypeAliasAssignment,
TSUnionType: checkForTypeAnnotationSpace,
TSIntersectionType: checkForTypeAnnotationSpace,
TSConditionalType: checkForTypeConditional
};
}
});
export { space_infix_ops_default };

View File

@@ -0,0 +1,158 @@
import { canTokensBeAdjacent, createRule, isKeywordToken } from "../utils.js";
var space_unary_ops_default = createRule({
name: "space-unary-ops",
meta: {
type: "layout",
docs: { description: "Enforce consistent spacing before or after unary operators" },
fixable: "whitespace",
schema: [{
type: "object",
properties: {
words: {
type: "boolean",
default: true
},
nonwords: {
type: "boolean",
default: false
},
overrides: {
type: "object",
additionalProperties: { type: "boolean" }
}
},
additionalProperties: false
}],
messages: {
unexpectedBefore: "Unexpected space before unary operator '{{operator}}'.",
unexpectedAfter: "Unexpected space after unary operator '{{operator}}'.",
unexpectedAfterWord: "Unexpected space after unary word operator '{{word}}'.",
requireAfterWord: "Unary word operator '{{word}}' must be followed by whitespace.",
requireAfter: "Unary operator '{{operator}}' must be followed by whitespace.",
requireBefore: "Space is required before unary operator '{{operator}}'."
}
},
create(context) {
const options = context.options[0] || {
words: true,
nonwords: false
};
const sourceCode = context.sourceCode;
function isFirstBangInBangBangExpression(node) {
return node && node.type === "UnaryExpression" && node.argument && node.argument.type === "UnaryExpression" && node.argument.operator === "!";
}
function overrideExistsForOperator(operator) {
return options.overrides && Object.prototype.hasOwnProperty.call(options.overrides, operator);
}
function overrideEnforcesSpaces(operator) {
return options.overrides?.[operator];
}
function verifyWordHasSpaces(node, firstToken, secondToken, word) {
if (secondToken.range[0] === firstToken.range[1]) context.report({
node,
messageId: "requireAfterWord",
data: { word },
fix(fixer) {
return fixer.insertTextAfter(firstToken, " ");
}
});
}
function verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word) {
if (canTokensBeAdjacent(firstToken, secondToken)) {
if (secondToken.range[0] > firstToken.range[1]) context.report({
node,
messageId: "unexpectedAfterWord",
data: { word },
fix(fixer) {
return fixer.removeRange([firstToken.range[1], secondToken.range[0]]);
}
});
}
}
function checkUnaryWordOperatorForSpaces(node, firstToken, secondToken, word) {
const shouldHaveSpace = overrideExistsForOperator(word) ? overrideEnforcesSpaces(word) : options.words;
if (shouldHaveSpace) verifyWordHasSpaces(node, firstToken, secondToken, word);
else verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word);
}
function checkForSpacesAroundNonNull(node) {
const operator = "!";
const operatorToken = sourceCode.getLastToken(node, (token) => token.value === operator);
const prefixToken = sourceCode.getTokenBefore(operatorToken);
const shouldHaveSpace = overrideExistsForOperator(operator) ? overrideEnforcesSpaces(operator) : options.nonwords;
if (shouldHaveSpace) verifyNonWordsHaveSpaces(node, prefixToken, operatorToken);
else verifyNonWordsDontHaveSpaces(node, prefixToken, operatorToken);
}
function checkForSpacesAfterYield(node) {
const tokens = sourceCode.getFirstTokens(node, 3);
const word = "yield";
if (!node.argument || node.delegate) return;
checkUnaryWordOperatorForSpaces(node, tokens[0], tokens[1], word);
}
function checkForSpacesAfterAwait(node) {
const tokens = sourceCode.getFirstTokens(node, 3);
checkUnaryWordOperatorForSpaces(node, tokens[0], tokens[1], "await");
}
function verifyNonWordsHaveSpaces(node, firstToken, secondToken) {
if ("prefix" in node && node.prefix) {
if (isFirstBangInBangBangExpression(node)) return;
if (firstToken.range[1] === secondToken.range[0]) context.report({
node,
messageId: "requireAfter",
data: { operator: firstToken.value },
fix(fixer) {
return fixer.insertTextAfter(firstToken, " ");
}
});
} else if (firstToken.range[1] === secondToken.range[0]) context.report({
node,
messageId: "requireBefore",
data: { operator: secondToken.value },
fix(fixer) {
return fixer.insertTextBefore(secondToken, " ");
}
});
}
function verifyNonWordsDontHaveSpaces(node, firstToken, secondToken) {
if ("prefix" in node && node.prefix) {
if (secondToken.range[0] > firstToken.range[1]) context.report({
node,
messageId: "unexpectedAfter",
data: { operator: firstToken.value },
fix(fixer) {
if (canTokensBeAdjacent(firstToken, secondToken)) return fixer.removeRange([firstToken.range[1], secondToken.range[0]]);
return null;
}
});
} else if (secondToken.range[0] > firstToken.range[1]) context.report({
node,
messageId: "unexpectedBefore",
data: { operator: secondToken.value },
fix(fixer) {
return fixer.removeRange([firstToken.range[1], secondToken.range[0]]);
}
});
}
function checkForSpaces(node) {
const tokens = node.type === "UpdateExpression" && !node.prefix ? sourceCode.getLastTokens(node, 2) : sourceCode.getFirstTokens(node, 2);
const firstToken = tokens[0];
const secondToken = tokens[1];
if ((node.type === "NewExpression" || node.prefix) && isKeywordToken(firstToken)) {
checkUnaryWordOperatorForSpaces(node, firstToken, secondToken, firstToken.value);
return;
}
const operator = "prefix" in node && node.prefix ? tokens[0].value : tokens[1].value;
const shouldHaveSpace = overrideExistsForOperator(operator) ? overrideEnforcesSpaces(operator) : options.nonwords;
if (shouldHaveSpace) verifyNonWordsHaveSpaces(node, firstToken, secondToken);
else verifyNonWordsDontHaveSpaces(node, firstToken, secondToken);
}
return {
UnaryExpression: checkForSpaces,
UpdateExpression: checkForSpaces,
NewExpression: checkForSpaces,
YieldExpression: checkForSpacesAfterYield,
AwaitExpression: checkForSpacesAfterAwait,
TSNonNullExpression: checkForSpacesAroundNonNull
};
}
});
export { space_unary_ops_default };

View File

@@ -0,0 +1,189 @@
import { LINEBREAKS, createRule, isHashbangComment } from "../utils.js";
import { escapeStringRegexp } from "../vendor.js";
function escape(s) {
return `(?:${escapeStringRegexp(s)})`;
}
function escapeAndRepeat(s) {
return `${escape(s)}+`;
}
function parseMarkersOption(markers) {
if (!markers.includes("*")) return markers.concat("*");
return markers;
}
function createExceptionsPattern(exceptions) {
let pattern = "";
if (exceptions.length === 0) pattern += "\\s";
else {
pattern += "(?:\\s|";
if (exceptions.length === 1) pattern += escapeAndRepeat(exceptions[0]);
else {
pattern += "(?:";
pattern += exceptions.map(escapeAndRepeat).join("|");
pattern += ")";
}
pattern += `(?:$|[${Array.from(LINEBREAKS).join("")}]))`;
}
return pattern;
}
function createAlwaysStylePattern(markers, exceptions) {
let pattern = "^";
if (markers.length === 1) pattern += escape(markers[0]);
else {
pattern += "(?:";
pattern += markers.map(escape).join("|");
pattern += ")";
}
pattern += "?";
pattern += createExceptionsPattern(exceptions);
return new RegExp(pattern, "u");
}
function createNeverStylePattern(markers) {
const pattern = `^(${markers.map(escape).join("|")})?[ \t]+`;
return new RegExp(pattern, "u");
}
var spaced_comment_default = createRule({
name: "spaced-comment",
meta: {
type: "layout",
docs: { description: "Enforce consistent spacing after the `//` or `/*` in a comment" },
fixable: "whitespace",
schema: [{
type: "string",
enum: ["always", "never"]
}, {
type: "object",
properties: {
exceptions: {
type: "array",
items: { type: "string" }
},
markers: {
type: "array",
items: { type: "string" }
},
line: {
type: "object",
properties: {
exceptions: {
type: "array",
items: { type: "string" }
},
markers: {
type: "array",
items: { type: "string" }
}
},
additionalProperties: false
},
block: {
type: "object",
properties: {
exceptions: {
type: "array",
items: { type: "string" }
},
markers: {
type: "array",
items: { type: "string" }
},
balanced: {
type: "boolean",
default: false
}
},
additionalProperties: false
}
},
additionalProperties: false
}],
messages: {
unexpectedSpaceAfterMarker: "Unexpected space or tab after marker ({{refChar}}) in comment.",
expectedExceptionAfter: "Expected exception block, space or tab after '{{refChar}}' in comment.",
unexpectedSpaceBefore: "Unexpected space or tab before '*/' in comment.",
unexpectedSpaceAfter: "Unexpected space or tab after '{{refChar}}' in comment.",
expectedSpaceBefore: "Expected space or tab before '*/' in comment.",
expectedSpaceAfter: "Expected space or tab after '{{refChar}}' in comment."
}
},
create(context) {
const sourceCode = context.sourceCode;
const requireSpace = context.options[0] !== "never";
const config = context.options[1] || {};
const balanced = config.block && config.block.balanced;
const styleRules = ["block", "line"].reduce((rule, type) => {
const nodeType = type;
const markers = parseMarkersOption(config[nodeType] && config[nodeType]?.markers || config.markers || []);
const exceptions = config[nodeType] && config[nodeType]?.exceptions || config.exceptions || [];
const endNeverPattern = "[ ]+$";
rule[nodeType] = {
beginRegex: requireSpace ? createAlwaysStylePattern(markers, exceptions) : createNeverStylePattern(markers),
endRegex: balanced && requireSpace ? new RegExp(`${createExceptionsPattern(exceptions)}$`, "u") : new RegExp(endNeverPattern, "u"),
hasExceptions: exceptions.length > 0,
captureMarker: new RegExp(`^(${markers.map(escape).join("|")})`, "u"),
markers: new Set(markers)
};
return rule;
}, {});
function reportBegin(node, messageId, match, refChar) {
const type = node.type.toLowerCase();
const commentIdentifier = type === "block" ? "/*" : "//";
context.report({
node,
fix(fixer) {
const start = node.range[0];
let end = start + 2;
if (requireSpace) {
if (match) end += match[0].length;
return fixer.insertTextAfterRange([start, end], " ");
}
if (match) end += match[0].length;
return fixer.replaceTextRange([start, end], commentIdentifier + (match && match[1] ? match[1] : ""));
},
messageId,
data: { refChar }
});
}
function reportEnd(node, messageId, match) {
context.report({
node,
fix(fixer) {
if (requireSpace) return fixer.insertTextAfterRange([node.range[0], node.range[1] - 2], " ");
const end = node.range[1] - 2;
let start = end;
if (match) start -= match[0].length;
return fixer.replaceTextRange([start, end], "");
},
messageId
});
}
function checkCommentForSpace(node) {
const type = node.type.toLowerCase();
const rule = styleRules[type];
const commentIdentifier = type === "block" ? "/*" : "//";
if (node.value.length === 0 || rule.markers.has(node.value)) return;
if (type === "line" && (node.value.startsWith("/ <reference") || node.value.startsWith("/ <amd"))) return;
const beginMatch = rule.beginRegex.exec(node.value);
const endMatch = rule.endRegex.exec(node.value);
if (requireSpace) {
if (!beginMatch) {
const hasMarker = rule.captureMarker.exec(node.value);
const marker = hasMarker ? commentIdentifier + hasMarker[0] : commentIdentifier;
if (rule.hasExceptions) reportBegin(node, "expectedExceptionAfter", hasMarker, marker);
else reportBegin(node, "expectedSpaceAfter", hasMarker, marker);
}
if (balanced && type === "block" && !endMatch) reportEnd(node, "expectedSpaceBefore", null);
} else {
if (beginMatch) if (!beginMatch[1]) reportBegin(node, "unexpectedSpaceAfter", beginMatch, commentIdentifier);
else reportBegin(node, "unexpectedSpaceAfterMarker", beginMatch, beginMatch[1]);
if (balanced && type === "block" && endMatch) reportEnd(node, "unexpectedSpaceBefore", endMatch);
}
}
return { Program() {
const comments = sourceCode.getAllComments();
comments.forEach((comment) => {
if (!isHashbangComment(comment)) checkCommentForSpace(comment);
});
} };
}
});
export { spaced_comment_default };

View File

@@ -0,0 +1,61 @@
import { ast_exports, createRule, getSwitchCaseColonToken, hasCommentsBetween } from "../utils.js";
var switch_colon_spacing_default = createRule({
name: "switch-colon-spacing",
meta: {
type: "layout",
docs: { description: "Enforce spacing around colons of switch statements" },
schema: [{
type: "object",
properties: {
before: {
type: "boolean",
default: false
},
after: {
type: "boolean",
default: true
}
},
additionalProperties: false
}],
fixable: "whitespace",
messages: {
expectedBefore: "Expected space(s) before this colon.",
expectedAfter: "Expected space(s) after this colon.",
unexpectedBefore: "Unexpected space(s) before this colon.",
unexpectedAfter: "Unexpected space(s) after this colon."
}
},
create(context) {
const sourceCode = context.sourceCode;
const options = context.options[0] || {};
const beforeSpacing = options.before === true;
const afterSpacing = options.after !== false;
function isValidSpacing(left, right, expected) {
return (0, ast_exports.isClosingBraceToken)(right) || !(0, ast_exports.isTokenOnSameLine)(left, right) || sourceCode.isSpaceBetween(left, right) === expected;
}
function fix(fixer, left, right, spacing) {
if (hasCommentsBetween(sourceCode, left, right)) return null;
if (spacing) return fixer.insertTextAfter(left, " ");
return fixer.removeRange([left.range[1], right.range[0]]);
}
return { SwitchCase(node) {
const colonToken = getSwitchCaseColonToken(node, sourceCode);
const beforeToken = sourceCode.getTokenBefore(colonToken);
const afterToken = sourceCode.getTokenAfter(colonToken);
if (!isValidSpacing(beforeToken, colonToken, beforeSpacing)) context.report({
node,
loc: colonToken.loc,
messageId: beforeSpacing ? "expectedBefore" : "unexpectedBefore",
fix: (fixer) => fix(fixer, beforeToken, colonToken, beforeSpacing)
});
if (!isValidSpacing(colonToken, afterToken, afterSpacing)) context.report({
node,
loc: colonToken.loc,
messageId: afterSpacing ? "expectedAfter" : "unexpectedAfter",
fix: (fixer) => fix(fixer, colonToken, afterToken, afterSpacing)
});
} };
}
});
export { switch_colon_spacing_default };

View File

@@ -0,0 +1,79 @@
import { ast_exports, createRule } from "../utils.js";
var template_curly_spacing_default = createRule({
name: "template-curly-spacing",
meta: {
type: "layout",
docs: { description: "Require or disallow spacing around embedded expressions of template strings" },
fixable: "whitespace",
schema: [{
type: "string",
enum: ["always", "never"]
}],
messages: {
expectedBefore: "Expected space(s) before '}'.",
expectedAfter: "Expected space(s) after '${'.",
unexpectedBefore: "Unexpected space(s) before '}'.",
unexpectedAfter: "Unexpected space(s) after '${'."
}
},
create(context) {
const sourceCode = context.sourceCode;
const always = context.options[0] === "always";
function checkSpacingBefore(token) {
if (!token.value.startsWith("}")) return;
const prevToken = sourceCode.getTokenBefore(token, { includeComments: true });
const hasSpace = sourceCode.isSpaceBetween(prevToken, token);
if (!(0, ast_exports.isTokenOnSameLine)(prevToken, token)) return;
if (always && !hasSpace) context.report({
loc: {
start: token.loc.start,
end: {
line: token.loc.start.line,
column: token.loc.start.column + 1
}
},
messageId: "expectedBefore",
fix: (fixer) => fixer.insertTextBefore(token, " ")
});
if (!always && hasSpace) context.report({
loc: {
start: prevToken.loc.end,
end: token.loc.start
},
messageId: "unexpectedBefore",
fix: (fixer) => fixer.removeRange([prevToken.range[1], token.range[0]])
});
}
function checkSpacingAfter(token) {
if (!token.value.endsWith("${")) return;
const nextToken = sourceCode.getTokenAfter(token, { includeComments: true });
const hasSpace = sourceCode.isSpaceBetween(token, nextToken);
if (!(0, ast_exports.isTokenOnSameLine)(token, nextToken)) return;
if (always && !hasSpace) context.report({
loc: {
start: {
line: token.loc.end.line,
column: token.loc.end.column - 2
},
end: token.loc.end
},
messageId: "expectedAfter",
fix: (fixer) => fixer.insertTextAfter(token, " ")
});
if (!always && hasSpace) context.report({
loc: {
start: token.loc.end,
end: nextToken.loc.start
},
messageId: "unexpectedAfter",
fix: (fixer) => fixer.removeRange([token.range[1], nextToken.range[0]])
});
}
return { TemplateElement(node) {
const token = sourceCode.getFirstToken(node);
checkSpacingBefore(token);
checkSpacingAfter(token);
} };
}
});
export { template_curly_spacing_default };

View File

@@ -0,0 +1,52 @@
import { createRule } from "../utils.js";
var template_tag_spacing_default = createRule({
name: "template-tag-spacing",
meta: {
type: "layout",
docs: { description: "Require or disallow spacing between template tags and their literals" },
fixable: "whitespace",
schema: [{
type: "string",
enum: ["always", "never"]
}],
messages: {
unexpected: "Unexpected space between template tag and template literal.",
missing: "Missing space between template tag and template literal."
}
},
create(context) {
const never = context.options[0] !== "always";
const sourceCode = context.sourceCode;
function checkSpacing(node) {
const tagToken = sourceCode.getTokenBefore(node.quasi);
const literalToken = sourceCode.getFirstToken(node.quasi);
const hasWhitespace = sourceCode.isSpaceBetween(tagToken, literalToken);
if (never && hasWhitespace) context.report({
node,
loc: {
start: tagToken.loc.end,
end: literalToken.loc.start
},
messageId: "unexpected",
fix(fixer) {
const comments = sourceCode.getCommentsBefore(node.quasi);
if (comments.some((comment) => comment.type === "Line")) return null;
return fixer.replaceTextRange([tagToken.range[1], literalToken.range[0]], comments.reduce((text, comment) => text + sourceCode.getText(comment), ""));
}
});
else if (!never && !hasWhitespace) context.report({
node,
loc: {
start: node.loc.start,
end: literalToken.loc.start
},
messageId: "missing",
fix(fixer) {
return fixer.insertTextAfter(tagToken, " ");
}
});
}
return { TaggedTemplateExpression: checkSpacing };
}
});
export { template_tag_spacing_default };

View File

@@ -0,0 +1,175 @@
import { ast_exports, createRule } from "../utils.js";
function createRules(options) {
const globals = {
...options?.before !== void 0 ? { before: options.before } : {},
...options?.after !== void 0 ? { after: options.after } : {}
};
const override = options?.overrides ?? {};
const colon = {
before: false,
after: true,
...globals,
...override?.colon
};
const arrow = {
before: true,
after: true,
...globals,
...override?.arrow
};
return {
colon,
arrow,
variable: {
...colon,
...override?.variable
},
property: {
...colon,
...override?.property
},
parameter: {
...colon,
...override?.parameter
},
returnType: {
...colon,
...override?.returnType
}
};
}
function getIdentifierRules(rules, node) {
const scope = node?.parent;
if ((0, ast_exports.isVariableDeclarator)(scope)) return rules.variable;
else if ((0, ast_exports.isFunctionOrFunctionType)(scope)) return rules.parameter;
return rules.colon;
}
function getRules(rules, node) {
const scope = node?.parent?.parent;
if ((0, ast_exports.isTSFunctionType)(scope) || (0, ast_exports.isTSConstructorType)(scope)) return rules.arrow;
else if ((0, ast_exports.isIdentifier)(scope)) return getIdentifierRules(rules, scope);
else if ((0, ast_exports.isClassOrTypeElement)(scope)) return rules.property;
else if ((0, ast_exports.isFunction)(scope)) return rules.returnType;
return rules.colon;
}
var type_annotation_spacing_default = createRule({
name: "type-annotation-spacing",
meta: {
type: "layout",
docs: { description: "Require consistent spacing around type annotations" },
fixable: "whitespace",
messages: {
expectedSpaceAfter: "Expected a space after the '{{type}}'.",
expectedSpaceBefore: "Expected a space before the '{{type}}'.",
unexpectedSpaceAfter: "Unexpected space after the '{{type}}'.",
unexpectedSpaceBefore: "Unexpected space before the '{{type}}'.",
unexpectedSpaceBetween: "Unexpected space between the '{{previousToken}}' and the '{{type}}'."
},
schema: [{
$defs: { spacingConfig: {
type: "object",
properties: {
before: { type: "boolean" },
after: { type: "boolean" }
},
additionalProperties: false
} },
type: "object",
properties: {
before: { type: "boolean" },
after: { type: "boolean" },
overrides: {
type: "object",
properties: {
colon: { $ref: "#/items/0/$defs/spacingConfig" },
arrow: { $ref: "#/items/0/$defs/spacingConfig" },
variable: { $ref: "#/items/0/$defs/spacingConfig" },
parameter: { $ref: "#/items/0/$defs/spacingConfig" },
property: { $ref: "#/items/0/$defs/spacingConfig" },
returnType: { $ref: "#/items/0/$defs/spacingConfig" }
},
additionalProperties: false
}
},
additionalProperties: false
}]
},
defaultOptions: [{}],
create(context, [options]) {
const punctuators = [":", "=>"];
const sourceCode = context.sourceCode;
const ruleSet = createRules(options);
function checkTypeAnnotationSpacing(typeAnnotation) {
const punctuatorTokenEnd = sourceCode.getTokenBefore(typeAnnotation, ast_exports.isNotOpeningParenToken);
let punctuatorTokenStart = punctuatorTokenEnd;
let previousToken = sourceCode.getTokenBefore(punctuatorTokenEnd);
let type = punctuatorTokenEnd.value;
if (!punctuators.includes(type)) return;
const { before, after } = getRules(ruleSet, typeAnnotation);
if (type === ":" && previousToken.value === "?") {
if (sourceCode.isSpaceBetween(previousToken, punctuatorTokenStart)) context.report({
node: punctuatorTokenStart,
messageId: "unexpectedSpaceBetween",
data: {
type,
previousToken: previousToken.value
},
fix(fixer) {
return fixer.removeRange([previousToken.range[1], punctuatorTokenStart.range[0]]);
}
});
type = "?:";
punctuatorTokenStart = previousToken;
previousToken = sourceCode.getTokenBefore(previousToken);
if (previousToken.value === "+" || previousToken.value === "-") {
type = `${previousToken.value}?:`;
punctuatorTokenStart = previousToken;
previousToken = sourceCode.getTokenBefore(previousToken);
}
}
const hasNextSpace = sourceCode.isSpaceBetween(punctuatorTokenEnd, typeAnnotation);
if (after && !hasNextSpace) context.report({
node: punctuatorTokenEnd,
messageId: "expectedSpaceAfter",
data: { type },
fix(fixer) {
return fixer.insertTextAfter(punctuatorTokenEnd, " ");
}
});
else if (!after && hasNextSpace) context.report({
node: punctuatorTokenEnd,
messageId: "unexpectedSpaceAfter",
data: { type },
fix(fixer) {
return fixer.removeRange([punctuatorTokenEnd.range[1], typeAnnotation.range[0]]);
}
});
const hasPrevSpace = sourceCode.isSpaceBetween(previousToken, punctuatorTokenStart);
if (before && !hasPrevSpace) context.report({
node: punctuatorTokenStart,
messageId: "expectedSpaceBefore",
data: { type },
fix(fixer) {
return fixer.insertTextAfter(previousToken, " ");
}
});
else if (!before && hasPrevSpace) context.report({
node: punctuatorTokenStart,
messageId: "unexpectedSpaceBefore",
data: { type },
fix(fixer) {
return fixer.removeRange([previousToken.range[1], punctuatorTokenStart.range[0]]);
}
});
}
return {
TSMappedType(node) {
if (node.typeAnnotation) checkTypeAnnotationSpacing(node.typeAnnotation);
},
TSTypeAnnotation(node) {
checkTypeAnnotationSpacing(node.typeAnnotation);
}
};
}
});
export { type_annotation_spacing_default };

View File

@@ -0,0 +1,109 @@
import { createRule } from "../utils.js";
const PRESERVE_PREFIX_SPACE_BEFORE_GENERIC = new Set([
"TSCallSignatureDeclaration",
"ArrowFunctionExpression",
"TSFunctionType",
"FunctionExpression",
"ClassExpression"
]);
var type_generic_spacing_default = createRule({
name: "type-generic-spacing",
meta: {
type: "layout",
docs: { description: "Enforces consistent spacing inside TypeScript type generics" },
fixable: "whitespace",
schema: [],
messages: { genericSpacingMismatch: "Generic spaces mismatch" }
},
defaultOptions: [],
create: (context) => {
const sourceCode = context.sourceCode;
function removeSpaceBetween(left, right) {
const textBetween = sourceCode.text.slice(left.range[1], right.range[0]);
if (/\s/.test(textBetween) && !/^[\r\n]/.test(textBetween)) context.report({
loc: {
start: left.loc.end,
end: right.loc.start
},
messageId: "genericSpacingMismatch",
*fix(fixer) {
yield fixer.replaceTextRange([left.range[1], right.range[0]], "");
}
});
}
function checkBracketSpacing(openToken, closeToken) {
if (openToken) {
const firstToken = sourceCode.getTokenAfter(openToken);
if (firstToken) removeSpaceBetween(openToken, firstToken);
}
if (closeToken) {
const lastToken = sourceCode.getTokenBefore(closeToken);
if (lastToken) removeSpaceBetween(lastToken, closeToken);
}
}
return {
TSTypeParameterInstantiation: (node) => {
const params = node.params;
if (params.length === 0) return;
const openToken = sourceCode.getTokenBefore(params[0]);
const closeToken = sourceCode.getTokenAfter(params[params.length - 1]);
checkBracketSpacing(openToken, closeToken);
},
TSTypeParameterDeclaration: (node) => {
if (!PRESERVE_PREFIX_SPACE_BEFORE_GENERIC.has(node.parent.type)) {
const pre = sourceCode.text.slice(0, node.range[0]);
const preSpace = pre.match(/(\s+)$/)?.[0];
if (preSpace && preSpace.length) context.report({
node,
messageId: "genericSpacingMismatch",
*fix(fixer) {
yield fixer.replaceTextRange([node.range[0] - preSpace.length, node.range[0]], "");
}
});
}
const params = node.params;
if (params.length === 0) return;
const openToken = sourceCode.getTokenBefore(params[0]);
const closeToken = sourceCode.getTokenAfter(params[params.length - 1]);
checkBracketSpacing(openToken, closeToken);
for (let i = 1; i < params.length; i++) {
const prev = params[i - 1];
const current = params[i];
const from = prev.range[1];
const to = current.range[0];
const span = sourceCode.text.slice(from, to);
if (span !== ", " && !span.match(/,\s*\n/)) context.report({
*fix(fixer) {
yield fixer.replaceTextRange([from, to], ", ");
},
loc: {
start: prev.loc.end,
end: current.loc.start
},
messageId: "genericSpacingMismatch",
node
});
}
},
TSTypeParameter: (node) => {
if (!node.default) return;
const endNode = node.constraint || node.name;
const from = endNode.range[1];
const to = node.default.range[0];
const span = sourceCode.text.slice(from, to);
if (!span.match(/(?:^|[^ ]) = (?:$|[^ ])/)) context.report({
*fix(fixer) {
yield fixer.replaceTextRange([from, to], span.replace(/\s*=\s*/, " = "));
},
loc: {
start: endNode.loc.end,
end: node.default.loc.start
},
messageId: "genericSpacingMismatch",
node
});
}
};
}
});
export { type_generic_spacing_default };

View File

@@ -0,0 +1,59 @@
import { createRule } from "../utils.js";
const tupleRe = /^([\w$]+)(\s*)(\?\s*)?:(\s*)(.*)$/;
var type_named_tuple_spacing_default = createRule({
name: "type-named-tuple-spacing",
meta: {
type: "layout",
docs: { description: "Expect space before the type declaration in the named tuple" },
fixable: "whitespace",
schema: [],
messages: {
expectedSpaceAfter: "Expected a space after the ':'.",
unexpectedSpaceBetween: "Unexpected space between '?' and the ':'.",
unexpectedSpaceBefore: "Unexpected space before the ':'."
}
},
defaultOptions: [],
create: (context) => {
const sourceCode = context.getSourceCode();
return { TSNamedTupleMember: (node) => {
const code = sourceCode.text.slice(node.range[0], node.range[1]);
const match = code.match(tupleRe);
if (!match) return;
const labelName = node.label.name;
const spaceBeforeColon = match[2];
const optionalMark = match[3];
const spacesAfterColon = match[4];
const elementType = match[5];
function getReplaceValue() {
let ret = labelName;
if (node.optional) ret += "?";
ret += ": ";
ret += elementType;
return ret;
}
if (optionalMark?.length > 1) context.report({
node,
messageId: "unexpectedSpaceBetween",
*fix(fixer) {
yield fixer.replaceTextRange(node.range, code.replace(tupleRe, getReplaceValue()));
}
});
if (spaceBeforeColon?.length) context.report({
node,
messageId: "unexpectedSpaceBefore",
*fix(fixer) {
yield fixer.replaceTextRange(node.range, code.replace(tupleRe, getReplaceValue()));
}
});
if (spacesAfterColon != null && spacesAfterColon.length !== 1) context.report({
node,
messageId: "expectedSpaceAfter",
*fix(fixer) {
yield fixer.replaceTextRange(node.range, code.replace(tupleRe, getReplaceValue()));
}
});
} };
}
});
export { type_named_tuple_spacing_default };

Some files were not shown because too many files have changed in this diff Show More