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

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 };

View File

@@ -0,0 +1,84 @@
import { ast_exports, createRule, getStaticPropertyName, isParenthesised, skipChainExpression } from "../utils.js";
function isCalleeOfNewExpression(node) {
const maybeCallee = node.parent?.type === "ChainExpression" ? node.parent : node;
return maybeCallee.parent?.type === "NewExpression" && maybeCallee.parent.callee === maybeCallee;
}
var wrap_iife_default = createRule({
name: "wrap-iife",
meta: {
type: "layout",
docs: { description: "Require parentheses around immediate `function` invocations" },
schema: [{
type: "string",
enum: [
"outside",
"inside",
"any"
]
}, {
type: "object",
properties: { functionPrototypeMethods: {
type: "boolean",
default: false
} },
additionalProperties: false
}],
fixable: "code",
messages: {
wrapInvocation: "Wrap an immediate function invocation in parentheses.",
wrapExpression: "Wrap only the function expression in parens.",
moveInvocation: "Move the invocation into the parens that contain the function."
}
},
create(context) {
const style = context.options[0] || "outside";
const includeFunctionPrototypeMethods = context.options[1] && context.options[1].functionPrototypeMethods;
const sourceCode = context.sourceCode;
function isWrappedInAnyParens(node) {
return isParenthesised(sourceCode, node);
}
function isWrappedInGroupingParens(node) {
return (0, ast_exports.isParenthesized)(node, sourceCode);
}
function getFunctionNodeFromIIFE(node) {
const callee = skipChainExpression(node.callee);
if (callee.type === "FunctionExpression") return callee;
if (includeFunctionPrototypeMethods && callee.type === "MemberExpression" && callee.object.type === "FunctionExpression" && (getStaticPropertyName(callee) === "call" || getStaticPropertyName(callee) === "apply")) return callee.object;
return null;
}
return { CallExpression(node) {
const innerNode = getFunctionNodeFromIIFE(node);
if (!innerNode) return;
const isCallExpressionWrapped = isWrappedInAnyParens(node);
const isFunctionExpressionWrapped = isWrappedInAnyParens(innerNode);
if (!isCallExpressionWrapped && !isFunctionExpressionWrapped) context.report({
node,
messageId: "wrapInvocation",
fix(fixer) {
const nodeToSurround = style === "inside" ? innerNode : node;
return fixer.replaceText(nodeToSurround, `(${sourceCode.getText(nodeToSurround)})`);
}
});
else if (style === "inside" && !isFunctionExpressionWrapped) context.report({
node,
messageId: "wrapExpression",
fix(fixer) {
if (isWrappedInGroupingParens(node) && !isCalleeOfNewExpression(node)) {
const parenAfter = sourceCode.getTokenAfter(node);
return fixer.replaceTextRange([innerNode.range[1], parenAfter.range[1]], `)${sourceCode.getText().slice(innerNode.range[1], parenAfter.range[0])}`);
}
return fixer.replaceText(innerNode, `(${sourceCode.getText(innerNode)})`);
}
});
else if (style === "outside" && !isCallExpressionWrapped) context.report({
node,
messageId: "moveInvocation",
fix(fixer) {
const parenAfter = sourceCode.getTokenAfter(innerNode);
return fixer.replaceTextRange([parenAfter.range[0], node.range[1]], `${sourceCode.getText().slice(parenAfter.range[1], node.range[1])})`);
}
});
} };
}
});
export { wrap_iife_default };

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