feat: Phase 1 — extract the client (web SPA + desktop) to dtoro/oikos-web
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled

Problem: the hexagonal refactor churns the backend tree for nine more
phases; the UI delivery stack (web/ SPA, cmd/desktop Wails wrapper,
compose/web image) must move to its own repo first so doc/layout
rewrites land once on a backend-only tree.

Change:
- New repo git.hubris.network/dtoro/oikos-web (v0.33.0): web/, desktop/
  (updateURL repointed to oikos-web releases), compose/, own CI (web +
  desktop jobs), own deploy script (CI-green gate, TOCTOU guard,
  version-tagged images, prune-to-3), own webhook receiver on :9798 +
  launchd unit, own compose project publishing the same 8091:80.
- Cutover executed on mac-mini in order: oikos stack's web service
  stopped+removed, oikos-web project brought up on 8091; outer Caddy
  untouched (targets the published port) — serving + Authentik flow +
  /wails 404 quirk verified post-cutover.
- Stripped from oikos: web/, cmd/desktop/, compose/web/, desktop CI
  workflow, ci.yml web job, Makefile ui/desktop/desktop-package/install
  targets, the compose web service, oikos-web from deploy.sh's fallback
  prune list; wails + go-keyring dropped from go.mod, vendor synced.
- README / CONTRIBUTING / AGENTS.md / .agents dev+operations docs now
  point at the new repo; mbse + mascot design docs carry a path note.

Risk: production SPA serving depends on the new pipeline now; rollback
is versioned-image re-up of the old web service from a pre-split
checkout (port 8091). Desktop builds installed before the split still
check dtoro/oikos releases — one manual reinstall, noted in the
oikos-web release notes.

Verification: go vet, make test (race), make generate-check, golangci
(no new findings; baseline down 400→365); post-cutover curls —
localhost:8091 200, /wails/runtime.js 404, outer Caddy 302 Authentik.
This commit is contained in:
2026-08-15 22:27:52 +02:00
parent e074f04bdf
commit d4d99a7473
18854 changed files with 2615729 additions and 173735 deletions

View File

@@ -0,0 +1,3 @@
import type { Linter } from 'eslint';
declare const config: Linter.LegacyConfig;
export = config;

View File

@@ -0,0 +1,18 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
const node_path_1 = __importDefault(require("node:path"));
const rules_1 = require("../utils/rules");
const base = require.resolve('./base');
const baseExtend = node_path_1.default.extname(`${base}`) === '.ts' ? 'plugin:svelte/base' : base;
const config = {
extends: [baseExtend],
rules: Object.fromEntries(rules_1.rules
.map((rule) => [`svelte/${rule.meta.docs.ruleName}`, 'error'])
.filter(([ruleName]) => ![
// Does not work without options.
'svelte/no-restricted-html-elements'
].includes(ruleName)))
};
module.exports = config;

View File

@@ -0,0 +1,3 @@
import type { Linter } from 'eslint';
declare const config: Linter.LegacyConfig;
export = config;

View File

@@ -0,0 +1,21 @@
"use strict";
const config = {
plugins: ['svelte'],
overrides: [
{
files: ['*.svelte'],
parser: require.resolve('svelte-eslint-parser'),
rules: {
// ESLint core rules known to cause problems with `.svelte`.
'no-inner-declarations': 'off', // The AST generated by svelte-eslint-parser will false positives in it rule because the root node of the script is not the `Program`.
// "no-irregular-whitespace": "off",
// Self assign is one of way to update reactive value in Svelte.
'no-self-assign': 'off',
// eslint-plugin-svelte rules
'svelte/comment-directive': 'error',
'svelte/system': 'error'
}
}
]
};
module.exports = config;

View File

@@ -0,0 +1,3 @@
import type { Linter } from 'eslint';
declare const config: Linter.Config[];
export default config;

View File

@@ -0,0 +1,20 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const rules_1 = require("../../utils/rules");
const base_1 = __importDefault(require("./base"));
const config = [
...base_1.default,
{
name: 'svelte:all:rules',
rules: Object.fromEntries(rules_1.rules
.map((rule) => [`svelte/${rule.meta.docs.ruleName}`, 'error'])
.filter(([ruleName]) => ![
// Does not work without options.
'svelte/no-restricted-html-elements'
].includes(ruleName)))
}
];
exports.default = config;

View File

@@ -0,0 +1,3 @@
import type { Linter } from 'eslint';
declare const config: Linter.Config[];
export default config;

View File

@@ -0,0 +1,33 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const config = [
{
name: 'svelte:base:setup-plugin',
plugins: {
get svelte() {
// eslint-disable-next-line @typescript-eslint/no-require-imports -- ignore
return require('../../index');
}
}
},
{
name: 'svelte:base:setup-for-svelte',
files: ['*.svelte', '**/*.svelte'],
languageOptions: {
// eslint-disable-next-line @typescript-eslint/no-require-imports -- ignore
parser: require('svelte-eslint-parser')
},
rules: {
// ESLint core rules known to cause problems with `.svelte`.
'no-inner-declarations': 'off', // The AST generated by svelte-eslint-parser will false positives in it rule because the root node of the script is not the `Program`.
// "no-irregular-whitespace": "off",
// Self assign is one of way to update reactive value in Svelte.
'no-self-assign': 'off',
// eslint-plugin-svelte rules
'svelte/comment-directive': 'error',
'svelte/system': 'error'
},
processor: 'svelte/svelte'
}
];
exports.default = config;

View File

@@ -0,0 +1,3 @@
import type { Linter } from 'eslint';
declare const config: Linter.Config[];
export default config;

View File

@@ -0,0 +1,28 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const base_1 = __importDefault(require("./base"));
const config = [
...base_1.default,
{
name: 'svelte:prettier:turn-off-rules',
rules: {
// eslint-plugin-svelte rules
'svelte/first-attribute-linebreak': 'off',
'svelte/html-closing-bracket-new-line': 'off',
'svelte/html-closing-bracket-spacing': 'off',
'svelte/html-quotes': 'off',
'svelte/html-self-closing': 'off',
'svelte/indent': 'off',
'svelte/max-attributes-per-line': 'off',
'svelte/mustache-spacing': 'off',
'svelte/no-spaces-around-equal-signs-in-attribute': 'off',
'svelte/no-trailing-spaces': 'off',
'svelte/shorthand-attribute': 'off',
'svelte/shorthand-directive': 'off'
}
}
];
exports.default = config;

View File

@@ -0,0 +1,3 @@
import type { Linter } from 'eslint';
declare const config: Linter.Config[];
export default config;

View File

@@ -0,0 +1,30 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const base_1 = __importDefault(require("./base"));
const config = [
...base_1.default,
{
name: 'svelte:recommended:rules',
rules: {
// eslint-plugin-svelte rules
'svelte/comment-directive': 'error',
'svelte/no-at-debug-tags': 'warn',
'svelte/no-at-html-tags': 'error',
'svelte/no-dupe-else-if-blocks': 'error',
'svelte/no-dupe-style-properties': 'error',
'svelte/no-dynamic-slot-name': 'error',
'svelte/no-inner-declarations': 'error',
'svelte/no-not-function-handler': 'error',
'svelte/no-object-in-text-mustaches': 'error',
'svelte/no-shorthand-style-property-overrides': 'error',
'svelte/no-unknown-style-directive-property': 'error',
'svelte/no-unused-svelte-ignore': 'error',
'svelte/system': 'error',
'svelte/valid-compile': 'error'
}
}
];
exports.default = config;

View File

@@ -0,0 +1,3 @@
import type { Linter } from 'eslint';
declare const config: Linter.LegacyConfig;
export = config;

View File

@@ -0,0 +1,26 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
const node_path_1 = __importDefault(require("node:path"));
const base = require.resolve('./base');
const baseExtend = node_path_1.default.extname(`${base}`) === '.ts' ? 'plugin:svelte/base' : base;
const config = {
extends: [baseExtend],
rules: {
// eslint-plugin-svelte rules
'svelte/first-attribute-linebreak': 'off',
'svelte/html-closing-bracket-new-line': 'off',
'svelte/html-closing-bracket-spacing': 'off',
'svelte/html-quotes': 'off',
'svelte/html-self-closing': 'off',
'svelte/indent': 'off',
'svelte/max-attributes-per-line': 'off',
'svelte/mustache-spacing': 'off',
'svelte/no-spaces-around-equal-signs-in-attribute': 'off',
'svelte/no-trailing-spaces': 'off',
'svelte/shorthand-attribute': 'off',
'svelte/shorthand-directive': 'off'
}
};
module.exports = config;

View File

@@ -0,0 +1,3 @@
import type { Linter } from 'eslint';
declare const config: Linter.LegacyConfig;
export = config;

View File

@@ -0,0 +1,28 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
const node_path_1 = __importDefault(require("node:path"));
const base = require.resolve('./base');
const baseExtend = node_path_1.default.extname(`${base}`) === '.ts' ? 'plugin:svelte/base' : base;
const config = {
extends: [baseExtend],
rules: {
// eslint-plugin-svelte rules
'svelte/comment-directive': 'error',
'svelte/no-at-debug-tags': 'warn',
'svelte/no-at-html-tags': 'error',
'svelte/no-dupe-else-if-blocks': 'error',
'svelte/no-dupe-style-properties': 'error',
'svelte/no-dynamic-slot-name': 'error',
'svelte/no-inner-declarations': 'error',
'svelte/no-not-function-handler': 'error',
'svelte/no-object-in-text-mustaches': 'error',
'svelte/no-shorthand-style-property-overrides': 'error',
'svelte/no-unknown-style-directive-property': 'error',
'svelte/no-unused-svelte-ignore': 'error',
'svelte/system': 'error',
'svelte/valid-compile': 'error'
}
};
module.exports = config;

24
web/node_modules/eslint-plugin-svelte/lib/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,24 @@
import './rule-types';
import type { RuleModule } from './types';
import * as processor from './processor';
declare const _default: {
meta: typeof processor.meta;
configs: {
base: import("eslint").Linter.LegacyConfig<import("eslint").Linter.RulesRecord, import("eslint").Linter.RulesRecord>;
recommended: import("eslint").Linter.LegacyConfig<import("eslint").Linter.RulesRecord, import("eslint").Linter.RulesRecord>;
prettier: import("eslint").Linter.LegacyConfig<import("eslint").Linter.RulesRecord, import("eslint").Linter.RulesRecord>;
all: import("eslint").Linter.LegacyConfig<import("eslint").Linter.RulesRecord, import("eslint").Linter.RulesRecord>;
'flat/base': import("eslint").Linter.Config<import("eslint").Linter.RulesRecord>[];
'flat/recommended': import("eslint").Linter.Config<import("eslint").Linter.RulesRecord>[];
'flat/prettier': import("eslint").Linter.Config<import("eslint").Linter.RulesRecord>[];
'flat/all': import("eslint").Linter.Config<import("eslint").Linter.RulesRecord>[];
};
rules: {
[key: string]: RuleModule;
};
processors: {
'.svelte': typeof processor;
svelte: typeof processor;
};
};
export = _default;

72
web/node_modules/eslint-plugin-svelte/lib/index.js generated vendored Normal file
View File

@@ -0,0 +1,72 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
require("./rule-types");
const rules_1 = require("./utils/rules");
const base_1 = __importDefault(require("./configs/base"));
const recommended_1 = __importDefault(require("./configs/recommended"));
const prettier_1 = __importDefault(require("./configs/prettier"));
const all_1 = __importDefault(require("./configs/all"));
const base_2 = __importDefault(require("./configs/flat/base"));
const recommended_2 = __importDefault(require("./configs/flat/recommended"));
const prettier_2 = __importDefault(require("./configs/flat/prettier"));
const all_2 = __importDefault(require("./configs/flat/all"));
const processor = __importStar(require("./processor"));
const meta = __importStar(require("./meta"));
const configs = {
base: base_1.default,
recommended: recommended_1.default,
prettier: prettier_1.default,
all: all_1.default,
'flat/base': base_2.default,
'flat/recommended': recommended_2.default,
'flat/prettier': prettier_2.default,
'flat/all': all_2.default
};
const rules = rules_1.rules.reduce((obj, r) => {
obj[r.meta.docs.ruleName] = r;
return obj;
}, {});
module.exports = {
meta,
configs,
rules,
processors: {
'.svelte': processor,
svelte: processor
}
};

2
web/node_modules/eslint-plugin-svelte/lib/meta.d.ts generated vendored Normal file
View File

@@ -0,0 +1,2 @@
export declare const name = "eslint-plugin-svelte";
export declare const version = "2.46.1";

8
web/node_modules/eslint-plugin-svelte/lib/meta.js generated vendored Normal file
View File

@@ -0,0 +1,8 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.version = exports.name = void 0;
// IMPORTANT!
// This file has been automatically generated,
// in order to update its content execute "pnpm run update"
exports.name = 'eslint-plugin-svelte';
exports.version = '2.46.1';

View File

@@ -0,0 +1,7 @@
import type { Linter } from 'eslint';
export * as meta from '../meta';
/** preprocess */
export declare function preprocess(code: string, filename: string): string[];
/** postprocess */
export declare function postprocess([messages]: Linter.LintMessage[][], filename: string): Linter.LintMessage[];
export declare const supportsAutofix = true;

View File

@@ -0,0 +1,67 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.supportsAutofix = exports.meta = void 0;
exports.preprocess = preprocess;
exports.postprocess = postprocess;
const shared_1 = require("../shared");
exports.meta = __importStar(require("../meta"));
/** preprocess */
function preprocess(code, filename) {
if (filename) {
(0, shared_1.beginShared)(filename);
}
return [code];
}
/** postprocess */
function postprocess([messages], filename) {
const shared = (0, shared_1.terminateShared)(filename);
if (shared) {
return filter(messages, shared);
}
return messages;
}
exports.supportsAutofix = true;
/** Filter */
function filter(messages, shared) {
if (shared.commentDirectives.length === 0) {
return messages;
}
let filteredMessages = messages;
for (const cd of shared.commentDirectives) {
filteredMessages = cd.filterMessages(filteredMessages);
}
return filteredMessages;
}

View File

@@ -0,0 +1,518 @@
import type { Linter } from 'eslint';
declare module 'eslint' {
namespace Linter {
interface RulesRecord extends RuleOptions {
}
}
}
export interface RuleOptions {
/**
* disallow conditionals where the type is always truthy or always falsy
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/@typescript-eslint/no-unnecessary-condition/
* @deprecated
*/
'svelte/@typescript-eslint/no-unnecessary-condition'?: Linter.RuleEntry<SvelteTypescriptEslintNoUnnecessaryCondition>;
/**
* disallows the use of languages other than those specified in the configuration for the lang attribute of `<script>` and `<style>` blocks.
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/block-lang/
*/
'svelte/block-lang'?: Linter.RuleEntry<SvelteBlockLang>;
/**
* disallow usage of button without an explicit type attribute
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/button-has-type/
*/
'svelte/button-has-type'?: Linter.RuleEntry<SvelteButtonHasType>;
/**
* support comment-directives in HTML template
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/comment-directive/
*/
'svelte/comment-directive'?: Linter.RuleEntry<SvelteCommentDirective>;
/**
* derived store should use same variable names between values and callback
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/derived-has-same-inputs-outputs/
*/
'svelte/derived-has-same-inputs-outputs'?: Linter.RuleEntry<[]>;
/**
* require slot type declaration using the `$$Slots` interface
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/experimental-require-slot-types/
*/
'svelte/experimental-require-slot-types'?: Linter.RuleEntry<[]>;
/**
* require the strictEvents attribute on `<script>` tags
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/experimental-require-strict-events/
*/
'svelte/experimental-require-strict-events'?: Linter.RuleEntry<[]>;
/**
* enforce the location of first attribute
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/first-attribute-linebreak/
*/
'svelte/first-attribute-linebreak'?: Linter.RuleEntry<SvelteFirstAttributeLinebreak>;
/**
* Require or disallow a line break before tag's closing brackets
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/html-closing-bracket-new-line/
*/
'svelte/html-closing-bracket-new-line'?: Linter.RuleEntry<SvelteHtmlClosingBracketNewLine>;
/**
* require or disallow a space before tag's closing brackets
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/html-closing-bracket-spacing/
*/
'svelte/html-closing-bracket-spacing'?: Linter.RuleEntry<SvelteHtmlClosingBracketSpacing>;
/**
* enforce quotes style of HTML attributes
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/html-quotes/
*/
'svelte/html-quotes'?: Linter.RuleEntry<SvelteHtmlQuotes>;
/**
* enforce self-closing style
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/html-self-closing/
*/
'svelte/html-self-closing'?: Linter.RuleEntry<SvelteHtmlSelfClosing>;
/**
* enforce consistent indentation
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/indent/
*/
'svelte/indent'?: Linter.RuleEntry<SvelteIndent>;
/**
* Svelte runtime prevents calling the same reactive statement twice in a microtask. But between different microtask, it doesn't prevent.
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/infinite-reactive-loop/
*/
'svelte/infinite-reactive-loop'?: Linter.RuleEntry<[]>;
/**
* enforce the maximum number of attributes per line
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/max-attributes-per-line/
*/
'svelte/max-attributes-per-line'?: Linter.RuleEntry<SvelteMaxAttributesPerLine>;
/**
* enforce unified spacing in mustache
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/mustache-spacing/
*/
'svelte/mustache-spacing'?: Linter.RuleEntry<SvelteMustacheSpacing>;
/**
* disallow the use of `{@debug}`
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-at-debug-tags/
*/
'svelte/no-at-debug-tags'?: Linter.RuleEntry<[]>;
/**
* disallow use of `{@html}` to prevent XSS attack
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-at-html-tags/
*/
'svelte/no-at-html-tags'?: Linter.RuleEntry<[]>;
/**
* disallow DOM manipulating
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-dom-manipulating/
*/
'svelte/no-dom-manipulating'?: Linter.RuleEntry<[]>;
/**
* disallow duplicate conditions in `{#if}` / `{:else if}` chains
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-dupe-else-if-blocks/
*/
'svelte/no-dupe-else-if-blocks'?: Linter.RuleEntry<[]>;
/**
* disallow duplicate `on:` directives
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-dupe-on-directives/
*/
'svelte/no-dupe-on-directives'?: Linter.RuleEntry<[]>;
/**
* disallow duplicate style properties
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-dupe-style-properties/
*/
'svelte/no-dupe-style-properties'?: Linter.RuleEntry<[]>;
/**
* disallow duplicate `use:` directives
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-dupe-use-directives/
*/
'svelte/no-dupe-use-directives'?: Linter.RuleEntry<[]>;
/**
* disallow dynamic slot name
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-dynamic-slot-name/
*/
'svelte/no-dynamic-slot-name'?: Linter.RuleEntry<[]>;
/**
* disallow exporting load functions in `*.svelte` module in SvelteKit page components.
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-export-load-in-svelte-module-in-kit-pages/
*/
'svelte/no-export-load-in-svelte-module-in-kit-pages'?: Linter.RuleEntry<[]>;
/**
* disallow wrapping single reactive statements in curly braces
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-extra-reactive-curlies/
*/
'svelte/no-extra-reactive-curlies'?: Linter.RuleEntry<[]>;
/**
* disallow using goto() without the base path
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-goto-without-base/
*/
'svelte/no-goto-without-base'?: Linter.RuleEntry<[]>;
/**
* disallow ignoring the unsubscribe method returned by the `subscribe()` on Svelte stores.
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-ignored-unsubscribe/
*/
'svelte/no-ignored-unsubscribe'?: Linter.RuleEntry<[]>;
/**
* disallow reactive statements that don't reference reactive values.
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-immutable-reactive-statements/
*/
'svelte/no-immutable-reactive-statements'?: Linter.RuleEntry<[]>;
/**
* disallow attributes and directives that produce inline styles
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-inline-styles/
*/
'svelte/no-inline-styles'?: Linter.RuleEntry<SvelteNoInlineStyles>;
/**
* disallow variable or `function` declarations in nested blocks
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-inner-declarations/
*/
'svelte/no-inner-declarations'?: Linter.RuleEntry<SvelteNoInnerDeclarations>;
/**
* Warns against the use of `$inspect` directive
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-inspect/
*/
'svelte/no-inspect'?: Linter.RuleEntry<[]>;
/**
* disallow use of not function in event handler
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-not-function-handler/
*/
'svelte/no-not-function-handler'?: Linter.RuleEntry<[]>;
/**
* disallow objects in text mustache interpolation
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-object-in-text-mustaches/
*/
'svelte/no-object-in-text-mustaches'?: Linter.RuleEntry<[]>;
/**
* it's not necessary to define functions in reactive statements
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-reactive-functions/
*/
'svelte/no-reactive-functions'?: Linter.RuleEntry<[]>;
/**
* don't assign literal values in reactive statements
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-reactive-literals/
*/
'svelte/no-reactive-literals'?: Linter.RuleEntry<[]>;
/**
* disallow reassigning reactive values
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-reactive-reassign/
*/
'svelte/no-reactive-reassign'?: Linter.RuleEntry<SvelteNoReactiveReassign>;
/**
* disallow specific HTML elements
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-restricted-html-elements/
*/
'svelte/no-restricted-html-elements'?: Linter.RuleEntry<SvelteNoRestrictedHtmlElements>;
/**
* disallow shorthand style properties that override related longhand properties
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-shorthand-style-property-overrides/
*/
'svelte/no-shorthand-style-property-overrides'?: Linter.RuleEntry<[]>;
/**
* disallow spaces around equal signs in attribute
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-spaces-around-equal-signs-in-attribute/
*/
'svelte/no-spaces-around-equal-signs-in-attribute'?: Linter.RuleEntry<[]>;
/**
* disallow using async/await inside svelte stores because it causes issues with the auto-unsubscribing features
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-store-async/
*/
'svelte/no-store-async'?: Linter.RuleEntry<[]>;
/**
* svelte/internal will be removed in Svelte 6.
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-svelte-internal/
*/
'svelte/no-svelte-internal'?: Linter.RuleEntry<[]>;
/**
* disallow `target="_blank"` attribute without `rel="noopener noreferrer"`
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-target-blank/
*/
'svelte/no-target-blank'?: Linter.RuleEntry<SvelteNoTargetBlank>;
/**
* disallow trailing whitespace at the end of lines
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-trailing-spaces/
*/
'svelte/no-trailing-spaces'?: Linter.RuleEntry<SvelteNoTrailingSpaces>;
/**
* disallow unknown `style:property`
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-unknown-style-directive-property/
*/
'svelte/no-unknown-style-directive-property'?: Linter.RuleEntry<SvelteNoUnknownStyleDirectiveProperty>;
/**
* disallow the use of a class in the template without a corresponding style
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-unused-class-name/
*/
'svelte/no-unused-class-name'?: Linter.RuleEntry<SvelteNoUnusedClassName>;
/**
* disallow unused svelte-ignore comments
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-unused-svelte-ignore/
*/
'svelte/no-unused-svelte-ignore'?: Linter.RuleEntry<[]>;
/**
* disallow unnecessary mustache interpolations
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-useless-mustaches/
*/
'svelte/no-useless-mustaches'?: Linter.RuleEntry<SvelteNoUselessMustaches>;
/**
* require class directives instead of ternary expressions
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/prefer-class-directive/
*/
'svelte/prefer-class-directive'?: Linter.RuleEntry<SveltePreferClassDirective>;
/**
* destructure values from object stores for better change tracking & fewer redraws
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/prefer-destructured-store-props/
*/
'svelte/prefer-destructured-store-props'?: Linter.RuleEntry<[]>;
/**
* require style directives instead of style attribute
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/prefer-style-directive/
*/
'svelte/prefer-style-directive'?: Linter.RuleEntry<[]>;
/**
* require keyed `{#each}` block
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/require-each-key/
*/
'svelte/require-each-key'?: Linter.RuleEntry<[]>;
/**
* require type parameters for `createEventDispatcher`
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/require-event-dispatcher-types/
*/
'svelte/require-event-dispatcher-types'?: Linter.RuleEntry<[]>;
/**
* require style attributes that can be optimized
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/require-optimized-style-attribute/
*/
'svelte/require-optimized-style-attribute'?: Linter.RuleEntry<[]>;
/**
* store callbacks must use `set` param
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/require-store-callbacks-use-set-param/
*/
'svelte/require-store-callbacks-use-set-param'?: Linter.RuleEntry<[]>;
/**
* disallow to use of the store itself as an operand. Need to use $ prefix or get function.
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/require-store-reactive-access/
*/
'svelte/require-store-reactive-access'?: Linter.RuleEntry<[]>;
/**
* require initial value in store
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/require-stores-init/
*/
'svelte/require-stores-init'?: Linter.RuleEntry<[]>;
/**
* enforce use of shorthand syntax in attribute
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/shorthand-attribute/
*/
'svelte/shorthand-attribute'?: Linter.RuleEntry<SvelteShorthandAttribute>;
/**
* enforce use of shorthand syntax in directives
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/shorthand-directive/
*/
'svelte/shorthand-directive'?: Linter.RuleEntry<SvelteShorthandDirective>;
/**
* enforce order of attributes
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/sort-attributes/
*/
'svelte/sort-attributes'?: Linter.RuleEntry<SvelteSortAttributes>;
/**
* enforce consistent spacing after the `<!--` and before the `-->` in a HTML comment
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/spaced-html-comment/
*/
'svelte/spaced-html-comment'?: Linter.RuleEntry<SvelteSpacedHtmlComment>;
/**
* system rule for working this plugin
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/system/
*/
'svelte/system'?: Linter.RuleEntry<[]>;
/**
* disallow warnings when compiling.
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/valid-compile/
*/
'svelte/valid-compile'?: Linter.RuleEntry<SvelteValidCompile>;
/**
* enforce keys to use variables defined in the `{#each}` block
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/valid-each-key/
*/
'svelte/valid-each-key'?: Linter.RuleEntry<[]>;
/**
* disallow props other than data or errors in SvelteKit page components.
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/valid-prop-names-in-kit-pages/
*/
'svelte/valid-prop-names-in-kit-pages'?: Linter.RuleEntry<[]>;
}
type SvelteTypescriptEslintNoUnnecessaryCondition = [] | [
{
allowConstantLoopConditions?: boolean;
allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing?: boolean;
}
];
type SvelteBlockLang = [] | [
{
enforceScriptPresent?: boolean;
enforceStylePresent?: boolean;
script?: ((string | null) | [(string | null), ...((string | null))[]]);
style?: ((string | null) | [(string | null), ...((string | null))[]]);
}
];
type SvelteButtonHasType = [] | [
{
button?: boolean;
submit?: boolean;
reset?: boolean;
}
];
type SvelteCommentDirective = [] | [
{
reportUnusedDisableDirectives?: boolean;
}
];
type SvelteFirstAttributeLinebreak = [] | [
{
multiline?: ("below" | "beside");
singleline?: ("below" | "beside");
}
];
type SvelteHtmlClosingBracketNewLine = [] | [
{
singleline?: ("always" | "never");
multiline?: ("always" | "never");
selfClosingTag?: {
singleline?: ("always" | "never");
multiline?: ("always" | "never");
};
}
];
type SvelteHtmlClosingBracketSpacing = [] | [
{
startTag?: ("always" | "never" | "ignore");
endTag?: ("always" | "never" | "ignore");
selfClosingTag?: ("always" | "never" | "ignore");
}
];
type SvelteHtmlQuotes = [] | [
{
prefer?: ("double" | "single");
dynamic?: {
quoted?: boolean;
avoidInvalidUnquotedInHTML?: boolean;
};
}
];
type SvelteHtmlSelfClosing = [] | [
({
void?: ("never" | "always" | "ignore");
normal?: ("never" | "always" | "ignore");
foreign?: ("never" | "always" | "ignore");
component?: ("never" | "always" | "ignore");
svelte?: ("never" | "always" | "ignore");
} | ("all" | "html" | "none"))
];
type SvelteIndent = [] | [
{
indent?: (number | "tab");
indentScript?: boolean;
switchCase?: number;
alignAttributesVertically?: boolean;
ignoredNodes?: (string & {
[k: string]: unknown | undefined;
} & {
[k: string]: unknown | undefined;
})[];
}
];
type SvelteMaxAttributesPerLine = [] | [
{
multiline?: number;
singleline?: number;
}
];
type SvelteMustacheSpacing = [] | [
{
textExpressions?: ("never" | "always");
attributesAndProps?: ("never" | "always");
directiveExpressions?: ("never" | "always");
tags?: {
openingBrace?: ("never" | "always");
closingBrace?: ("never" | "always" | "always-after-expression");
};
}
];
type SvelteNoInlineStyles = [] | [
{
allowTransitions?: boolean;
}
];
type SvelteNoInnerDeclarations = [] | [("functions" | "both")] | [
("functions" | "both"),
{
blockScopedFunctions?: ("allow" | "disallow");
}
];
type SvelteNoReactiveReassign = [] | [
{
props?: boolean;
}
];
type SvelteNoRestrictedHtmlElements = [
(string | {
elements?: [string, ...(string)[]];
message?: string;
}),
...((string | {
elements?: [string, ...(string)[]];
message?: string;
}))[]
];
type SvelteNoTargetBlank = [] | [
{
allowReferrer?: boolean;
enforceDynamicLinks?: ("always" | "never");
}
];
type SvelteNoTrailingSpaces = [] | [
{
skipBlankLines?: boolean;
ignoreComments?: boolean;
}
];
type SvelteNoUnknownStyleDirectiveProperty = [] | [
{
ignoreProperties?: [string, ...(string)[]];
ignorePrefixed?: boolean;
}
];
type SvelteNoUnusedClassName = [] | [
{
allowedClassNames?: string[];
}
];
type SvelteNoUselessMustaches = [] | [
{
ignoreIncludesComment?: boolean;
ignoreStringEscape?: boolean;
}
];
type SveltePreferClassDirective = [] | [
{
prefer?: ("always" | "empty");
}
];
type SvelteShorthandAttribute = [] | [
{
prefer?: ("always" | "never");
}
];
type SvelteShorthandDirective = [] | [
{
prefer?: ("always" | "never");
}
];
type SvelteSortAttributes = [] | [
{
order?: (string | [string, ...(string)[]] | {
match: (string | [string, ...(string)[]]);
sort: ("alphabetical" | "ignore");
})[];
alphabetical?: boolean;
}
];
type SvelteSpacedHtmlComment = [] | [("always" | "never")];
type SvelteValidCompile = [] | [
{
ignoreWarnings?: boolean;
}
];
export {};

View File

@@ -0,0 +1,5 @@
"use strict";
// IMPORTANT!
// This file has been automatically generated,
// in order to update its content execute "pnpm run update"
Object.defineProperty(exports, "__esModule", { value: true });

View File

@@ -0,0 +1,2 @@
declare const _default: import("../../types").RuleModule;
export default _default;

View File

@@ -0,0 +1,590 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../../utils");
const ts_utils_1 = require("../../utils/ts-utils");
const compat_1 = require("../../utils/compat");
/**
* Returns all types of a union type or an array containing `type` itself if it's no union type.
* This method is heavily inspired by tsutils. https://github.com/ajafff/tsutils
* The MIT License (MIT) Copyright (c) 2017 Klaus Meinhardt
* https://github.com/ajafff/tsutils/blob/master/LICENSE
*/
function unionTypeParts(type) {
return [...iterate(type)];
/**
* iterate
*/
function* iterate(t) {
if (t.isUnion()) {
for (const type of t.types) {
yield* iterate(type);
}
}
else {
yield t;
}
}
}
/**
* Check whether the given type can be a falsy type or not.
*/
function isPossiblyFalsy(type, tsTools) {
return (unionTypeParts(type)
// PossiblyFalsy flag includes literal values, so exclude ones that
// are definitely truthy
.filter((t) => !(0, ts_utils_1.isTruthyLiteral)(t, tsTools))
.some((type) => (0, ts_utils_1.isPossiblyFalsyType)(type, tsTools.ts)));
}
/**
* Check whether the given type can be a truthy type or not.
*/
function isPossiblyTruthy(type, tsTools) {
return unionTypeParts(type).some((type) => !(0, ts_utils_1.isFalsyType)(type, tsTools));
}
/**
* Check whether the given type can be a nullish type or not.
*/
function isPossiblyNullish(type, tsTools) {
return (0, ts_utils_1.isNullableType)(type, tsTools.ts);
}
/**
* Check whether the given type is a nullish type or not.
*/
function isAlwaysNullish(type, tsTools) {
return (0, ts_utils_1.isNullishType)(type, tsTools.ts);
}
/**
* Check whether the given type is a literal type or not.
*/
function isLiteral(type, tsTools) {
return ((0, ts_utils_1.isBooleanLiteralType)(type, tsTools.ts) || (0, ts_utils_1.isNullishType)(type, tsTools.ts) || type.isLiteral());
}
exports.default = (0, utils_1.createRule)('@typescript-eslint/no-unnecessary-condition', {
meta: {
docs: {
description: 'disallow conditionals where the type is always truthy or always falsy',
category: 'Extension Rules',
recommended: false,
extensionRule: {
plugin: '@typescript-eslint/eslint-plugin',
url: 'https://typescript-eslint.io/rules/no-unnecessary-condition/'
}
},
schema: [
{
type: 'object',
properties: {
allowConstantLoopConditions: {
description: 'Whether to ignore constant loop conditions, such as `while (true)`.',
type: 'boolean'
},
allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: {
description: 'Whether to not error when running with a tsconfig that has strictNullChecks turned.',
type: 'boolean'
}
},
additionalProperties: false
}
],
fixable: 'code',
messages: {
alwaysTruthy: 'Unnecessary conditional, value is always truthy.',
alwaysFalsy: 'Unnecessary conditional, value is always falsy.',
alwaysTruthyFunc: 'This callback should return a conditional, but return is always truthy.',
alwaysFalsyFunc: 'This callback should return a conditional, but return is always falsy.',
neverNullish: 'Unnecessary conditional, expected left-hand side of `??` operator to be possibly null or undefined.',
alwaysNullish: 'Unnecessary conditional, left-hand side of `??` operator is always `null` or `undefined`.',
literalBooleanExpression: 'Unnecessary conditional, both sides of the expression are literal values.',
noOverlapBooleanExpression: 'Unnecessary conditional, the types have no overlap.',
never: 'Unnecessary conditional, value is `never`.',
neverOptionalChain: 'Unnecessary optional chain on a non-nullish value.',
noStrictNullCheck: 'This rule requires the `strictNullChecks` compiler option to be turned on to function correctly.'
},
type: 'suggestion', // "problem", or "layout",
deprecated: true,
replacedBy: {
note: 'This rule is no longer needed when using svelte-eslint-parser>=v0.19.0.'
}
},
create(context) {
const { allowConstantLoopConditions = false, allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing = false } = (context.options[0] || {});
const tools = (0, ts_utils_1.getTypeScriptTools)(context);
if (!tools) {
return {};
}
const { service, ts } = tools;
const checker = service.program.getTypeChecker();
const sourceCode = (0, compat_1.getSourceCode)(context);
const compilerOptions = service.program.getCompilerOptions();
const isStrictNullChecks = compilerOptions.strict
? compilerOptions.strictNullChecks !== false
: compilerOptions.strictNullChecks;
if (!isStrictNullChecks && allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing !== true) {
context.report({
loc: {
start: { line: 0, column: 0 },
end: { line: 0, column: 0 }
},
messageId: 'noStrictNullCheck'
});
}
const mutableVarReferenceIds = [];
const scriptElements = [];
let inSvelteReactiveStatement = false;
// Extract references to mutable variables in the root scope.
for (const scope of [
sourceCode.scopeManager.globalScope,
sourceCode.scopeManager.globalScope?.childScopes.find((scope) => scope.type === 'module')
]) {
if (!scope)
continue;
for (const variable of scope.variables) {
if (variable.defs.some((def) => def.type === 'Variable' && (def.parent.kind === 'var' || def.parent.kind === 'let'))) {
for (const reference of variable.references) {
mutableVarReferenceIds.push(reference.identifier);
}
}
}
}
// Extract <script> ranges.
for (const body of sourceCode.ast.body) {
if (body.type === 'SvelteScriptElement') {
scriptElements.push(body);
}
}
/**
* Checks whether the given expression node is in Svelte reactive scope
* and the variables that make up the given expression node use
* mutable variables declared in component root scope.
*/
function hasSvelteReactiveVar(node) {
const inReactiveScope = inSvelteReactiveStatement ||
(scriptElements.length &&
scriptElements.every((elem) => node.range[1] <= elem.range[0] || elem.range[1] <= node.range[0]));
if (!inReactiveScope) {
// The given expression node is neither in a reactive scope nor in a template scope.
return false;
}
return mutableVarReferenceIds.some((id) => node.range[0] <= id.range[0] && id.range[1] <= node.range[1]);
}
/** Get the TS type from ES Node */
function getNodeType(node) {
const tsNode = service.esTreeNodeToTSNodeMap.get(node);
return tsNode && (0, ts_utils_1.getConstrainedTypeAtLocation)(checker, tsNode);
}
/**
* Check whether the given node is an array type or not.
*/
function nodeIsArrayType(node) {
const nodeType = getNodeType(node);
if (!nodeType) {
return false;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- isArrayType is an internal API of TS.
return checker.isArrayType(nodeType);
}
/**
* Check whether the given node is an tuple type or not.
*/
function nodeIsTupleType(node) {
const nodeType = getNodeType(node);
return Boolean(nodeType && (0, ts_utils_1.isTupleType)(nodeType, ts));
}
/**
* Check whether the given node is an array index signature or not.
*/
function isArrayIndexExpression(node) {
return (
// Is an index signature
node.type === 'MemberExpression' &&
node.computed &&
// ...into an array type
(nodeIsArrayType(node.object) ||
// ... or a tuple type
(nodeIsTupleType(node.object) &&
// Exception: literal index into a tuple - will have a sound type
node.property.type !== 'Literal')));
}
/**
* Checks if a conditional node is necessary:
* if the type of the node is always true or always false, it's not necessary.
*/
function checkNode(node, isUnaryNotArgument = false) {
if (hasSvelteReactiveVar(node)) {
return;
}
// Check if the node is Unary Negation expression and handle it
if (node.type === 'UnaryExpression' && node.operator === '!') {
checkNode(node.argument, true);
return;
}
// Since typescript array index signature types don't represent the
// possibility of out-of-bounds access, if we're indexing into an array
// just skip the check, to avoid false positives
if (isArrayIndexExpression(node)) {
return;
}
// When checking logical expressions, only check the right side
// as the left side has been checked by checkLogicalExpressionForUnnecessaryConditionals
//
// Unless the node is nullish coalescing, as it's common to use patterns like `nullBool ?? true` to to strict
// boolean checks if we inspect the right here, it'll usually be a constant condition on purpose.
// In this case it's better to inspect the type of the expression as a whole.
if (node.type === 'LogicalExpression' && node.operator !== '??') {
checkNode(node.right);
return;
}
const type = getNodeType(node);
// Conditional is always necessary if it involves:
// `any` or `unknown` or a naked type parameter
if (!type ||
unionTypeParts(type).some((part) => (0, ts_utils_1.isAnyType)(part, ts) || (0, ts_utils_1.isUnknownType)(part, ts) || part.isTypeParameter())) {
return;
}
let messageId = null;
if (unionTypeParts(type).some((part) => (0, ts_utils_1.isNeverType)(part, ts))) {
messageId = 'never';
}
else if (!isPossiblyTruthy(type, tools)) {
messageId = !isUnaryNotArgument ? 'alwaysFalsy' : 'alwaysTruthy';
}
else if (!isPossiblyFalsy(type, tools)) {
messageId = !isUnaryNotArgument ? 'alwaysTruthy' : 'alwaysFalsy';
}
if (messageId) {
context.report({ node, messageId });
}
}
/**
* Checks if a conditional node is necessary from the given lhs of nullish coalescing.
*/
function checkNodeForNullish(node) {
if (hasSvelteReactiveVar(node)) {
return;
}
const type = getNodeType(node);
// Conditional is always necessary if it involves `any` or `unknown`
if (!type || (0, ts_utils_1.isAnyType)(type, ts) || (0, ts_utils_1.isUnknownType)(type, ts)) {
return;
}
let messageId = null;
if (unionTypeParts(type).some((part) => (0, ts_utils_1.isNeverType)(part, ts))) {
messageId = 'never';
}
else if (!isPossiblyNullish(type, tools)) {
// Since typescript array index signature types don't represent the
// possibility of out-of-bounds access, if we're indexing into an array
// just skip the check, to avoid false positives
if (!isArrayIndexExpression(node) &&
!(node.type === 'ChainExpression' &&
node.expression.type !== 'TSNonNullExpression' &&
optionChainContainsOptionArrayIndex(node.expression))) {
messageId = 'neverNullish';
}
}
else if (isAlwaysNullish(type, tools)) {
messageId = 'alwaysNullish';
}
if (messageId) {
context.report({ node, messageId });
}
}
/**
* Checks that a binary expression is necessarily conditional, reports otherwise.
* If both sides of the binary expression are literal values, it's not a necessary condition.
*
* NOTE: It's also unnecessary if the types that don't overlap at all
* but that case is handled by the Typescript compiler itself.
* Known exceptions:
* * https://github.com/microsoft/TypeScript/issues/32627
* * https://github.com/microsoft/TypeScript/issues/37160 (handled)
*/
const BOOL_OPERATORS = new Set(['<', '>', '<=', '>=', '==', '===', '!=', '!==']);
/**
* Checks if a conditional node is necessary from the given binary expression.
*/
function checkIfBinaryExpressionIsNecessaryConditional(node) {
if (hasSvelteReactiveVar(node)) {
return;
}
if (!BOOL_OPERATORS.has(node.operator)) {
return;
}
const leftType = getNodeType(node.left);
const rightType = getNodeType(node.right);
if (!leftType || !rightType) {
return;
}
if (isLiteral(leftType, tools) && isLiteral(rightType, tools)) {
context.report({ node, messageId: 'literalBooleanExpression' });
return;
}
// Workaround for https://github.com/microsoft/TypeScript/issues/37160
if (isStrictNullChecks) {
const UNDEFINED = ts.TypeFlags.Undefined;
const NULL = ts.TypeFlags.Null;
// eslint-disable-next-line func-style -- ignore
const isComparable = (type, f) => {
let flag = f;
// Allow comparison to `any`, `unknown` or a naked type parameter.
flag |= ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.TypeParameter;
// Allow loose comparison to nullish values.
if (node.operator === '==' || node.operator === '!=') {
flag |= NULL | UNDEFINED;
}
return unionTypeParts(type).some((t) => (t.flags & flag) !== 0);
};
if ((leftType.flags === UNDEFINED && !isComparable(rightType, UNDEFINED)) ||
(rightType.flags === UNDEFINED && !isComparable(leftType, UNDEFINED)) ||
(leftType.flags === NULL && !isComparable(rightType, NULL)) ||
(rightType.flags === NULL && !isComparable(leftType, NULL))) {
context.report({ node, messageId: 'noOverlapBooleanExpression' });
}
}
}
/**
* Checks that a logical expression contains a boolean, reports otherwise.
*/
function checkLogicalExpressionForUnnecessaryConditionals(node) {
if (node.operator === '??') {
checkNodeForNullish(node.left);
return;
}
// Only checks the left side, since the right side might not be "conditional" at all.
// The right side will be checked if the LogicalExpression is used in a conditional context
checkNode(node.left);
}
/**
* Checks that a testable expression of a loop is necessarily conditional, reports otherwise.
*/
function checkIfLoopIsNecessaryConditional(node) {
if (node.test === null) {
// e.g. `for(;;)`
return;
}
/**
* Allow:
* while (true) {}
* for (;true;) {}
* do {} while (true)
*/
if (allowConstantLoopConditions) {
const nodeType = getNodeType(node.test);
if (nodeType &&
(0, ts_utils_1.isBooleanLiteralType)(nodeType, ts) &&
checker.typeToString(nodeType) === 'true')
return;
}
checkNode(node.test);
}
const ARRAY_PREDICATE_FUNCTIONS = new Set(['filter', 'find', 'some', 'every']);
/**
* Checks whether the given call expression is an array method that takes a predicate as an argument.
*/
function isArrayPredicateFunction(node) {
const { callee } = node;
return (
// looks like `something.filter` or `something.find`
callee.type === 'MemberExpression' &&
callee.property.type === 'Identifier' &&
ARRAY_PREDICATE_FUNCTIONS.has(callee.property.name) &&
// and the left-hand side is an array, according to the types
(nodeIsArrayType(callee.object) || nodeIsTupleType(callee.object)));
}
/**
* Checks if a conditional node is necessary from the given call expression.
*/
function checkCallExpression(node) {
// If this is something like arr.filter(x => /*condition*/), check `condition`
if (isArrayPredicateFunction(node) && node.arguments.length) {
const callback = node.arguments[0];
// Inline defined functions
if ((callback.type === 'ArrowFunctionExpression' || callback.type === 'FunctionExpression') &&
callback.body) {
// Two special cases, where we can directly check the node that's returned:
// () => something
if (callback.body.type !== 'BlockStatement') {
checkNode(callback.body);
return;
}
// () => { return something; }
const callbackBody = callback.body.body;
if (callbackBody.length === 1 &&
callbackBody[0].type === 'ReturnStatement' &&
callbackBody[0].argument) {
checkNode(callbackBody[0].argument);
return;
}
// Potential enhancement: could use code-path analysis to check
// any function with a single return statement
// (Value to complexity ratio is dubious however)
}
const nodeType = getNodeType(callback);
if (!nodeType) {
return;
}
// Otherwise just do type analysis on the function as a whole.
const returnTypes = (0, ts_utils_1.getCallSignaturesOfType)(nodeType).map((sig) => sig.getReturnType());
/* istanbul ignore if */ if (returnTypes.length === 0) {
// Not a callable function
return;
}
// Predicate is always necessary if it involves `any` or `unknown`
if (returnTypes.some((t) => (0, ts_utils_1.isAnyType)(t, ts) || (0, ts_utils_1.isUnknownType)(t, ts))) {
return;
}
if (!returnTypes.some((t) => isPossiblyFalsy(t, tools))) {
context.report({
node: callback,
messageId: 'alwaysTruthyFunc'
});
return;
}
if (!returnTypes.some((t) => isPossiblyTruthy(t, tools))) {
context.report({
node: callback,
messageId: 'alwaysFalsyFunc'
});
}
}
}
/**
*
* Recursively searches an optional chain for an array index expression
* Has to search the entire chain, because an array index will "infect" the rest of the types
* Example:
* ```
* [{x: {y: "z"} }][n] // type is {x: {y: "z"}}
* ?.x // type is {y: "z"}
* ?.y // This access is considered "unnecessary" according to the types
* ```
*/
function optionChainContainsOptionArrayIndex(node) {
const lhsNode = node.type === 'CallExpression' ? node.callee : node.object;
if (node.optional && isArrayIndexExpression(lhsNode)) {
return true;
}
if (lhsNode.type === 'MemberExpression' || lhsNode.type === 'CallExpression') {
return optionChainContainsOptionArrayIndex(lhsNode);
}
return false;
}
/**
* Check whether the given property is a nullable or not.
*/
function isNullablePropertyType(objType, propertyType) {
if (propertyType.isUnion()) {
return propertyType.types.some((type) => isNullablePropertyType(objType, type));
}
if (propertyType.isNumberLiteral() || propertyType.isStringLiteral()) {
const propType = (0, ts_utils_1.getTypeOfPropertyOfType)(objType, propertyType.value.toString(), checker);
if (propType) {
return (0, ts_utils_1.isNullableType)(propType, ts);
}
}
const typeName = (0, ts_utils_1.getTypeName)(propertyType, tools);
return Boolean((typeName === 'string' && checker.getIndexInfoOfType(objType, ts.IndexKind.String)) ||
(typeName === 'number' && checker.getIndexInfoOfType(objType, ts.IndexKind.Number)));
}
/**
* Checks whether a member expression is nullable or not regardless of it's previous node.
* Example:
* ```
* // 'bar' is nullable if 'foo' is null.
* // but this function checks regardless of 'foo' type, so returns 'true'.
* declare const foo: { bar : { baz: string } } | null
* foo?.bar;
* ```
*/
function isNullableOriginFromPrev(node) {
const prevType = getNodeType(node.object);
const property = node.property;
if (prevType && prevType.isUnion() && property.type === 'Identifier') {
const isOwnNullable = prevType.types.some((type) => {
if (node.computed) {
const propertyType = getNodeType(node.property);
return Boolean(propertyType && isNullablePropertyType(type, propertyType));
}
const propType = (0, ts_utils_1.getTypeOfPropertyOfType)(type, property.name, checker);
return propType && (0, ts_utils_1.isNullableType)(propType, ts);
});
return !isOwnNullable && (0, ts_utils_1.isNullableType)(prevType, ts);
}
return false;
}
/**
* Checks whether a lhs expression is optionable or not.
*/
function isOptionableExpression(node) {
const type = getNodeType(node);
if (!type) {
return false;
}
const isOwnNullable = node.type === 'MemberExpression' ? !isNullableOriginFromPrev(node) : true;
return ((0, ts_utils_1.isAnyType)(type, ts) ||
(0, ts_utils_1.isUnknownType)(type, ts) ||
((0, ts_utils_1.isNullableType)(type, ts) && isOwnNullable));
}
/**
* Checks if a conditional node is necessary from the given optional chaining expression.
*/
function checkOptionalChain(node, beforeOperator, fix) {
// We only care if this step in the chain is optional. If just descend
// from an optional chain, then that's fine.
if (!node.optional) {
return;
}
// Since typescript array index signature types don't represent the
// possibility of out-of-bounds access, if we're indexing into an array
// just skip the check, to avoid false positives
if (optionChainContainsOptionArrayIndex(node)) {
return;
}
const nodeToCheck = node.type === 'CallExpression' ? node.callee : node.object;
if (hasSvelteReactiveVar(nodeToCheck)) {
return;
}
if (isOptionableExpression(nodeToCheck)) {
return;
}
const questionDotOperator = sourceCode.getTokenAfter(beforeOperator, {
includeComments: false,
filter: (token) => token.type === 'Punctuator' && token.value === '?.'
});
context.report({
node,
loc: questionDotOperator.loc,
messageId: 'neverOptionalChain',
fix(fixer) {
return fixer.replaceText(questionDotOperator, fix);
}
});
}
/**
* Checks for optional member expression
*/
function checkOptionalMemberExpression(node) {
checkOptionalChain(node, node.object, node.computed ? '' : '.');
}
/**
* Checks for optional call expression
*/
function checkOptionalCallExpression(node) {
checkOptionalChain(node, node.callee, '');
}
return {
SvelteReactiveStatement: () => (inSvelteReactiveStatement = true),
'SvelteReactiveStatement:exit': () => (inSvelteReactiveStatement = false),
BinaryExpression: checkIfBinaryExpressionIsNecessaryConditional,
CallExpression: checkCallExpression,
ConditionalExpression: (node) => checkNode(node.test),
DoWhileStatement: checkIfLoopIsNecessaryConditional,
ForStatement: checkIfLoopIsNecessaryConditional,
IfStatement: (node) => checkNode(node.test),
LogicalExpression: checkLogicalExpressionForUnnecessaryConditionals,
WhileStatement: checkIfLoopIsNecessaryConditional,
'MemberExpression[optional = true]': checkOptionalMemberExpression,
'CallExpression[optional = true]': checkOptionalCallExpression
};
}
});

View File

@@ -0,0 +1,2 @@
declare const _default: import("../types").RuleModule;
export default _default;

View File

@@ -0,0 +1,127 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
const ast_utils_1 = require("../utils/ast-utils");
const compat_1 = require("../utils/compat");
exports.default = (0, utils_1.createRule)('block-lang', {
meta: {
docs: {
description: 'disallows the use of languages other than those specified in the configuration for the lang attribute of `<script>` and `<style>` blocks.',
category: 'Best Practices',
recommended: false
},
schema: [
{
type: 'object',
properties: {
enforceScriptPresent: {
type: 'boolean'
},
enforceStylePresent: {
type: 'boolean'
},
script: {
oneOf: [
{
type: ['string', 'null']
},
{
type: 'array',
items: {
type: ['string', 'null']
},
minItems: 1
}
]
},
style: {
oneOf: [
{
type: ['string', 'null']
},
{
type: 'array',
items: {
type: ['string', 'null']
},
minItems: 1
}
]
}
},
additionalProperties: false
}
],
messages: {},
type: 'suggestion'
},
create(context) {
if (!(0, compat_1.getSourceCode)(context).parserServices.isSvelte) {
return {};
}
const enforceScriptPresent = context.options[0]?.enforceScriptPresent ?? false;
const enforceStylePresent = context.options[0]?.enforceStylePresent ?? false;
const scriptOption = context.options[0]?.script ?? null;
const allowedScriptLangs = Array.isArray(scriptOption)
? scriptOption
: [scriptOption];
const scriptNodes = [];
const styleOption = context.options[0]?.style ?? null;
const allowedStyleLangs = Array.isArray(styleOption)
? styleOption
: [styleOption];
const styleNodes = [];
return {
SvelteScriptElement(node) {
scriptNodes.push(node);
},
SvelteStyleElement(node) {
styleNodes.push(node);
},
'Program:exit'() {
if (scriptNodes.length === 0 && enforceScriptPresent) {
context.report({
loc: { line: 1, column: 1 },
message: `The <script> block should be present and its lang attribute should be ${prettyPrintLangs(allowedScriptLangs)}.`
});
}
for (const scriptNode of scriptNodes) {
if (!allowedScriptLangs.includes((0, ast_utils_1.getLangValue)(scriptNode)?.toLowerCase() ?? null)) {
context.report({
node: scriptNode,
message: `The lang attribute of the <script> block should be ${prettyPrintLangs(allowedScriptLangs)}.`
});
}
}
if (styleNodes.length === 0 && enforceStylePresent) {
context.report({
loc: { line: 1, column: 1 },
message: `The <style> block should be present and its lang attribute should be ${prettyPrintLangs(allowedStyleLangs)}.`
});
}
for (const styleNode of styleNodes) {
if (!allowedStyleLangs.includes((0, ast_utils_1.getLangValue)(styleNode)?.toLowerCase() ?? null)) {
context.report({
node: styleNode,
message: `The lang attribute of the <style> block should be ${prettyPrintLangs(allowedStyleLangs)}.`
});
}
}
}
};
}
});
/**
* Prints the list of allowed languages, with special handling of the `null` option.
*/
function prettyPrintLangs(langs) {
const hasNull = langs.includes(null);
const nonNullLangs = langs.filter((lang) => lang !== null).map((lang) => `"${lang}"`);
if (nonNullLangs.length === 0) {
// No special behaviour for `hasNull`, because that can never happen.
return 'omitted';
}
const hasNullText = hasNull ? 'either omitted or ' : '';
const nonNullText = nonNullLangs.length === 1 ? nonNullLangs[0] : `one of ${nonNullLangs.join(', ')}`;
return hasNullText + nonNullText;
}

View File

@@ -0,0 +1,2 @@
declare const _default: import("../types").RuleModule;
export default _default;

View File

@@ -0,0 +1,112 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
const ast_utils_1 = require("../utils/ast-utils");
exports.default = (0, utils_1.createRule)('button-has-type', {
meta: {
docs: {
description: 'disallow usage of button without an explicit type attribute',
category: 'Best Practices',
recommended: false
},
schema: [
{
type: 'object',
properties: {
button: {
type: 'boolean'
},
submit: {
type: 'boolean'
},
reset: {
type: 'boolean'
}
},
additionalProperties: false
}
],
messages: {
missingTypeAttribute: 'Missing an explicit type attribute for button.',
invalidTypeAttribute: '{{value}} is an invalid value for button type attribute.',
forbiddenTypeAttribute: '{{value}} is a forbidden value for button type attribute.',
emptyTypeAttribute: 'A value must be set for button type attribute.'
},
type: 'suggestion' // "problem",
},
create(context) {
const configuration = {
button: true,
submit: true,
reset: true,
...(context.options[0] ?? {})
};
/**
* Checks whether given text is known button type
*/
function isButtonType(type) {
return type === 'button' || type === 'submit' || type === 'reset';
}
/**
* Report
*/
function report(node, messageId, data = {}) {
context.report({
node,
messageId,
data
});
}
/**
* Validate attribute
*/
function validateAttribute(attribute) {
if (attribute.value.length === 0) {
report(attribute, 'emptyTypeAttribute');
return;
}
const strValue = (0, ast_utils_1.getStaticAttributeValue)(attribute);
if (strValue == null) {
return;
}
if (!isButtonType(strValue)) {
report(attribute, 'invalidTypeAttribute', { value: strValue });
}
else if (!configuration[strValue]) {
report(attribute, 'forbiddenTypeAttribute', { value: strValue });
}
}
/**
* @param {VDirective} directive
*/
function validateDirective(directive) {
if (!directive.expression) {
report(directive, 'emptyTypeAttribute');
}
}
return {
"SvelteElement[name.name='button'] > SvelteStartTag"(node) {
const typeAttr = (0, ast_utils_1.findAttribute)(node, 'type');
if (typeAttr) {
validateAttribute(typeAttr);
return;
}
const typeDir = (0, ast_utils_1.findBindDirective)(node, 'type');
if (typeDir) {
validateDirective(typeDir);
return;
}
const typeShortAttr = (0, ast_utils_1.findShorthandAttribute)(node, 'type');
if (typeShortAttr) {
return;
}
for (const attr of node.attributes) {
if (attr.type === 'SvelteSpreadAttribute') {
return;
}
}
report(node, 'missingTypeAttribute');
}
};
}
});

View File

@@ -0,0 +1,2 @@
declare const _default: import("../types").RuleModule;
export default _default;

View File

@@ -0,0 +1,200 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const shared_1 = require("../shared");
const utils_1 = require("../utils");
const compat_1 = require("../utils/compat");
// -----------------------------------------------------------------------------
// Helpers
// -----------------------------------------------------------------------------
const COMMENT_DIRECTIVE_B = /^\s*(eslint-(?:en|dis)able)(?:\s+|$)/;
const COMMENT_DIRECTIVE_L = /^\s*(eslint-disable(?:-next)?-line)(?:\s+|$)/;
// eslint-disable-next-line func-style -- ignore
const ALL_RULES = () => true;
/**
* Remove the ignored part from a given directive comment and trim it.
* @param {string} value The comment text to strip.
* @returns {string} The stripped text.
*/
function stripDirectiveComment(value) {
return value.split(/\s-{2,}\s/u)[0];
}
exports.default = (0, utils_1.createRule)('comment-directive', {
meta: {
docs: {
description: 'support comment-directives in HTML template',
category: 'System',
recommended: 'base'
},
schema: [
{
type: 'object',
properties: {
reportUnusedDisableDirectives: {
type: 'boolean'
}
},
additionalProperties: false
}
],
messages: {
unused: 'Unused {{kind}} directive (no problems were reported).',
unusedRule: "Unused {{kind}} directive (no problems were reported from '{{rule}}').",
unusedEnable: 'Unused {{kind}} directive (reporting is not suppressed).',
unusedEnableRule: "Unused {{kind}} directive (reporting from '{{rule}}' is not suppressed)."
},
type: 'problem'
},
create(context) {
const shared = (0, shared_1.getShared)((0, compat_1.getFilename)(context));
if (!shared)
return {};
const options = context.options[0] || {};
const reportUnusedDisableDirectives = Boolean(options.reportUnusedDisableDirectives);
const directives = shared.newCommentDirectives({
ruleId: 'svelte/comment-directive',
reportUnusedDisableDirectives
});
const sourceCode = (0, compat_1.getSourceCode)(context);
/**
* Parse a given comment.
*/
function parse(pattern, comment) {
const text = stripDirectiveComment(comment.value);
const match = pattern.exec(text);
if (match == null) {
return null;
}
const type = match[1];
const rules = [];
const rulesRe = /([^\s,]+)[\s,]*/g;
let startIndex = match[0].length;
rulesRe.lastIndex = startIndex;
let res;
while ((res = rulesRe.exec(text))) {
const ruleId = res[1].trim();
const commentStart = comment.range[0] + 4; /* <!-- */
const start = sourceCode.getLocFromIndex(commentStart + startIndex);
const end = sourceCode.getLocFromIndex(commentStart + startIndex + ruleId.length);
rules.push({
ruleId,
loc: {
start,
end
}
});
startIndex = rulesRe.lastIndex;
}
return { type, rules };
}
/**
* Process a given comment token.
* If the comment is `eslint-disable` or `eslint-enable` then it reports the comment.
*/
function processBlock(directives, comment) {
const parsed = parse(COMMENT_DIRECTIVE_B, comment);
if (parsed != null) {
if (parsed.type === 'eslint-disable') {
if (parsed.rules.length) {
for (const rule of parsed.rules) {
if (reportUnusedDisableDirectives) {
context.report({
loc: rule.loc,
messageId: 'unusedRule',
data: { rule: rule.ruleId, kind: parsed.type }
});
}
directives.disableBlock(comment.loc.end, rule.ruleId, {
loc: rule.loc.start
});
}
}
else {
if (reportUnusedDisableDirectives) {
context.report({
loc: comment.loc,
messageId: 'unused',
data: { kind: parsed.type }
});
}
directives.disableBlock(comment.loc.end, ALL_RULES, {
loc: comment.loc.start
});
}
}
else if (parsed.rules.length) {
for (const rule of parsed.rules) {
if (reportUnusedDisableDirectives) {
context.report({
loc: rule.loc,
messageId: 'unusedEnableRule',
data: { rule: rule.ruleId, kind: parsed.type }
});
}
directives.enableBlock(comment.loc.start, rule.ruleId, {
loc: rule.loc.start
});
}
}
else {
if (reportUnusedDisableDirectives) {
context.report({
loc: comment.loc,
messageId: 'unusedEnable',
data: { kind: parsed.type }
});
}
directives.enableBlock(comment.loc.start, ALL_RULES, {
loc: comment.loc.start
});
}
}
}
/**
* Process a given comment token.
* If the comment is `eslint-disable-line` or `eslint-disable-next-line` then it reports the comment.
*/
function processLine(directives, comment) {
const parsed = parse(COMMENT_DIRECTIVE_L, comment);
if (parsed != null && comment.loc.start.line === comment.loc.end.line) {
const line = comment.loc.start.line + (parsed.type === 'eslint-disable-line' ? 0 : 1);
if (parsed.rules.length) {
for (const rule of parsed.rules) {
if (reportUnusedDisableDirectives) {
context.report({
loc: rule.loc,
messageId: 'unusedRule',
data: { rule: rule.ruleId, kind: parsed.type }
});
}
directives.disableLine(line, rule.ruleId, {
loc: rule.loc.start
});
}
}
else {
if (reportUnusedDisableDirectives) {
context.report({
loc: comment.loc,
messageId: 'unused',
data: { kind: parsed.type }
});
}
directives.disableLine(line, ALL_RULES, {
loc: comment.loc.start
});
}
}
}
return {
SvelteHTMLComment(node) {
processBlock(directives, node);
processLine(directives, node);
},
SvelteScriptElement(node) {
directives.enableBlock(node.startTag.loc.end, ALL_RULES, {
loc: node.loc.start
});
}
};
}
});

View File

@@ -0,0 +1,2 @@
declare const _default: import("../types").RuleModule;
export default _default;

View File

@@ -0,0 +1,90 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
const svelte_store_1 = require("./reference-helpers/svelte-store");
exports.default = (0, utils_1.createRule)('derived-has-same-inputs-outputs', {
meta: {
docs: {
description: 'derived store should use same variable names between values and callback',
category: 'Stylistic Issues',
recommended: false,
conflictWithPrettier: false
},
schema: [],
messages: {
unexpected: "The argument name should be '{{name}}'."
},
type: 'suggestion'
},
create(context) {
/** check node type */
function isIdentifierOrArrayExpression(node) {
return ['Identifier', 'ArrayExpression'].includes(node.type);
}
/** check node type */
function isFunctionExpression(node) {
return ['ArrowFunctionExpression', 'FunctionExpression'].includes(node.type);
}
/**
* Check for identifier type.
* e.g. derived(a, ($a) => {});
*/
function checkIdentifier(context, args, fn) {
const fnParam = fn.params[0];
if (fnParam.type !== 'Identifier')
return;
const expectedName = `$${args.name}`;
if (expectedName !== fnParam.name) {
context.report({
node: fn,
loc: fnParam.loc,
messageId: 'unexpected',
data: { name: expectedName }
});
}
}
/**
* Check for array type.
* e.g. derived([ a, b ], ([ $a, $b ]) => {})
*/
function checkArrayExpression(context, args, fn) {
const fnParam = fn.params[0];
if (fnParam.type !== 'ArrayPattern')
return;
const argNames = args.elements.map((element) => {
return element && element.type === 'Identifier' ? element.name : null;
});
fnParam.elements.forEach((element, index) => {
const argName = argNames[index];
if (element && element.type === 'Identifier' && argName) {
const expectedName = `$${argName}`;
if (expectedName !== element.name) {
context.report({
node: fn,
loc: element.loc,
messageId: 'unexpected',
data: { name: expectedName }
});
}
}
});
}
return {
Program() {
for (const { node } of (0, svelte_store_1.extractStoreReferences)(context, ['derived'])) {
const [args, fn] = node.arguments;
if (!args || !isIdentifierOrArrayExpression(args))
continue;
if (!fn || !isFunctionExpression(fn))
continue;
if (!fn.params || fn.params.length === 0)
continue;
if (args.type === 'Identifier')
checkIdentifier(context, args, fn);
else
checkArrayExpression(context, args, fn);
}
}
};
}
});

View File

@@ -0,0 +1,2 @@
declare const _default: import("../types").RuleModule;
export default _default;

View File

@@ -0,0 +1,56 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
const ast_utils_1 = require("../utils/ast-utils");
const SLOTS_TYPE_NAME = '$$Slots';
exports.default = (0, utils_1.createRule)('experimental-require-slot-types', {
meta: {
docs: {
description: 'require slot type declaration using the `$$Slots` interface',
category: 'Experimental',
recommended: false
},
schema: [],
messages: {
missingSlotsInterface: `The component must define the $$Slots interface.`
},
type: 'suggestion'
},
create(context) {
let isTs = false;
let hasSlot = false;
let hasDeclaredSlots = false;
return {
SvelteScriptElement(node) {
const lang = (0, ast_utils_1.getLangValue)(node)?.toLowerCase();
isTs = lang === 'ts' || lang === 'typescript';
},
SvelteElement(node) {
if (node.name.type === 'SvelteName' && node.name.name === 'slot') {
hasSlot = true;
}
},
TSInterfaceDeclaration(node) {
if (node.id.name === SLOTS_TYPE_NAME) {
hasDeclaredSlots = true;
}
},
TSTypeAliasDeclaration(node) {
if (node.id.name === SLOTS_TYPE_NAME) {
hasDeclaredSlots = true;
}
},
'Program:exit'() {
if (isTs && hasSlot && !hasDeclaredSlots) {
context.report({
loc: {
line: 1,
column: 1
},
messageId: 'missingSlotsInterface'
});
}
}
};
}
});

View File

@@ -0,0 +1,2 @@
declare const _default: import("../types").RuleModule;
export default _default;

View File

@@ -0,0 +1,51 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
const ast_utils_1 = require("../utils/ast-utils");
const EVENTS_TYPE_NAME = '$$Events';
exports.default = (0, utils_1.createRule)('experimental-require-strict-events', {
meta: {
docs: {
description: 'require the strictEvents attribute on `<script>` tags',
category: 'Experimental',
recommended: false
},
schema: [],
messages: {
missingStrictEvents: `The component must have the strictEvents attribute on its <script> tag or it must define the $$Events interface.`
},
type: 'suggestion'
},
create(context) {
let isTs = false;
let hasAttribute = false;
let hasDeclaredEvents = false;
let scriptNode;
return {
SvelteScriptElement(node) {
const lang = (0, ast_utils_1.getLangValue)(node)?.toLowerCase();
isTs = lang === 'ts' || lang === 'typescript';
hasAttribute = (0, ast_utils_1.findAttribute)(node, 'strictEvents') !== null;
scriptNode = node;
},
TSInterfaceDeclaration(node) {
if (node.id.name === EVENTS_TYPE_NAME) {
hasDeclaredEvents = true;
}
},
TSTypeAliasDeclaration(node) {
if (node.id.name === EVENTS_TYPE_NAME) {
hasDeclaredEvents = true;
}
},
'Program:exit'() {
if (isTs && !hasAttribute && !hasDeclaredEvents) {
context.report({
node: scriptNode,
messageId: 'missingStrictEvents'
});
}
}
};
}
});

View File

@@ -0,0 +1,2 @@
declare const _default: import("../types").RuleModule;
export default _default;

View File

@@ -0,0 +1,68 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
const compat_1 = require("../utils/compat");
exports.default = (0, utils_1.createRule)('first-attribute-linebreak', {
meta: {
docs: {
description: 'enforce the location of first attribute',
category: 'Stylistic Issues',
recommended: false,
conflictWithPrettier: true
},
fixable: 'whitespace',
schema: [
{
type: 'object',
properties: {
multiline: { enum: ['below', 'beside'] },
singleline: { enum: ['below', 'beside'] }
},
additionalProperties: false
}
],
messages: {
expected: 'Expected a linebreak before this attribute.',
unexpected: 'Expected no linebreak before this attribute.'
},
type: 'layout'
},
create(context) {
const multiline = context.options[0]?.multiline || 'below';
const singleline = context.options[0]?.singleline || 'beside';
const sourceCode = (0, compat_1.getSourceCode)(context);
/**
* Report attribute
*/
function report(firstAttribute, location) {
context.report({
node: firstAttribute,
messageId: location === 'beside' ? 'unexpected' : 'expected',
fix(fixer) {
const prevToken = sourceCode.getTokenBefore(firstAttribute, {
includeComments: true
});
return fixer.replaceTextRange([prevToken.range[1], firstAttribute.range[0]], location === 'beside' ? ' ' : '\n');
}
});
}
return {
SvelteStartTag(node) {
const firstAttribute = node.attributes[0];
if (!firstAttribute)
return;
const lastAttribute = node.attributes[node.attributes.length - 1];
const location = firstAttribute.loc.start.line === lastAttribute.loc.end.line ? singleline : multiline;
if (location === 'beside') {
if (node.parent.name.loc.end.line === firstAttribute.loc.start.line) {
return;
}
}
else if (node.parent.name.loc.end.line < firstAttribute.loc.start.line) {
return;
}
report(firstAttribute, location);
}
};
}
});

View File

@@ -0,0 +1,2 @@
declare const _default: import("../types").RuleModule;
export default _default;

View File

@@ -0,0 +1,118 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
const compat_1 = require("../utils/compat");
function getPhrase(lineBreaks) {
switch (lineBreaks) {
case 0: {
return 'no line breaks';
}
case 1: {
return '1 line break';
}
default: {
return `${lineBreaks} line breaks`;
}
}
}
function getExpectedLineBreaks(node, options, type) {
const isSelfClosingTag = node.type === 'SvelteStartTag' && node.selfClosing;
if (isSelfClosingTag && options.selfClosingTag && options.selfClosingTag[type]) {
return options.selfClosingTag[type] === 'always' ? 1 : 0;
}
return options[type] === 'always' ? 1 : 0;
}
function getSelfClosingData(sourceCode, node, options) {
const tokens = sourceCode.getTokens(node);
const closingToken = tokens[tokens.length - 2];
if (closingToken.value !== '/') {
return null;
}
const prevToken = sourceCode.getTokenBefore(closingToken);
const type = node.loc.start.line === prevToken.loc.end.line ? 'singleline' : 'multiline';
const expectedLineBreaks = getExpectedLineBreaks(node, options, type);
const actualLineBreaks = closingToken.loc.start.line - prevToken.loc.end.line;
return { actualLineBreaks, expectedLineBreaks, startToken: prevToken, endToken: closingToken };
}
function getNodeData(sourceCode, node, options) {
const closingToken = sourceCode.getLastToken(node);
if (closingToken.value !== '>') {
return null;
}
const prevToken = sourceCode.getTokenBefore(closingToken);
const type = node.loc.start.line === prevToken.loc.end.line ? 'singleline' : 'multiline';
const expectedLineBreaks = getExpectedLineBreaks(node, options, type);
const actualLineBreaks = closingToken.loc.start.line - prevToken.loc.end.line;
return { actualLineBreaks, expectedLineBreaks, startToken: prevToken, endToken: closingToken };
}
exports.default = (0, utils_1.createRule)('html-closing-bracket-new-line', {
meta: {
docs: {
description: "Require or disallow a line break before tag's closing brackets",
category: 'Stylistic Issues',
recommended: false,
conflictWithPrettier: true
},
schema: [
{
type: 'object',
properties: {
singleline: { enum: ['always', 'never'] },
multiline: { enum: ['always', 'never'] },
selfClosingTag: {
type: 'object',
properties: {
singleline: { enum: ['always', 'never'] },
multiline: { enum: ['always', 'never'] }
},
additionalProperties: false,
minProperties: 1
}
},
additionalProperties: false
}
],
messages: {
expectedBeforeClosingBracket: 'Expected {{expected}} before closing bracket, but {{actual}} found.'
},
fixable: 'code',
type: 'suggestion'
},
create(context) {
const options = context.options[0] ?? {};
options.singleline ?? (options.singleline = 'never');
options.multiline ?? (options.multiline = 'always');
const sourceCode = (0, compat_1.getSourceCode)(context);
return {
'SvelteStartTag, SvelteEndTag'(node) {
const data = node.type === 'SvelteStartTag' && node.selfClosing
? getSelfClosingData(sourceCode, node, options)
: getNodeData(sourceCode, node, options);
if (!data) {
return;
}
const { actualLineBreaks, expectedLineBreaks, startToken, endToken } = data;
if (actualLineBreaks !== expectedLineBreaks) {
// For SvelteEndTag, does not make sense to add a line break, so we only fix if there are extra line breaks
if (node.type === 'SvelteEndTag' && expectedLineBreaks !== 0) {
return;
}
context.report({
node,
loc: { start: startToken.loc.end, end: endToken.loc.start },
messageId: 'expectedBeforeClosingBracket',
data: {
expected: getPhrase(expectedLineBreaks),
actual: getPhrase(actualLineBreaks)
},
fix(fixer) {
const range = [startToken.range[1], endToken.range[0]];
const text = '\n'.repeat(expectedLineBreaks);
return fixer.replaceTextRange(range, text);
}
});
}
}
};
}
});

View File

@@ -0,0 +1,2 @@
declare const _default: import("../types").RuleModule;
export default _default;

View File

@@ -0,0 +1,98 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
exports.default = (0, utils_1.createRule)('html-closing-bracket-spacing', {
meta: {
docs: {
description: "require or disallow a space before tag's closing brackets",
category: 'Stylistic Issues',
conflictWithPrettier: true,
recommended: false
},
schema: [
{
type: 'object',
properties: {
startTag: {
enum: ['always', 'never', 'ignore']
},
endTag: {
enum: ['always', 'never', 'ignore']
},
selfClosingTag: {
enum: ['always', 'never', 'ignore']
}
},
additionalProperties: false
}
],
messages: {
expectedSpace: "Expected space before '>', but not found.",
unexpectedSpace: "Expected no space before '>', but found."
},
fixable: 'whitespace',
type: 'layout'
},
create(ctx) {
const options = {
startTag: 'never',
endTag: 'never',
selfClosingTag: 'always',
...ctx.options[0]
};
const src = ctx.getSourceCode();
/**
* Returns true if string contains newline characters
*/
function containsNewline(string) {
return string.includes('\n');
}
/**
* Report
*/
function report(node, shouldHave) {
const tagSrc = src.getText(node);
const match = /(\s*)\/?>$/.exec(tagSrc);
const end = node.range[1];
const start = node.range[1] - match[0].length;
const loc = {
start: src.getLocFromIndex(start),
end: src.getLocFromIndex(end)
};
ctx.report({
loc,
messageId: shouldHave ? 'expectedSpace' : 'unexpectedSpace',
*fix(fixer) {
if (shouldHave) {
yield fixer.insertTextBeforeRange([start, end], ' ');
}
else {
const spaces = match[1];
yield fixer.removeRange([start, start + spaces.length]);
}
}
});
}
return {
'SvelteStartTag, SvelteEndTag'(node) {
const tagType = node.type === 'SvelteEndTag'
? 'endTag'
: node.selfClosing
? 'selfClosingTag'
: 'startTag';
if (options[tagType] === 'ignore')
return;
const tagSrc = src.getText(node);
const match = /(\s*)\/?>$/.exec(tagSrc);
if (containsNewline(match[1]))
return;
if (options[tagType] === 'always' && !match[1]) {
report(node, true);
}
else if (options[tagType] === 'never' && match[1]) {
report(node, false);
}
}
};
}
});

View File

@@ -0,0 +1,2 @@
declare const _default: import("../types").RuleModule;
export default _default;

View File

@@ -0,0 +1,168 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
const ast_utils_1 = require("../utils/ast-utils");
const ast_utils_2 = require("../utils/ast-utils");
const compat_1 = require("../utils/compat");
const QUOTE_CHARS = {
double: '"',
single: "'"
};
const QUOTE_NAMES = {
double: 'double quotes',
single: 'single quotes',
unquoted: 'unquoted'
};
exports.default = (0, utils_1.createRule)('html-quotes', {
meta: {
docs: {
description: 'enforce quotes style of HTML attributes',
category: 'Stylistic Issues',
recommended: false,
conflictWithPrettier: true
},
fixable: 'code',
schema: [
{
type: 'object',
properties: {
prefer: { enum: ['double', 'single'] },
dynamic: {
type: 'object',
properties: {
quoted: { type: 'boolean' },
avoidInvalidUnquotedInHTML: { type: 'boolean' }
},
additionalProperties: false
}
},
additionalProperties: false
}
],
messages: {
expectedEnclosed: 'Expected to be enclosed by quotes.',
expectedEnclosedBy: 'Expected to be enclosed by {{kind}}.',
unexpectedEnclosed: 'Unexpected to be enclosed by any quotes.'
},
type: 'layout' // "problem",
},
create(context) {
const sourceCode = (0, compat_1.getSourceCode)(context);
const preferQuote = context.options[0]?.prefer ?? 'double';
const dynamicQuote = context.options[0]?.dynamic?.quoted ? preferQuote : 'unquoted';
const avoidInvalidUnquotedInHTML = Boolean(context.options[0]?.dynamic?.avoidInvalidUnquotedInHTML);
/** Checks whether the given text can remove quotes in HTML. */
function canBeUnquotedInHTML(text) {
return !/[\s"'<=>`]/u.test(text);
}
/** Verify quote */
function verifyQuote(prefer, quoteAndRange) {
if (!quoteAndRange) {
// invalid
return;
}
if (quoteAndRange.quote === prefer) {
// valid
return;
}
let messageId;
let expectedQuote = prefer;
if (quoteAndRange.quote !== 'unquoted') {
if (expectedQuote === 'unquoted') {
messageId = 'unexpectedEnclosed';
}
else {
const contentText = sourceCode.text.slice(quoteAndRange.range[0] + 1, quoteAndRange.range[1] - 1);
const needEscape = contentText.includes(QUOTE_CHARS[expectedQuote]);
if (needEscape) {
// avoid escape
return;
}
messageId = 'expectedEnclosedBy';
}
}
else {
const contentText = sourceCode.text.slice(...quoteAndRange.range);
const needEscapeDoubleQuote = contentText.includes('"');
const needEscapeSingleQuote = contentText.includes("'");
if (needEscapeDoubleQuote && needEscapeSingleQuote) {
// avoid escape
return;
}
if (needEscapeDoubleQuote && expectedQuote === 'double') {
expectedQuote = 'single';
messageId = 'expectedEnclosed';
}
else if (needEscapeSingleQuote && expectedQuote === 'single') {
expectedQuote = 'double';
messageId = 'expectedEnclosed';
}
else {
messageId = 'expectedEnclosedBy';
}
}
context.report({
loc: {
start: sourceCode.getLocFromIndex(quoteAndRange.range[0]),
end: sourceCode.getLocFromIndex(quoteAndRange.range[1])
},
messageId,
data: { kind: QUOTE_NAMES[expectedQuote] },
*fix(fixer) {
if (expectedQuote !== 'unquoted') {
yield fixer.insertTextBeforeRange([quoteAndRange.range[0], quoteAndRange.range[0]], QUOTE_CHARS[expectedQuote]);
}
if (quoteAndRange.quote !== 'unquoted') {
yield fixer.removeRange([quoteAndRange.range[0], quoteAndRange.range[0] + 1]);
yield fixer.removeRange([quoteAndRange.range[1] - 1, quoteAndRange.range[1]]);
}
if (expectedQuote !== 'unquoted') {
yield fixer.insertTextAfterRange([quoteAndRange.range[1], quoteAndRange.range[1]], QUOTE_CHARS[expectedQuote]);
}
}
});
}
/** Verify for standard attribute */
function verifyForValues(attr) {
const quoteAndRange = (0, ast_utils_2.getAttributeValueQuoteAndRange)(attr, sourceCode);
verifyQuote(preferQuote, quoteAndRange);
}
/** Verify for dynamic attribute */
function verifyForDynamicMustacheTag(attr, valueNode) {
const quoteAndRange = (0, ast_utils_2.getAttributeValueQuoteAndRange)(attr, sourceCode);
const text = sourceCode.text.slice(...valueNode.range);
verifyQuote(avoidInvalidUnquotedInHTML && !canBeUnquotedInHTML(text) ? preferQuote : dynamicQuote, quoteAndRange);
}
/** Verify for directive value */
function verifyForDirective(attr) {
const mustacheTokens = (0, ast_utils_1.getMustacheTokens)(attr, sourceCode);
if (!mustacheTokens) {
return;
}
const quoteAndRange = (0, ast_utils_2.getAttributeValueQuoteAndRange)(attr, sourceCode);
const text = sourceCode.text.slice(mustacheTokens.openToken.range[0], mustacheTokens.closeToken.range[1]);
verifyQuote(avoidInvalidUnquotedInHTML && !canBeUnquotedInHTML(text) ? preferQuote : dynamicQuote, quoteAndRange);
}
return {
'SvelteAttribute, SvelteStyleDirective'(node) {
if (node.value.length === 1 && node.value[0].type === 'SvelteMustacheTag') {
verifyForDynamicMustacheTag(node, node.value[0]);
}
else if (node.value.length >= 1) {
verifyForValues(node);
}
},
'SvelteDirective, SvelteSpecialDirective'(node) {
if (node.expression == null) {
return;
}
if (node.key.range[0] <= node.expression.range[0] &&
node.expression.range[1] <= node.key.range[1]) {
// shorthand
return;
}
verifyForDirective(node);
}
};
}
});

View File

@@ -0,0 +1,2 @@
declare const _default: import("../types").RuleModule;
export default _default;

View File

@@ -0,0 +1,176 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
const ast_utils_1 = require("../utils/ast-utils");
const compat_1 = require("../utils/compat");
const TYPE_MESSAGES = {
normal: 'HTML elements',
void: 'HTML void elements',
foreign: 'foreign (SVG or MathML) elements',
component: 'Svelte custom components',
svelte: 'Svelte special elements'
};
exports.default = (0, utils_1.createRule)('html-self-closing', {
meta: {
docs: {
description: 'enforce self-closing style',
category: 'Stylistic Issues',
recommended: false,
conflictWithPrettier: true
},
type: 'layout',
fixable: 'code',
messages: {
requireClosing: 'Require self-closing on {{type}}.',
disallowClosing: 'Disallow self-closing on {{type}}.'
},
schema: [
{
anyOf: [
{
properties: {
void: {
enum: ['never', 'always', 'ignore']
},
normal: {
enum: ['never', 'always', 'ignore']
},
foreign: {
enum: ['never', 'always', 'ignore']
},
component: {
enum: ['never', 'always', 'ignore']
},
svelte: {
enum: ['never', 'always', 'ignore']
}
},
additionalProperties: false
},
{
enum: ['all', 'html', 'none']
}
]
}
]
},
create(context) {
let options = {
void: 'always',
normal: 'always',
foreign: 'always',
component: 'always',
svelte: 'always'
};
const option = context.options?.[0];
switch (option) {
case 'none':
options = {
void: 'never',
normal: 'never',
foreign: 'never',
component: 'never',
svelte: 'never'
};
break;
case 'html':
options = {
void: 'always',
normal: 'never',
foreign: 'always',
component: 'never',
svelte: 'always'
};
break;
default:
if (typeof option !== 'object' || option === null)
break;
options = {
...options,
...option
};
break;
}
/**
* Get SvelteElement type.
* If element is custom component "component" is returned
* If element is svelte special element such as svelte:self "svelte" is returned
* If element is void element "void" is returned
* otherwise "normal" is returned
*/
function getElementType(node) {
if (node.kind === 'component')
return 'component';
if (node.kind === 'special')
return 'svelte';
if ((0, ast_utils_1.isVoidHtmlElement)(node))
return 'void';
if ((0, ast_utils_1.isForeignElement)(node))
return 'foreign';
return 'normal';
}
/**
* Returns true if element has no children, or has only whitespace text
*/
function isElementEmpty(node) {
if (node.children.length <= 0)
return true;
for (const child of node.children) {
if (child.type !== 'SvelteText')
return false;
if (!/^\s*$/.test(child.value))
return false;
}
return true;
}
/**
* Report
*/
function report(node, shouldBeClosed) {
const elementType = getElementType(node);
context.report({
node,
loc: {
start: (0, compat_1.getSourceCode)(context).getLocFromIndex(node.startTag.range[1] - (node.startTag.selfClosing ? 2 : 1)),
end: node.loc.end
},
messageId: shouldBeClosed ? 'requireClosing' : 'disallowClosing',
data: {
type: TYPE_MESSAGES[elementType]
},
*fix(fixer) {
if (shouldBeClosed) {
for (const child of node.children) {
yield fixer.removeRange(child.range);
}
yield fixer.insertTextBeforeRange([node.startTag.range[1] - 1, node.startTag.range[1]], '/');
if (node.endTag)
yield fixer.removeRange(node.endTag.range);
}
else {
yield fixer.removeRange([node.startTag.range[1] - 2, node.startTag.range[1] - 1]);
if (!(0, ast_utils_1.isVoidHtmlElement)(node))
yield fixer.insertTextAfter(node, `</${(0, ast_utils_1.getNodeName)(node)}>`);
}
}
});
}
return {
SvelteElement(node) {
if (!isElementEmpty(node))
return;
const elementType = getElementType(node);
const elementTypeOptions = options[elementType];
if (elementTypeOptions === 'ignore')
return;
const shouldBeClosed = elementTypeOptions === 'always';
if (shouldBeClosed && !node.startTag.selfClosing) {
report(node, true);
}
else if (!shouldBeClosed && node.startTag.selfClosing) {
report(node, false);
}
}
};
}
});

View File

@@ -0,0 +1,12 @@
import type { AST } from 'svelte-eslint-parser';
import type { TSESTree } from '@typescript-eslint/types';
type AnyToken = AST.Token | AST.Comment;
/**
* Check whether the given token is a whitespace.
*/
export declare function isWhitespace(token: AnyToken | TSESTree.Comment | null | undefined): boolean;
/**
* Check whether the given token is a not whitespace.
*/
export declare function isNotWhitespace(token: AnyToken | TSESTree.Comment | null | undefined): boolean;
export {};

View File

@@ -0,0 +1,20 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isWhitespace = isWhitespace;
exports.isNotWhitespace = isNotWhitespace;
/**
* Check whether the given token is a whitespace.
*/
function isWhitespace(token) {
return (token != null &&
((token.type === 'HTMLText' && !token.value.trim()) ||
(token.type === 'JSXText' && !token.value.trim())));
}
/**
* Check whether the given token is a not whitespace.
*/
function isNotWhitespace(token) {
return (token != null &&
(token.type !== 'HTMLText' || Boolean(token.value.trim())) &&
(token.type !== 'JSXText' || Boolean(token.value.trim())));
}

View File

@@ -0,0 +1,39 @@
import type { ASTNode, SourceCode } from '../../types';
import type { AST } from 'svelte-eslint-parser';
import type { OffsetContext } from './offset-context';
export type AnyToken = AST.Token | AST.Comment;
export type MaybeNode = {
type: string;
range: [number, number];
loc: AST.SourceLocation;
};
export type IndentOptions = {
indentChar: ' ' | '\t';
indentScript: boolean;
indentSize: number;
switchCase: number;
alignAttributesVertically: boolean;
ignoredNodes: string[];
};
export type IndentContext = {
sourceCode: SourceCode;
options: IndentOptions;
offsets: OffsetContext;
};
/**
* Get the first and last tokens of the given node.
* If the node is parenthesized, this gets the outermost parentheses.
* If the node have whitespace at the start and the end, they will be skipped.
*/
export declare function getFirstAndLastTokens(sourceCode: SourceCode, node: ASTNode | AnyToken | MaybeNode, borderOffset?: number): {
firstToken: AST.Token;
lastToken: AST.Token;
};
/**
* Check whether the given node or token is the beginning of a line.
*/
export declare function isBeginningOfLine(sourceCode: SourceCode, node: ASTNode | AnyToken | MaybeNode): boolean;
/**
* Check whether the given node is the beginning of element.
*/
export declare function isBeginningOfElement(node: AST.SvelteText): boolean;

View File

@@ -0,0 +1,70 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getFirstAndLastTokens = getFirstAndLastTokens;
exports.isBeginningOfLine = isBeginningOfLine;
exports.isBeginningOfElement = isBeginningOfElement;
const eslint_utils_1 = require("@eslint-community/eslint-utils");
const ast_1 = require("./ast");
/**
* Get the first and last tokens of the given node.
* If the node is parenthesized, this gets the outermost parentheses.
* If the node have whitespace at the start and the end, they will be skipped.
*/
function getFirstAndLastTokens(sourceCode, node, borderOffset = 0) {
let firstToken = sourceCode.getFirstToken(node);
let lastToken = sourceCode.getLastToken(node);
// Get the outermost left parenthesis if it's parenthesized.
let left, right;
while ((left = sourceCode.getTokenBefore(firstToken)) != null &&
(right = sourceCode.getTokenAfter(lastToken)) != null &&
(0, eslint_utils_1.isOpeningParenToken)(left) &&
(0, eslint_utils_1.isClosingParenToken)(right) &&
borderOffset <= left.range[0]) {
firstToken = left;
lastToken = right;
}
while ((0, ast_1.isWhitespace)(firstToken) && firstToken.range[0] < lastToken.range[0]) {
firstToken = sourceCode.getTokenAfter(firstToken);
}
while ((0, ast_1.isWhitespace)(lastToken) && firstToken.range[0] < lastToken.range[0]) {
lastToken = sourceCode.getTokenBefore(lastToken);
}
return { firstToken, lastToken };
}
/**
* Check whether the given node or token is the beginning of a line.
*/
function isBeginningOfLine(sourceCode, node) {
const prevToken = sourceCode.getTokenBefore(node, {
includeComments: false,
filter: ast_1.isNotWhitespace
});
return !prevToken || prevToken.loc.end.line < node.loc.start.line;
}
/**
* Check whether the given node is the beginning of element.
*/
function isBeginningOfElement(node) {
if (node.parent.type === 'SvelteElement' ||
node.parent.type === 'SvelteAwaitCatchBlock' ||
node.parent.type === 'SvelteAwaitPendingBlock' ||
node.parent.type === 'SvelteAwaitThenBlock' ||
node.parent.type === 'SvelteEachBlock' ||
node.parent.type === 'SvelteElseBlock' ||
node.parent.type === 'SvelteIfBlock' ||
node.parent.type === 'SvelteKeyBlock' ||
node.parent.type === 'SvelteSnippetBlock' ||
node.parent.type === 'SvelteStyleElement') {
return node.parent.children[0] === node;
}
if (node.parent.type === 'Program') {
return node.parent.body[0] === node;
}
return assertNever(node.parent);
}
/**
* Throws an error when invoked.
*/
function assertNever(value) {
throw new Error(`This part of the code should never be reached but ${value} made it through.`);
}

View File

@@ -0,0 +1,11 @@
import type { IndentContext } from './commons';
import type { ESNodeListener } from '../../types-for-node';
type NodeListener = ESNodeListener;
/**
* Creates AST event handlers for ES nodes.
*
* @param context The rule context.
* @returns AST event handlers.
*/
export declare function defineVisitor(context: IndentContext): NodeListener;
export {};

View File

@@ -0,0 +1,784 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.defineVisitor = defineVisitor;
const commons_1 = require("./commons");
const eslint_utils_1 = require("@eslint-community/eslint-utils");
const ast_utils_1 = require("../../utils/ast-utils");
/**
* Creates AST event handlers for ES nodes.
*
* @param context The rule context.
* @returns AST event handlers.
*/
function defineVisitor(context) {
const { sourceCode, offsets, options } = context;
/**
* Find the head of chaining nodes.
*/
function getChainHeadNode(node) {
let target = node;
let parent = (0, ast_utils_1.getParent)(target);
while (parent &&
(parent.type === 'AssignmentExpression' ||
parent.type === 'AssignmentPattern' ||
parent.type === 'BinaryExpression' ||
parent.type === 'LogicalExpression')) {
const prevToken = sourceCode.getTokenBefore(target);
const nextToken = sourceCode.getTokenAfter(target);
if (prevToken &&
(0, eslint_utils_1.isOpeningParenToken)(prevToken) &&
nextToken &&
(0, eslint_utils_1.isClosingParenToken)(nextToken)) {
// The chain is broken because it is enclosed in parentheses.
break;
}
target = parent;
parent = (0, ast_utils_1.getParent)(target);
}
return target;
}
const visitor = {
Program(node) {
for (const body of node.body) {
if (body.type === 'SvelteText' && !body.value.trim()) {
continue;
}
offsets.setStartOffsetToken(sourceCode.getFirstToken(body), 0);
}
},
ArrayExpression(node) {
const firstToken = sourceCode.getFirstToken(node);
const rightToken = sourceCode.getTokenAfter(node.elements[node.elements.length - 1] || firstToken, { filter: eslint_utils_1.isClosingBracketToken, includeComments: false });
offsets.setOffsetElementList(node.elements, firstToken, rightToken, 1);
},
ArrayPattern(node) {
visitor.ArrayExpression(node);
},
ArrowFunctionExpression(node) {
const [firstToken, secondToken] = sourceCode.getFirstTokens(node, {
count: 2,
includeComments: false
});
const leftToken = node.async ? secondToken : firstToken;
const arrowToken = sourceCode.getTokenBefore(node.body, {
filter: eslint_utils_1.isArrowToken,
includeComments: false
});
if (node.async) {
offsets.setOffsetToken(secondToken, 1, firstToken);
}
if ((0, eslint_utils_1.isOpeningParenToken)(leftToken)) {
const rightToken = sourceCode.getTokenAfter(node.params[node.params.length - 1] || leftToken, { filter: eslint_utils_1.isClosingParenToken, includeComments: false });
offsets.setOffsetElementList(node.params, leftToken, rightToken, 1);
}
offsets.setOffsetToken(arrowToken, 1, firstToken);
const bodyFirstToken = sourceCode.getFirstToken(node.body);
offsets.setOffsetToken(bodyFirstToken, (0, eslint_utils_1.isOpeningBraceToken)(bodyFirstToken) ? 0 : 1, firstToken);
},
AssignmentExpression(node) {
const baseNode = getChainHeadNode(node);
const opToken = sourceCode.getTokenAfter(node.left, {
filter: eslint_utils_1.isNotClosingParenToken,
includeComments: false
});
const baseToken = baseNode.type === 'AssignmentExpression' || baseNode.type === 'AssignmentPattern'
? sourceCode.getFirstToken(baseNode)
: (0, commons_1.getFirstAndLastTokens)(sourceCode, baseNode).firstToken;
const leftToken = (0, commons_1.getFirstAndLastTokens)(sourceCode, node.left).firstToken;
const rightToken = (0, commons_1.getFirstAndLastTokens)(sourceCode, node.right).firstToken;
offsets.setOffsetToken([leftToken === baseToken ? null : leftToken, opToken, rightToken], 1, baseToken);
},
AssignmentPattern(node) {
visitor.AssignmentExpression(node);
},
BinaryExpression(node) {
visitor.AssignmentExpression(node);
},
LogicalExpression(node) {
visitor.AssignmentExpression(node);
},
AwaitExpression(node) {
// `await`, `...`, or UnaryOperator
const firstToken = sourceCode.getFirstToken(node);
const nextToken = sourceCode.getTokenAfter(firstToken);
offsets.setOffsetToken(nextToken, 1, firstToken);
},
RestElement(node) {
visitor.AwaitExpression(node);
},
SpreadElement(node) {
visitor.AwaitExpression(node);
},
UnaryExpression(node) {
visitor.AwaitExpression(node);
},
BlockStatement(node) {
offsets.setOffsetElementList(node.body, sourceCode.getFirstToken(node), sourceCode.getLastToken(node), 1);
},
ClassBody(node) {
visitor.BlockStatement(node);
},
BreakStatement(node) {
if (node.label) {
const firstToken = sourceCode.getFirstToken(node);
const nextToken = sourceCode.getTokenAfter(firstToken);
offsets.setOffsetToken(nextToken, 1, firstToken);
}
},
ContinueStatement(node) {
visitor.BreakStatement(node);
},
CallExpression(node) {
const typeArguments = node.typeArguments ?? node.typeParameters;
const firstToken = sourceCode.getFirstToken(node);
const leftParenToken = sourceCode.getTokenAfter(typeArguments || node.callee, {
filter: eslint_utils_1.isOpeningParenToken,
includeComments: false
});
const rightParenToken = sourceCode.getLastToken(node);
if (typeArguments) {
offsets.setOffsetToken(sourceCode.getFirstToken(typeArguments), 1, firstToken);
}
for (const optionalToken of sourceCode.getTokensBetween(sourceCode.getLastToken(typeArguments || node.callee), leftParenToken, { filter: isOptionalToken, includeComments: false })) {
offsets.setOffsetToken(optionalToken, 1, firstToken);
}
offsets.setOffsetToken(leftParenToken, 1, firstToken);
offsets.setOffsetElementList(node.arguments, leftParenToken, rightParenToken, 1);
},
CatchClause(node) {
const catchToken = sourceCode.getFirstToken(node);
if (node.param != null) {
const leftParenToken = sourceCode.getTokenBefore(node.param);
const rightParenToken = sourceCode.getTokenAfter(node.param);
offsets.setOffsetToken(leftParenToken, 1, catchToken);
offsets.setOffsetElementList([node.param], leftParenToken, rightParenToken, 1);
}
const bodyToken = sourceCode.getFirstToken(node.body);
offsets.setOffsetToken(bodyToken, 0, catchToken);
},
ClassDeclaration(node) {
const classToken = sourceCode.getFirstToken(node);
if (node.id != null) {
offsets.setOffsetToken(sourceCode.getFirstToken(node.id), 1, classToken);
}
if (node.superClass != null) {
const extendsToken = sourceCode.getTokenBefore(node.superClass);
const superClassToken = sourceCode.getTokenAfter(extendsToken);
offsets.setOffsetToken(extendsToken, 1, classToken);
offsets.setOffsetToken(superClassToken, 1, extendsToken);
}
const bodyToken = sourceCode.getFirstToken(node.body);
offsets.setOffsetToken(bodyToken, 0, classToken);
},
ClassExpression(node) {
visitor.ClassDeclaration(node);
},
ConditionalExpression(node) {
const questionToken = sourceCode.getTokenAfter(node.test, {
filter: eslint_utils_1.isNotClosingParenToken,
includeComments: false
});
const consequentToken = sourceCode.getTokenAfter(questionToken);
const colonToken = sourceCode.getTokenAfter(node.consequent, {
filter: eslint_utils_1.isNotClosingParenToken,
includeComments: false
});
const alternateToken = sourceCode.getTokenAfter(colonToken);
let baseNode = node;
let parent = (0, ast_utils_1.getParent)(baseNode);
while (parent && parent.type === 'ConditionalExpression' && parent.alternate === baseNode) {
baseNode = parent;
parent = (0, ast_utils_1.getParent)(baseNode);
}
const baseToken = sourceCode.getFirstToken(baseNode);
offsets.setOffsetToken([questionToken, colonToken], 1, baseToken);
offsets.setOffsetToken(consequentToken, 1, questionToken);
offsets.setOffsetToken(alternateToken, 1, colonToken);
},
DoWhileStatement(node) {
const doToken = sourceCode.getFirstToken(node);
const whileToken = sourceCode.getTokenAfter(node.body, {
filter: eslint_utils_1.isNotClosingParenToken,
includeComments: false
});
const leftParenToken = sourceCode.getTokenAfter(whileToken);
const rightParenToken = sourceCode.getTokenAfter(node.test);
const bodyFirstToken = sourceCode.getFirstToken(node.body);
offsets.setOffsetToken(bodyFirstToken, (0, eslint_utils_1.isOpeningBraceToken)(bodyFirstToken) ? 0 : 1, doToken);
offsets.setOffsetToken(whileToken, 0, doToken);
offsets.setOffsetToken(leftParenToken, 1, whileToken);
offsets.setOffsetElementList([node.test], leftParenToken, rightParenToken, 1);
},
ExportAllDeclaration(node) {
const exportToken = sourceCode.getFirstToken(node);
const tokens = sourceCode.getTokensBetween(exportToken, node.source);
const fromIndex = tokens.findIndex((t) => t.value === 'from');
const fromToken = tokens[fromIndex];
const beforeTokens = tokens.slice(0, fromIndex);
const afterTokens = [...tokens.slice(fromIndex + 1), sourceCode.getFirstToken(node.source)];
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- type bug?
if (!node.exported) {
// export * from "mod"
offsets.setOffsetToken(beforeTokens, 1, exportToken);
}
else {
// export * as foo from "mod"
const asIndex = beforeTokens.findIndex((t) => t.value === 'as');
offsets.setOffsetToken(beforeTokens.slice(0, asIndex), 1, exportToken);
offsets.setOffsetToken(beforeTokens.slice(asIndex), 1, beforeTokens[asIndex - 1]);
}
offsets.setOffsetToken(fromToken, 0, exportToken);
offsets.setOffsetToken(afterTokens, 1, fromToken);
// assertions
const lastToken = sourceCode.getLastToken(node, {
filter: eslint_utils_1.isNotSemicolonToken,
includeComments: false
});
const assertionTokens = sourceCode.getTokensBetween(node.source, lastToken);
if (assertionTokens.length) {
const assertToken = assertionTokens.shift();
offsets.setOffsetToken(assertToken, 0, exportToken);
const assertionOpen = assertionTokens.shift();
if (assertionOpen) {
offsets.setOffsetToken(assertionOpen, 1, assertToken);
offsets.setOffsetElementList(assertionTokens, assertionOpen, lastToken, 1);
}
}
},
ExportDefaultDeclaration(node) {
const exportToken = sourceCode.getFirstToken(node);
const declarationToken = (0, commons_1.getFirstAndLastTokens)(sourceCode, node.declaration).firstToken;
const defaultTokens = sourceCode.getTokensBetween(exportToken, declarationToken);
offsets.setOffsetToken([...defaultTokens, declarationToken], 1, exportToken);
},
ExportNamedDeclaration(node) {
const exportToken = sourceCode.getFirstToken(node);
if (node.declaration) {
// export var foo = 1;
const declarationToken = sourceCode.getFirstToken(node.declaration);
offsets.setOffsetToken(declarationToken, 1, exportToken);
}
else {
const firstSpecifier = node.specifiers[0];
if (!firstSpecifier || firstSpecifier.type === 'ExportSpecifier') {
// export {foo, bar}; or export {foo, bar} from "mod";
const leftBraceTokens = firstSpecifier
? sourceCode.getTokensBetween(exportToken, firstSpecifier)
: [sourceCode.getTokenAfter(exportToken)];
const rightBraceToken = node.source
? sourceCode.getTokenBefore(node.source, {
filter: eslint_utils_1.isClosingBraceToken,
includeComments: false
})
: sourceCode.getLastToken(node, {
filter: eslint_utils_1.isClosingBraceToken,
includeComments: false
});
offsets.setOffsetToken(leftBraceTokens, 0, exportToken);
offsets.setOffsetElementList(node.specifiers, leftBraceTokens[leftBraceTokens.length - 1], rightBraceToken, 1);
if (node.source) {
const [fromToken, ...tokens] = sourceCode.getTokensBetween(rightBraceToken, node.source);
offsets.setOffsetToken(fromToken, 0, exportToken);
offsets.setOffsetToken([...tokens, sourceCode.getFirstToken(node.source)], 1, fromToken);
// assertions
const lastToken = sourceCode.getLastToken(node, {
filter: eslint_utils_1.isNotSemicolonToken,
includeComments: false
});
const assertionTokens = sourceCode.getTokensBetween(node.source, lastToken);
if (assertionTokens.length) {
const assertToken = assertionTokens.shift();
offsets.setOffsetToken(assertToken, 0, exportToken);
const assertionOpen = assertionTokens.shift();
if (assertionOpen) {
offsets.setOffsetToken(assertionOpen, 1, assertToken);
offsets.setOffsetElementList(assertionTokens, assertionOpen, lastToken, 1);
}
}
}
}
else {
// maybe babel-eslint
}
}
},
ExportSpecifier(node) {
const tokens = sourceCode.getTokens(node);
let firstToken = tokens.shift();
if (firstToken.value === 'type') {
const typeToken = firstToken;
firstToken = tokens.shift();
offsets.setOffsetToken(firstToken, 0, typeToken);
}
offsets.setOffsetToken(tokens, 1, firstToken);
},
ForInStatement(node) {
const forToken = sourceCode.getFirstToken(node);
const awaitToken = (node.type === 'ForOfStatement' && node.await && sourceCode.getTokenAfter(forToken)) ||
null;
const leftParenToken = sourceCode.getTokenAfter(awaitToken || forToken);
const leftToken = sourceCode.getFirstToken(node.left);
const inOrOfToken = sourceCode.getTokenAfter(node.left, {
filter: eslint_utils_1.isNotClosingParenToken,
includeComments: false
});
const rightToken = sourceCode.getTokenAfter(inOrOfToken);
const rightParenToken = sourceCode.getTokenBefore(node.body, {
filter: eslint_utils_1.isNotOpeningParenToken,
includeComments: false
});
if (awaitToken != null) {
offsets.setOffsetToken(awaitToken, 0, forToken);
}
offsets.setOffsetToken(leftParenToken, 1, forToken);
offsets.setOffsetToken(leftToken, 1, leftParenToken);
offsets.setOffsetToken([inOrOfToken, rightToken], 1, leftToken);
offsets.setOffsetToken(rightParenToken, 0, leftParenToken);
const bodyFirstToken = sourceCode.getFirstToken(node.body);
offsets.setOffsetToken(bodyFirstToken, (0, eslint_utils_1.isOpeningBraceToken)(bodyFirstToken) ? 0 : 1, forToken);
},
ForOfStatement(node) {
visitor.ForInStatement(node);
},
ForStatement(node) {
const forToken = sourceCode.getFirstToken(node);
const leftParenToken = sourceCode.getTokenAfter(forToken);
const rightParenToken = sourceCode.getTokenBefore(node.body, {
filter: eslint_utils_1.isNotOpeningParenToken,
includeComments: false
});
offsets.setOffsetToken(leftParenToken, 1, forToken);
offsets.setOffsetElementList([node.init, node.test, node.update], leftParenToken, rightParenToken, 1);
const bodyFirstToken = sourceCode.getFirstToken(node.body);
offsets.setOffsetToken(bodyFirstToken, (0, eslint_utils_1.isOpeningBraceToken)(bodyFirstToken) ? 0 : 1, forToken);
},
FunctionDeclaration(node) {
const firstToken = sourceCode.getFirstToken(node);
const leftParenToken = sourceCode.getTokenBefore(node.params[0] ||
node.returnType ||
sourceCode.getTokenBefore(node.body), {
filter: eslint_utils_1.isOpeningParenToken,
includeComments: false
});
let bodyBaseToken = null;
if (firstToken.type === 'Punctuator') {
// method
bodyBaseToken = sourceCode.getFirstToken((0, ast_utils_1.getParent)(node));
}
else {
let tokenOffset = 0;
for (const token of sourceCode.getTokensBetween(firstToken, leftParenToken)) {
if (token.value === '<') {
break;
}
if (token.value === '*' || (node.id && token.range[0] === node.id.range[0])) {
tokenOffset = 1;
}
offsets.setOffsetToken(token, tokenOffset, firstToken);
}
bodyBaseToken = firstToken;
}
const rightParenToken = sourceCode.getTokenAfter(node.params[node.params.length - 1] || leftParenToken, { filter: eslint_utils_1.isClosingParenToken, includeComments: false });
offsets.setOffsetToken(leftParenToken, 1, bodyBaseToken);
offsets.setOffsetElementList(node.params, leftParenToken, rightParenToken, 1);
const bodyToken = sourceCode.getFirstToken(node.body);
offsets.setOffsetToken(bodyToken, 0, bodyBaseToken);
},
FunctionExpression(node) {
visitor.FunctionDeclaration(node);
},
IfStatement(node) {
const [ifToken, ifLeftParenToken] = sourceCode.getFirstTokens(node, {
count: 2,
includeComments: false
});
const ifRightParenToken = sourceCode.getTokenBefore(node.consequent, {
filter: eslint_utils_1.isClosingParenToken,
includeComments: false
});
offsets.setOffsetToken(ifLeftParenToken, 1, ifToken);
offsets.setOffsetToken(ifRightParenToken, 0, ifLeftParenToken);
const consequentFirstToken = sourceCode.getFirstToken(node.consequent);
offsets.setOffsetToken(consequentFirstToken, (0, eslint_utils_1.isOpeningBraceToken)(consequentFirstToken) ? 0 : 1, ifToken);
if (node.alternate != null) {
const elseToken = sourceCode.getTokenAfter(node.consequent, {
filter: eslint_utils_1.isNotClosingParenToken,
includeComments: false
});
offsets.setOffsetToken(elseToken, 0, ifToken);
const alternateFirstToken = sourceCode.getFirstToken(node.alternate);
offsets.setOffsetToken(alternateFirstToken, (0, eslint_utils_1.isOpeningBraceToken)(alternateFirstToken) ? 0 : 1, elseToken);
}
},
ImportDeclaration(node) {
const importToken = sourceCode.getFirstToken(node);
const tokens = sourceCode.getTokensBetween(importToken, node.source);
const fromIndex = tokens.map((t) => t.value).lastIndexOf('from');
const { fromToken, beforeTokens, afterTokens } = fromIndex >= 0
? {
fromToken: tokens[fromIndex],
beforeTokens: tokens.slice(0, fromIndex),
afterTokens: [...tokens.slice(fromIndex + 1), sourceCode.getFirstToken(node.source)]
}
: {
fromToken: null,
beforeTokens: [...tokens, sourceCode.getFirstToken(node.source)],
afterTokens: []
};
const namedSpecifiers = [];
for (const specifier of node.specifiers) {
if (specifier.type === 'ImportSpecifier') {
namedSpecifiers.push(specifier);
}
else {
const removeTokens = sourceCode.getTokens(specifier);
removeTokens.shift();
for (const token of removeTokens) {
const i = beforeTokens.indexOf(token);
if (i >= 0) {
beforeTokens.splice(i, 1);
}
}
}
}
if (namedSpecifiers.length) {
const leftBrace = sourceCode.getTokenBefore(namedSpecifiers[0]);
const rightBrace = sourceCode.getTokenAfter(namedSpecifiers[namedSpecifiers.length - 1], {
filter: eslint_utils_1.isClosingBraceToken,
includeComments: false
});
offsets.setOffsetElementList(namedSpecifiers, leftBrace, rightBrace, 1);
for (const token of [...sourceCode.getTokensBetween(leftBrace, rightBrace), rightBrace]) {
const i = beforeTokens.indexOf(token);
if (i >= 0) {
beforeTokens.splice(i, 1);
}
}
}
if (beforeTokens.every((t) => (0, eslint_utils_1.isOpeningBraceToken)(t) || (0, eslint_utils_1.isClosingBraceToken)(t))) {
offsets.setOffsetToken(beforeTokens, 0, importToken);
}
else {
offsets.setOffsetToken(beforeTokens, 1, importToken);
}
if (fromToken) {
offsets.setOffsetToken(fromToken, 0, importToken);
offsets.setOffsetToken(afterTokens, 1, fromToken);
}
// assertions
const lastToken = sourceCode.getLastToken(node, {
filter: eslint_utils_1.isNotSemicolonToken,
includeComments: false
});
const assertionTokens = sourceCode.getTokensBetween(node.source, lastToken);
if (assertionTokens.length) {
const assertToken = assertionTokens.shift();
offsets.setOffsetToken(assertToken, 0, importToken);
const assertionOpen = assertionTokens.shift();
if (assertionOpen) {
offsets.setOffsetToken(assertionOpen, 1, assertToken);
offsets.setOffsetElementList(assertionTokens, assertionOpen, lastToken, 1);
}
}
},
ImportExpression(node) {
const firstToken = sourceCode.getFirstToken(node);
const rightToken = sourceCode.getLastToken(node);
const leftToken = sourceCode.getTokenAfter(firstToken, {
filter: eslint_utils_1.isOpeningParenToken,
includeComments: false
});
offsets.setOffsetToken(leftToken, 1, firstToken);
offsets.setOffsetElementList([node.source], leftToken, rightToken, 1);
},
ImportNamespaceSpecifier(node) {
const tokens = sourceCode.getTokens(node);
const firstToken = tokens.shift();
offsets.setOffsetToken(tokens, 1, firstToken);
},
ImportSpecifier(node) {
visitor.ExportSpecifier(node);
},
LabeledStatement(node) {
const labelToken = sourceCode.getFirstToken(node);
const colonToken = sourceCode.getTokenAfter(labelToken);
const bodyToken = sourceCode.getTokenAfter(colonToken);
offsets.setOffsetToken([colonToken, bodyToken], 1, labelToken);
},
SvelteReactiveStatement(node) {
visitor.LabeledStatement(node);
},
MemberExpression(node) {
const objectToken = sourceCode.getFirstToken(node);
if (node.type === 'MemberExpression' && node.computed) {
const leftBracketToken = sourceCode.getTokenBefore(node.property, {
filter: eslint_utils_1.isOpeningBracketToken,
includeComments: false
});
const rightBracketToken = sourceCode.getTokenAfter(node.property, {
filter: eslint_utils_1.isClosingBracketToken,
includeComments: false
});
for (const optionalToken of sourceCode.getTokensBetween(sourceCode.getLastToken(node.object), leftBracketToken, { filter: isOptionalToken, includeComments: false })) {
offsets.setOffsetToken(optionalToken, 1, objectToken);
}
offsets.setOffsetToken(leftBracketToken, 1, objectToken);
offsets.setOffsetElementList([node.property], leftBracketToken, rightBracketToken, 1);
}
else {
const dotToken = sourceCode.getTokenBefore(node.property);
const propertyToken = sourceCode.getTokenAfter(dotToken);
offsets.setOffsetToken([dotToken, propertyToken], 1, objectToken);
}
},
MetaProperty(node) {
visitor.MemberExpression(node);
},
MethodDefinition(node) {
const firstToken = sourceCode.getFirstToken(node);
const keyTokens = (0, commons_1.getFirstAndLastTokens)(sourceCode, node.key);
const prefixTokens = sourceCode.getTokensBetween(firstToken, keyTokens.firstToken);
if (node.computed) {
prefixTokens.pop(); // pop [
}
offsets.setOffsetToken(prefixTokens, 0, firstToken);
let lastKeyToken;
if (node.computed) {
const leftBracketToken = sourceCode.getTokenBefore(keyTokens.firstToken);
const rightBracketToken = (lastKeyToken = sourceCode.getTokenAfter(keyTokens.lastToken));
offsets.setOffsetToken(leftBracketToken, 0, firstToken);
offsets.setOffsetElementList([node.key], leftBracketToken, rightBracketToken, 1);
}
else {
offsets.setOffsetToken(keyTokens.firstToken, 0, firstToken);
lastKeyToken = keyTokens.lastToken;
}
if (node.value) {
const initToken = sourceCode.getFirstToken(node.value);
offsets.setOffsetToken([...sourceCode.getTokensBetween(lastKeyToken, initToken), initToken], 1, lastKeyToken);
}
},
Property(node) {
visitor.MethodDefinition(node);
},
NewExpression(node) {
const typeArguments = node.typeArguments ?? node.typeParameters;
const newToken = sourceCode.getFirstToken(node);
const calleeTokens = (0, commons_1.getFirstAndLastTokens)(sourceCode, node.callee);
offsets.setOffsetToken(calleeTokens.firstToken, 1, newToken);
if (typeArguments) {
offsets.setOffsetToken(sourceCode.getFirstToken(typeArguments), 1, calleeTokens.firstToken);
}
const leftParenBefore = typeArguments || calleeTokens.lastToken;
if (node.arguments.length || leftParenBefore.range[1] < node.range[1]) {
const rightParenToken = sourceCode.getLastToken(node);
const leftParenToken = sourceCode.getTokenAfter(leftParenBefore);
offsets.setOffsetToken(leftParenToken, 1, calleeTokens.firstToken);
offsets.setOffsetElementList(node.arguments, leftParenToken, rightParenToken, 1);
}
},
ObjectExpression(node) {
const firstToken = sourceCode.getFirstToken(node);
const rightToken = sourceCode.getTokenAfter(node.properties[node.properties.length - 1] || firstToken, { filter: eslint_utils_1.isClosingBraceToken, includeComments: false });
offsets.setOffsetElementList(node.properties, firstToken, rightToken, 1);
},
ObjectPattern(node) {
visitor.ObjectExpression(node);
},
PropertyDefinition(node) {
visitor.MethodDefinition(node);
},
ReturnStatement(node) {
if (node.argument) {
const firstToken = sourceCode.getFirstToken(node);
const nextToken = sourceCode.getTokenAfter(firstToken);
offsets.setOffsetToken(nextToken, 1, firstToken);
}
},
ThrowStatement(node) {
visitor.ReturnStatement(node);
},
SequenceExpression(node) {
const firstToken = sourceCode.getFirstToken(node);
offsets.setOffsetElementList(node.expressions, firstToken, null, 0);
},
SwitchCase(node) {
const caseToken = sourceCode.getFirstToken(node);
if (node.test != null) {
const testTokens = (0, commons_1.getFirstAndLastTokens)(sourceCode, node.test);
const colonToken = sourceCode.getTokenAfter(testTokens.lastToken);
offsets.setOffsetToken([testTokens.firstToken, colonToken], 1, caseToken);
}
else {
const colonToken = sourceCode.getTokenAfter(caseToken);
offsets.setOffsetToken(colonToken, 1, caseToken);
}
if (node.consequent.length === 1 && node.consequent[0].type === 'BlockStatement') {
offsets.setOffsetToken(sourceCode.getFirstToken(node.consequent[0]), 0, caseToken);
}
else {
for (const statement of node.consequent) {
offsets.setOffsetToken((0, commons_1.getFirstAndLastTokens)(sourceCode, statement).firstToken, 1, caseToken);
}
}
},
SwitchStatement(node) {
const switchToken = sourceCode.getFirstToken(node);
const { firstToken: leftParenToken, lastToken: rightParenToken } = (0, commons_1.getFirstAndLastTokens)(sourceCode, node.discriminant);
const leftBraceToken = sourceCode.getTokenAfter(rightParenToken);
const rightBraceToken = sourceCode.getLastToken(node);
offsets.setOffsetToken(leftParenToken, 1, switchToken);
offsets.setOffsetElementList([node.discriminant], leftParenToken, rightParenToken, 1);
offsets.setOffsetToken(leftBraceToken, 0, switchToken);
offsets.setOffsetElementList(node.cases, leftBraceToken, rightBraceToken, options.switchCase);
},
TaggedTemplateExpression(node) {
const tagTokens = (0, commons_1.getFirstAndLastTokens)(sourceCode, node.tag);
offsets.setOffsetToken(sourceCode.getFirstToken(node.quasi), 1, tagTokens.firstToken);
},
TemplateLiteral(node) {
const firstToken = sourceCode.getFirstToken(node);
const quasiTokens = node.quasis.slice(1).map((n) => sourceCode.getFirstToken(n));
const expressionToken = node.quasis.slice(0, -1).map((n) => sourceCode.getTokenAfter(n));
offsets.setOffsetToken(quasiTokens, 0, firstToken);
offsets.setOffsetToken(expressionToken, 1, firstToken);
},
TryStatement(node) {
const tryToken = sourceCode.getFirstToken(node);
const tryBlockToken = sourceCode.getFirstToken(node.block);
offsets.setOffsetToken(tryBlockToken, 0, tryToken);
if (node.handler != null) {
const catchToken = sourceCode.getFirstToken(node.handler);
offsets.setOffsetToken(catchToken, 0, tryToken);
}
if (node.finalizer != null) {
const finallyToken = sourceCode.getTokenBefore(node.finalizer);
const finallyBlockToken = sourceCode.getFirstToken(node.finalizer);
offsets.setOffsetToken([finallyToken, finallyBlockToken], 0, tryToken);
}
},
UpdateExpression(node) {
const firstToken = sourceCode.getFirstToken(node);
const nextToken = sourceCode.getTokenAfter(firstToken);
offsets.setOffsetToken(nextToken, 1, firstToken);
},
VariableDeclaration(node) {
offsets.setOffsetElementList(node.declarations, sourceCode.getFirstToken(node), null, 1);
},
VariableDeclarator(node) {
if (node.init != null) {
const idToken = sourceCode.getFirstToken(node);
const eqToken = sourceCode.getTokenAfter(node.id);
const initToken = sourceCode.getTokenAfter(eqToken);
offsets.setOffsetToken([eqToken, initToken], 1, idToken);
}
},
WhileStatement(node) {
const firstToken = sourceCode.getFirstToken(node);
const leftParenToken = sourceCode.getTokenAfter(firstToken);
const rightParenToken = sourceCode.getTokenBefore(node.body, {
filter: eslint_utils_1.isClosingParenToken,
includeComments: false
});
offsets.setOffsetToken(leftParenToken, 1, firstToken);
offsets.setOffsetToken(rightParenToken, 0, leftParenToken);
const bodyFirstToken = sourceCode.getFirstToken(node.body);
offsets.setOffsetToken(bodyFirstToken, (0, eslint_utils_1.isOpeningBraceToken)(bodyFirstToken) ? 0 : 1, firstToken);
},
WithStatement(node) {
visitor.WhileStatement(node);
},
YieldExpression(node) {
if (node.argument != null) {
const [yieldToken, secondToken] = sourceCode.getFirstTokens(node, {
count: 2,
includeComments: false
});
offsets.setOffsetToken(secondToken, 1, yieldToken);
if (node.delegate) {
offsets.setOffsetToken(sourceCode.getTokenAfter(secondToken), 1, yieldToken);
}
}
},
// ----------------------------------------------------------------------
// SINGLE TOKEN NODES
// ----------------------------------------------------------------------
DebuggerStatement() {
// noop
},
Identifier() {
// noop
},
ImportDefaultSpecifier() {
// noop
},
Literal() {
// noop
},
PrivateIdentifier() {
// noop
},
Super() {
// noop
},
TemplateElement() {
// noop
},
ThisExpression() {
// noop
},
// ----------------------------------------------------------------------
// WRAPPER NODES
// ----------------------------------------------------------------------
ExpressionStatement() {
// noop
},
ChainExpression() {
// noop
},
EmptyStatement() {
// noop
}
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ignore
const commonVisitor = {
':statement, PropertyDefinition'(node) {
const firstToken = sourceCode.getFirstToken(node);
const lastToken = sourceCode.getLastToken(node);
if ((0, eslint_utils_1.isSemicolonToken)(lastToken) && firstToken !== lastToken) {
const next = sourceCode.getTokenAfter(lastToken);
if (!next || lastToken.loc.start.line < next.loc.start.line) {
// End of line semicolons
offsets.setOffsetToken(lastToken, 0, firstToken);
}
}
},
':expression'(node) {
// Proc parentheses.
let leftToken = sourceCode.getTokenBefore(node);
let rightToken = sourceCode.getTokenAfter(node);
let firstToken = sourceCode.getFirstToken(node);
while (leftToken &&
(0, eslint_utils_1.isOpeningParenToken)(leftToken) &&
rightToken &&
(0, eslint_utils_1.isClosingParenToken)(rightToken)) {
offsets.setOffsetToken(firstToken, 1, leftToken);
offsets.setOffsetToken(rightToken, 0, leftToken);
firstToken = leftToken;
leftToken = sourceCode.getTokenBefore(leftToken);
rightToken = sourceCode.getTokenAfter(rightToken);
}
}
};
const v = visitor;
return {
...v,
...commonVisitor
};
}
/**
* Checks whether given text is known button type
*/
function isOptionalToken(token) {
return token.type === 'Punctuator' && token.value === '?.';
}

View File

@@ -0,0 +1,10 @@
import type { RuleContext, RuleListener } from '../../types';
import type { IndentOptions } from './commons';
/**
* Creates AST event handlers for html-indent.
*
* @param context The rule context.
* @param defaultOptions The default value of options.
* @returns AST event handlers.
*/
export declare function defineVisitor(context: RuleContext, defaultOptions: Partial<IndentOptions>): RuleListener;

View File

@@ -0,0 +1,274 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.defineVisitor = defineVisitor;
const SV = __importStar(require("./svelte"));
const ES = __importStar(require("./es"));
const TS = __importStar(require("./ts"));
const ast_1 = require("./ast");
const eslint_utils_1 = require("@eslint-community/eslint-utils");
const offset_context_1 = require("./offset-context");
const compat_1 = require("../../utils/compat");
/**
* Normalize options.
* @param type The type of indentation.
* @param options Other options.
* @param defaultOptions The default value of options.
* @returns Normalized options.
*/
function parseOptions(options, defaultOptions) {
const ret = {
indentChar: ' ',
indentScript: true,
indentSize: 2,
switchCase: 1,
alignAttributesVertically: false,
ignoredNodes: [],
...defaultOptions
};
if (Number.isSafeInteger(options.indent)) {
ret.indentSize = Number(options.indent);
}
else if (options.indent === 'tab') {
ret.indentChar = '\t';
ret.indentSize = 1;
}
if (typeof options.indentScript === 'boolean') {
ret.indentScript = options.indentScript;
}
if (options.switchCase != null && Number.isSafeInteger(options.switchCase)) {
ret.switchCase = options.switchCase;
}
if (options.ignoredNodes != null) {
ret.ignoredNodes = options.ignoredNodes;
}
if (options.alignAttributesVertically && ret.indentChar === ' ') {
ret.alignAttributesVertically = true;
}
else if (ret.indentChar !== ' ') {
ret.alignAttributesVertically = false;
}
return ret;
}
/**
* Creates AST event handlers for html-indent.
*
* @param context The rule context.
* @param defaultOptions The default value of options.
* @returns AST event handlers.
*/
function defineVisitor(context, defaultOptions) {
if (!(0, compat_1.getFilename)(context).endsWith('.svelte'))
return {};
const options = parseOptions(context.options[0] || {}, defaultOptions);
const sourceCode = (0, compat_1.getSourceCode)(context);
const offsets = new offset_context_1.OffsetContext({ sourceCode, options });
/**
* Get the text of the indentation part of the given location.
*/
function getIndentText({ line, column }) {
return sourceCode.lines[line - 1].slice(0, column);
}
/**
* Validate the given token with the pre-calculated expected indentation.
*/
function validateToken(token, expectedIndent) {
const line = token.loc.start.line;
const indentText = getIndentText(token.loc.start);
// `indentText` contains non-whitespace characters.
if (indentText.trim() !== '') {
return;
}
const actualIndent = token.loc.start.column;
const mismatchCharIndexes = [];
for (let i = 0; i < indentText.length; ++i) {
if (indentText[i] !== options.indentChar) {
mismatchCharIndexes.push(i);
}
}
if (actualIndent !== expectedIndent) {
const loc = {
start: { line, column: 0 },
end: { line, column: actualIndent }
};
context.report({
loc,
messageId: 'unexpectedIndentation',
data: {
expectedIndent: String(expectedIndent),
actualIndent: String(actualIndent),
expectedUnit: options.indentChar === '\t' ? 'tab' : 'space',
actualUnit: mismatchCharIndexes.length
? 'whitespace'
: options.indentChar === '\t'
? 'tab'
: 'space',
expectedIndentPlural: expectedIndent === 1 ? '' : 's',
actualIndentPlural: actualIndent === 1 ? '' : 's'
},
fix(fixer) {
return fixer.replaceTextRange([sourceCode.getIndexFromLoc(loc.start), sourceCode.getIndexFromLoc(loc.end)], options.indentChar.repeat(expectedIndent));
}
});
return;
}
for (const i of mismatchCharIndexes) {
const loc = {
start: { line, column: i },
end: { line, column: i + 1 }
};
context.report({
loc,
messageId: 'unexpectedChar',
data: {
expected: JSON.stringify(options.indentChar),
actual: JSON.stringify(indentText[i])
},
fix(fixer) {
return fixer.replaceTextRange([sourceCode.getIndexFromLoc(loc.start), sourceCode.getIndexFromLoc(loc.end)], options.indentChar);
}
});
}
}
/** Process line tokens */
function processLine(tokens, prevComments, prevToken, calculator) {
const firstToken = tokens[0];
const actualIndent = firstToken.loc.start.column;
const expectedIndent = calculator.getExpectedIndentFromTokens(tokens);
if (expectedIndent == null) {
calculator.saveExpectedIndent(tokens, actualIndent);
return;
}
calculator.saveExpectedIndent(tokens, Math.min(...tokens
.map((t) => calculator.getExpectedIndentFromToken(t))
.filter((i) => i != null)));
let prev = prevToken;
if (prevComments.length) {
if (prev && prev.loc.end.line < prevComments[0].loc.start.line) {
validateToken(prevComments[0], expectedIndent);
}
prev = prevComments[prevComments.length - 1];
}
if (prev && prev.loc.end.line < tokens[0].loc.start.line) {
validateToken(tokens[0], expectedIndent);
}
}
const indentContext = {
sourceCode,
options,
offsets
};
const nodesVisitor = {
...ES.defineVisitor(indentContext),
...SV.defineVisitor(indentContext),
...TS.defineVisitor(indentContext)
};
const knownNodes = new Set(Object.keys(nodesVisitor));
/**
* Build a visitor combined with a visitor to handle the given ignore selector.
*/
function compositingIgnoresVisitor(visitor) {
for (const ignoreSelector of options.ignoredNodes) {
const key = `${ignoreSelector}:exit`;
if (visitor[key]) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ignore
const handler = visitor[key];
visitor[key] = function (node, ...args) {
const ret = handler.call(this, node, ...args);
offsets.ignore(node);
return ret;
};
}
else {
visitor[key] = (node) => offsets.ignore(node);
}
}
return visitor;
}
return compositingIgnoresVisitor({
...nodesVisitor,
'*:exit'(node) {
// Ignore tokens of unknown nodes.
if (!knownNodes.has(node.type)) {
// debugger
// console.log(node.type, node.loc!.start.line)
offsets.ignore(node);
}
},
'Program:exit'(node) {
const calculator = offsets.getOffsetCalculator();
let prevToken = null;
for (const { prevComments, tokens } of iterateLineTokens()) {
processLine(tokens, prevComments, prevToken, calculator);
prevToken = tokens[tokens.length - 1];
}
/** Iterate line tokens */
function* iterateLineTokens() {
let line = 0;
let prevComments = [];
let bufferTokens = [];
for (const token of sourceCode.getTokens(node, {
includeComments: true,
filter: ast_1.isNotWhitespace
})) {
const thisLine = token.loc.start.line;
if (line === thisLine || bufferTokens.length === 0) {
bufferTokens.push(token);
}
else {
if ((0, eslint_utils_1.isCommentToken)(bufferTokens[0]) && bufferTokens.every(eslint_utils_1.isCommentToken)) {
prevComments.push(bufferTokens[0]);
}
else {
yield {
prevComments,
tokens: bufferTokens
};
prevComments = [];
}
bufferTokens = [token];
}
line = thisLine;
}
if (bufferTokens.length && !bufferTokens.every(eslint_utils_1.isCommentToken)) {
yield {
prevComments,
tokens: bufferTokens
};
}
}
}
});
}

View File

@@ -0,0 +1,88 @@
import type { ASTNode, SourceCode } from '../../types';
import type { AnyToken, IndentOptions, MaybeNode } from './commons';
declare const enum OffsetDataType {
normal = 0,
align = 1,
start = 2
}
type OffsetData = {
type: OffsetDataType.normal;
base: number;
offset: number;
expectedIndent?: number;
} | {
type: OffsetDataType.align;
base: number;
alignIndent: number;
expectedIndent?: number;
} | {
type: OffsetDataType.start;
offset: number;
expectedIndent?: number;
};
export declare class OffsetContext {
private readonly sourceCode;
private readonly options;
private readonly offsets;
private readonly ignoreRanges;
constructor(arg: {
sourceCode: SourceCode;
options: IndentOptions;
});
/**
* Set offset to the given index.
*/
setOffsetIndex(index: number, offset: number, base: number): void;
/**
* Set align indent to the given index.
*/
private setAlignIndent;
/**
* Set offset to the given tokens.
*/
setOffsetToken(token: AnyToken | null | undefined | (AnyToken | null | undefined)[], offset: number, baseToken: AnyToken): void;
/**
* Copy offset to the given index from srcIndex.
*/
copyOffset(index: number, srcIndex: number): void;
/**
* Set start offset to the given index.
*/
setStartOffsetIndex(index: number, offset: number): void;
/**
* Set start offset to the given token.
*/
setStartOffsetToken(token: AnyToken | null | undefined | (AnyToken | null | undefined)[], offset: number): void;
setOffsetElementList(nodes: (ASTNode | AnyToken | MaybeNode | null | undefined)[], baseNodeOrToken: ASTNode | AnyToken | MaybeNode, lastNodeOrToken: ASTNode | AnyToken | MaybeNode | null, offset: number, align?: boolean): void;
private _setOffsetElementList;
/**
* Ignore range of the given node.
*/
ignore(node: ASTNode): void;
getOffsetCalculator(): OffsetCalculator;
}
export declare class OffsetCalculator {
private readonly options;
private readonly offsets;
private readonly ignoreRanges;
constructor(arg: {
offsets: Map<number, OffsetData>;
options: IndentOptions;
ignoreRanges: [number, number][];
});
/**
* Calculate correct indentation of the given index.
*/
private getExpectedIndentFromIndex;
/**
* Calculate correct indentation of the given token.
*/
getExpectedIndentFromToken(token: AnyToken): number | null;
/**
* Calculate correct indentation of the line of the given tokens.
*/
getExpectedIndentFromTokens(tokens: AnyToken[]): null | number;
/** Save expected indent to give tokens */
saveExpectedIndent(tokens: AnyToken[], expectedIndent: number): void;
}
export {};

View File

@@ -0,0 +1,225 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.OffsetCalculator = exports.OffsetContext = void 0;
const ast_1 = require("./ast");
const commons_1 = require("./commons");
const commons_2 = require("./commons");
class OffsetContext {
constructor(arg) {
this.offsets = new Map();
this.ignoreRanges = new Map();
this.sourceCode = arg.sourceCode;
this.options = arg.options;
}
/**
* Set offset to the given index.
*/
setOffsetIndex(index, offset, base) {
if (index === base) {
return;
}
this.offsets.set(index, {
type: 0 /* OffsetDataType.normal */,
base,
offset
});
}
/**
* Set align indent to the given index.
*/
setAlignIndent(index, alignIndent, base) {
if (index === base) {
return;
}
this.offsets.set(index, {
type: 1 /* OffsetDataType.align */,
base,
alignIndent
});
}
/**
* Set offset to the given tokens.
*/
setOffsetToken(token, offset, baseToken) {
if (!token) {
return;
}
if (Array.isArray(token)) {
for (const t of token) {
this.setOffsetToken(t, offset, baseToken);
}
return;
}
this.setOffsetIndex(token.range[0], offset, baseToken.range[0]);
}
/**
* Copy offset to the given index from srcIndex.
*/
copyOffset(index, srcIndex) {
const offsetData = this.offsets.get(srcIndex);
if (!offsetData) {
return;
}
if (offsetData.type === 2 /* OffsetDataType.start */) {
this.setStartOffsetIndex(index, offsetData.offset);
}
else if (offsetData.type === 1 /* OffsetDataType.align */) {
this.setAlignIndent(index, offsetData.alignIndent, offsetData.base);
}
else {
this.setOffsetIndex(index, offsetData.offset, offsetData.base);
}
}
/**
* Set start offset to the given index.
*/
setStartOffsetIndex(index, offset) {
this.offsets.set(index, {
type: 2 /* OffsetDataType.start */,
offset
});
}
/**
* Set start offset to the given token.
*/
setStartOffsetToken(token, offset) {
if (!token) {
return;
}
if (Array.isArray(token)) {
for (const t of token) {
this.setStartOffsetToken(t, offset);
}
return;
}
this.setStartOffsetIndex(token.range[0], offset);
}
setOffsetElementList(nodes, baseNodeOrToken, lastNodeOrToken, offset, align) {
let setIndent = (token, baseToken) => this.setOffsetToken(token, offset, baseToken);
if (align) {
for (const n of nodes) {
if (n) {
if (!(0, commons_1.isBeginningOfLine)(this.sourceCode, n)) {
const startLoc = n.loc.start;
const alignIndent = startLoc.column - /^\s*/u.exec(this.sourceCode.lines[startLoc.line - 1])[0].length;
setIndent = (token, baseToken) => this.setAlignIndent(token.range[0], alignIndent, baseToken.range[0]);
}
break;
}
}
}
this._setOffsetElementList(nodes, baseNodeOrToken, lastNodeOrToken, setIndent);
}
_setOffsetElementList(nodes, baseNodeOrToken, lastNodeOrToken, setIndent) {
const baseToken = this.sourceCode.getFirstToken(baseNodeOrToken);
let prevToken = this.sourceCode.getLastToken(baseNodeOrToken);
for (const node of nodes) {
if (node == null) {
continue;
}
const elementTokens = (0, commons_2.getFirstAndLastTokens)(this.sourceCode, node, prevToken.range[1]);
let t = prevToken;
while ((t = this.sourceCode.getTokenAfter(t, {
includeComments: true,
filter: ast_1.isNotWhitespace
})) != null &&
t.range[1] <= elementTokens.firstToken.range[0]) {
setIndent(t, baseToken);
}
setIndent(elementTokens.firstToken, baseToken);
prevToken = elementTokens.lastToken;
}
if (lastNodeOrToken) {
const lastToken = this.sourceCode.getFirstToken(lastNodeOrToken);
let t = prevToken;
while ((t = this.sourceCode.getTokenAfter(t, {
includeComments: true,
filter: ast_1.isNotWhitespace
})) != null &&
t.range[1] <= lastToken.range[0]) {
setIndent(t, baseToken);
}
this.setOffsetToken(lastToken, 0, baseToken);
}
}
/**
* Ignore range of the given node.
*/
ignore(node) {
const range = node.range;
const n = this.ignoreRanges.get(range[0]) ?? 0;
this.ignoreRanges.set(range[0], Math.max(n, range[1]));
}
getOffsetCalculator() {
// eslint-disable-next-line @typescript-eslint/no-use-before-define -- ignore
return new OffsetCalculator({
offsets: this.offsets,
options: this.options,
ignoreRanges: [...this.ignoreRanges]
});
}
}
exports.OffsetContext = OffsetContext;
class OffsetCalculator {
constructor(arg) {
this.offsets = arg.offsets;
this.options = arg.options;
this.ignoreRanges = arg.ignoreRanges;
}
/**
* Calculate correct indentation of the given index.
*/
getExpectedIndentFromIndex(index) {
const offsetInfo = this.offsets.get(index);
if (offsetInfo == null) {
return null;
}
if (offsetInfo.expectedIndent != null) {
return offsetInfo.expectedIndent;
}
if (offsetInfo.type === 2 /* OffsetDataType.start */) {
return offsetInfo.offset * this.options.indentSize;
}
const baseIndent = this.getExpectedIndentFromIndex(offsetInfo.base);
if (baseIndent == null) {
return null;
}
if (offsetInfo.type === 1 /* OffsetDataType.align */) {
return baseIndent + offsetInfo.alignIndent;
}
return baseIndent + offsetInfo.offset * this.options.indentSize;
}
/**
* Calculate correct indentation of the given token.
*/
getExpectedIndentFromToken(token) {
return this.getExpectedIndentFromIndex(token.range[0]);
}
/**
* Calculate correct indentation of the line of the given tokens.
*/
getExpectedIndentFromTokens(tokens) {
for (const token of tokens) {
const index = token.range[0];
if (this.ignoreRanges.some(([f, t]) => f <= index && index < t)) {
return null;
}
const expectedIndent = this.getExpectedIndentFromIndex(index);
if (expectedIndent != null) {
return expectedIndent;
}
}
return null;
}
/** Save expected indent to give tokens */
saveExpectedIndent(tokens, expectedIndent) {
for (const token of tokens) {
const offsetInfo = this.offsets.get(token.range[0]);
if (offsetInfo == null) {
continue;
}
offsetInfo.expectedIndent = offsetInfo.expectedIndent ?? expectedIndent;
}
}
}
exports.OffsetCalculator = OffsetCalculator;

View File

@@ -0,0 +1,11 @@
import type { SvelteNodeListener } from '../../types-for-node';
import type { IndentContext } from './commons';
type NodeListener = SvelteNodeListener;
/**
* Creates AST event handlers for svelte nodes.
*
* @param context The rule context.
* @returns AST event handlers.
*/
export declare function defineVisitor(context: IndentContext): NodeListener;
export {};

View File

@@ -0,0 +1,468 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.defineVisitor = defineVisitor;
const ast_1 = require("./ast");
const commons_1 = require("./commons");
const commons_2 = require("./commons");
const commons_3 = require("./commons");
const eslint_utils_1 = require("@eslint-community/eslint-utils");
const PREFORMATTED_ELEMENT_NAMES = ['pre', 'textarea', 'template'];
/**
* Creates AST event handlers for svelte nodes.
*
* @param context The rule context.
* @returns AST event handlers.
*/
function defineVisitor(context) {
const { sourceCode, offsets, options } = context;
const visitor = {
// ----------------------------------------------------------------------
// ELEMENTS
// ----------------------------------------------------------------------
SvelteScriptElement(node) {
offsets.setOffsetElementList(node.body, node.startTag, node.endTag, options.indentScript ? 1 : 0);
},
SvelteStyleElement(node) {
node.children.forEach((n) => offsets.ignore(n));
},
SvelteElement(node) {
if (node.name.type === 'Identifier' || node.name.type === 'SvelteName') {
if (PREFORMATTED_ELEMENT_NAMES.includes(node.name.name)) {
const startTagToken = sourceCode.getFirstToken(node);
const endTagToken = node.endTag && sourceCode.getFirstToken(node.endTag);
offsets.setOffsetToken(endTagToken, 0, startTagToken);
node.children.forEach((n) => offsets.ignore(n));
return;
}
if (node.name.name === 'style') {
// Inline style tag
node.children.forEach((n) => offsets.ignore(n));
return;
}
}
if (node.endTag) {
offsets.setOffsetElementList(node.children.filter(isNotEmptyTextNode), node.startTag, node.endTag, 1);
}
},
// ----------------------------------------------------------------------
// TAGS
// ----------------------------------------------------------------------
SvelteStartTag(node) {
const openToken = sourceCode.getFirstToken(node);
const closeToken = sourceCode.getLastToken(node);
offsets.setOffsetElementList(node.attributes, openToken, closeToken, 1, options.alignAttributesVertically);
if (node.selfClosing) {
const slash = sourceCode.getTokenBefore(closeToken);
if (slash.value === '/') {
offsets.setOffsetToken(slash, 0, openToken);
}
}
},
SvelteEndTag(node) {
const openToken = sourceCode.getFirstToken(node);
const closeToken = sourceCode.getLastToken(node);
offsets.setOffsetElementList([], openToken, closeToken, 1);
},
// ----------------------------------------------------------------------
// ATTRIBUTES
// ----------------------------------------------------------------------
SvelteAttribute(node) {
const keyToken = sourceCode.getFirstToken(node);
const eqToken = sourceCode.getTokenAfter(node.key);
if (eqToken != null && eqToken.range[1] <= node.range[1]) {
offsets.setOffsetToken(eqToken, 1, keyToken);
const valueStartToken = sourceCode.getTokenAfter(eqToken);
if (valueStartToken != null && valueStartToken.range[1] <= node.range[1]) {
offsets.setOffsetToken(valueStartToken, 1, keyToken);
const values = node.type === 'SvelteAttribute' || node.type === 'SvelteStyleDirective'
? node.value
: [];
// process quoted
let processedValues = false;
if (valueStartToken.type === 'Punctuator') {
const quoted = ['"', "'"].includes(valueStartToken.value);
const mustache = !quoted && valueStartToken.value === '{';
if (quoted || mustache) {
const last = sourceCode.getLastToken(node);
if (last.type === 'Punctuator' &&
((quoted && last.value === valueStartToken.value) ||
(mustache && last.value === '}'))) {
offsets.setOffsetToken(last, 0, valueStartToken);
offsets.setOffsetElementList(values, valueStartToken, last, 1);
processedValues = true;
}
}
}
if (!processedValues) {
for (const val of values) {
const token = sourceCode.getFirstToken(val);
offsets.setOffsetToken(token, 0, valueStartToken);
}
}
}
}
},
SvelteDirective(node) {
visitor.SvelteAttribute(node);
},
SvelteStyleDirective(node) {
visitor.SvelteAttribute(node);
},
SvelteSpecialDirective(node) {
visitor.SvelteAttribute(node);
},
SvelteShorthandAttribute(node) {
const openToken = sourceCode.getFirstToken(node);
const closeToken = sourceCode.getLastToken(node);
offsets.setOffsetElementList([], openToken, closeToken, 1);
},
SvelteSpreadAttribute(node) {
visitor.SvelteShorthandAttribute(node);
},
// ----------------------------------------------------------------------
// ATTRIBUTE KEYS
// ----------------------------------------------------------------------
SvelteDirectiveKey(_node) {
// noop
},
SvelteSpecialDirectiveKey(_node) {
// noop
},
// ----------------------------------------------------------------------
// CONTENTS
// ----------------------------------------------------------------------
SvelteText(node) {
const tokens = sourceCode.getTokens(node, {
filter: ast_1.isNotWhitespace,
includeComments: false
});
const first = tokens.shift();
if (!first) {
return;
}
offsets.setOffsetToken(tokens, (0, commons_2.isBeginningOfLine)(sourceCode, first) ? 0 : (0, commons_1.isBeginningOfElement)(node) ? 1 : 0, first);
},
SvelteLiteral(node) {
const tokens = sourceCode.getTokens(node, {
filter: ast_1.isNotWhitespace,
includeComments: false
});
const first = tokens.shift();
if (!first) {
return;
}
offsets.setOffsetToken(tokens, (0, commons_2.isBeginningOfLine)(sourceCode, first) ? 0 : 1, first);
},
// ----------------------------------------------------------------------
// MUSTACHE TAGS
// ----------------------------------------------------------------------
SvelteMustacheTag(node) {
const openToken = sourceCode.getFirstToken(node);
const closeToken = sourceCode.getLastToken(node);
offsets.setOffsetElementList([node.expression], openToken, closeToken, 1);
},
SvelteDebugTag(node) {
const openToken = sourceCode.getFirstToken(node);
const closeToken = sourceCode.getLastToken(node);
offsets.setOffsetElementList(node.identifiers, openToken, closeToken, 1);
},
SvelteConstTag(node) {
const openToken = sourceCode.getFirstToken(node);
const constToken = sourceCode.getTokenAfter(openToken);
const declarationToken = sourceCode.getFirstToken(node.declaration);
const closeToken = sourceCode.getLastToken(node);
offsets.setOffsetToken(constToken, 1, openToken);
offsets.setOffsetToken(declarationToken, 1, openToken);
offsets.setOffsetToken(closeToken, 0, openToken);
},
SvelteRenderTag(node) {
const openToken = sourceCode.getFirstToken(node);
const renderToken = sourceCode.getTokenAfter(openToken);
offsets.setOffsetToken(renderToken, 1, openToken);
offsets.setOffsetToken(node.expression, 1, renderToken);
},
// ----------------------------------------------------------------------
// BLOCKS
// ----------------------------------------------------------------------
SvelteIfBlock(node) {
const [openToken, ...ifTokens] = sourceCode.getFirstTokens(node, {
count: node.elseif ? 3 : 2,
includeComments: false
});
offsets.setOffsetToken(ifTokens, 1, openToken);
const exp = (0, commons_3.getFirstAndLastTokens)(sourceCode, node.expression);
offsets.setOffsetToken(exp.firstToken, 1, ifTokens[0]);
const closeOpenTagToken = sourceCode.getTokenAfter(exp.lastToken);
offsets.setOffsetToken(closeOpenTagToken, 0, openToken);
for (const child of node.children) {
const token = sourceCode.getFirstToken(child, {
includeComments: false,
filter: ast_1.isNotWhitespace
});
offsets.setOffsetToken(token, 1, openToken);
}
if (node.else) {
offsets.setOffsetToken(sourceCode.getFirstToken(node.else), 0, openToken);
if (node.else.elseif) {
// else if
return;
}
}
const [openCloseTagToken, endIfToken, closeCloseTagToken] = sourceCode.getLastTokens(node, {
count: 3,
includeComments: false
});
offsets.setOffsetToken(openCloseTagToken, 0, openToken);
offsets.setOffsetToken(endIfToken, 1, openCloseTagToken);
offsets.setOffsetToken(closeCloseTagToken, 0, openCloseTagToken);
},
SvelteElseBlock(node) {
if (node.elseif) {
return;
}
const [openToken, elseToken, closeToken] = sourceCode.getFirstTokens(node, {
count: 3,
includeComments: false
});
offsets.setOffsetToken(elseToken, 1, openToken);
offsets.setOffsetToken(closeToken, 0, openToken);
for (const child of node.children) {
const token = sourceCode.getFirstToken(child, {
includeComments: false,
filter: ast_1.isNotWhitespace
});
offsets.setOffsetToken(token, 1, openToken);
}
},
SvelteEachBlock(node) {
const [openToken, eachToken] = sourceCode.getFirstTokens(node, {
count: 2,
includeComments: false
});
offsets.setOffsetToken(eachToken, 1, openToken);
offsets.setOffsetElementList([node.expression, node.context, node.index], eachToken, null, 1);
if (node.key) {
const key = (0, commons_3.getFirstAndLastTokens)(sourceCode, node.key);
offsets.setOffsetToken(key.firstToken, 1, eachToken);
const closeOpenTagToken = sourceCode.getTokenAfter(key.lastToken);
offsets.setOffsetToken(closeOpenTagToken, 0, openToken);
}
else {
const closeOpenTagToken = sourceCode.getTokenAfter(node.index || node.context);
offsets.setOffsetToken(closeOpenTagToken, 0, openToken);
}
for (const child of node.children) {
const token = sourceCode.getFirstToken(child, {
includeComments: false,
filter: ast_1.isNotWhitespace
});
offsets.setOffsetToken(token, 1, openToken);
}
if (node.else) {
offsets.setOffsetToken(sourceCode.getFirstToken(node.else), 0, openToken);
}
const [openCloseTagToken, endEachToken, closeCloseTagToken] = sourceCode.getLastTokens(node, {
count: 3,
includeComments: false
});
offsets.setOffsetToken(openCloseTagToken, 0, openToken);
offsets.setOffsetToken(endEachToken, 1, openCloseTagToken);
offsets.setOffsetToken(closeCloseTagToken, 0, openCloseTagToken);
},
SvelteAwaitBlock(node) {
const [openToken, awaitToken] = sourceCode.getFirstTokens(node, {
count: 2,
includeComments: false
});
offsets.setOffsetToken(awaitToken, 1, openToken);
const exp = (0, commons_3.getFirstAndLastTokens)(sourceCode, node.expression);
offsets.setOffsetToken(exp.firstToken, 1, awaitToken);
if (node.pending) {
const closeOpenTagToken = sourceCode.getTokenAfter(exp.lastToken);
offsets.setOffsetToken(closeOpenTagToken, 0, openToken);
offsets.setOffsetToken(sourceCode.getFirstToken(node.pending, {
includeComments: false,
filter: ast_1.isNotWhitespace
}), 1, openToken);
}
if (node.then) {
if (node.kind === 'await-then') {
// {#await expression then value}
const thenToken = sourceCode.getTokenAfter(exp.lastToken);
offsets.setOffsetToken(thenToken, 1, openToken);
if (node.then.value) {
offsets.setOffsetToken(sourceCode.getFirstToken(node.then.value), 1, thenToken);
}
const closeOpenTagToken = sourceCode.getTokenAfter(node.then.value || thenToken);
offsets.setOffsetToken(closeOpenTagToken, 0, openToken);
}
else {
// {:then value}
offsets.setOffsetToken(sourceCode.getFirstToken(node.then), 0, openToken);
}
}
if (node.catch) {
if (node.kind === 'await-catch') {
// {#await expression catch error}
const catchToken = sourceCode.getTokenAfter(exp.lastToken);
offsets.setOffsetToken(catchToken, 1, openToken);
if (node.catch.error) {
offsets.setOffsetToken(sourceCode.getFirstToken(node.catch.error), 1, catchToken);
}
const closeOpenTagToken = sourceCode.getTokenAfter(node.catch.error || catchToken);
offsets.setOffsetToken(closeOpenTagToken, 0, openToken);
}
else {
// {:catch value}
offsets.setOffsetToken(sourceCode.getFirstToken(node.catch), 0, openToken);
}
}
const [openCloseTagToken, endAwaitToken, closeCloseTagToken] = sourceCode.getLastTokens(node, {
count: 3,
includeComments: false
});
offsets.setOffsetToken(openCloseTagToken, 0, openToken);
offsets.setOffsetToken(endAwaitToken, 1, openCloseTagToken);
offsets.setOffsetToken(closeCloseTagToken, 0, openCloseTagToken);
},
SvelteAwaitPendingBlock(node) {
const openToken = sourceCode.getFirstToken(node);
for (const child of node.children) {
const token = sourceCode.getFirstToken(child, {
includeComments: false,
filter: ast_1.isNotWhitespace
});
offsets.setOffsetToken(token, 1, openToken);
}
},
SvelteAwaitThenBlock(node) {
if (!node.awaitThen) {
// {:then value}
const [openToken, thenToken] = sourceCode.getFirstTokens(node, {
count: 2,
includeComments: false
});
offsets.setOffsetToken(thenToken, 1, openToken);
if (node.value) {
const valueToken = sourceCode.getFirstToken(node.value);
offsets.setOffsetToken(valueToken, 1, thenToken);
}
const closeOpenTagToken = sourceCode.getTokenAfter(node.value || thenToken);
offsets.setOffsetToken(closeOpenTagToken, 0, openToken);
}
const openToken = sourceCode.getFirstToken(node);
for (const child of node.children) {
const token = sourceCode.getFirstToken(child, {
includeComments: false,
filter: ast_1.isNotWhitespace
});
offsets.setOffsetToken(token, 1, openToken);
}
},
SvelteAwaitCatchBlock(node) {
if (!node.awaitCatch) {
// {:catch error}
const [openToken, catchToken] = sourceCode.getFirstTokens(node, {
count: 2,
includeComments: false
});
offsets.setOffsetToken(catchToken, 1, openToken);
if (node.error) {
const errorToken = sourceCode.getFirstToken(node.error);
offsets.setOffsetToken(errorToken, 1, catchToken);
}
const closeOpenTagToken = sourceCode.getTokenAfter(node.error || catchToken);
offsets.setOffsetToken(closeOpenTagToken, 0, openToken);
}
const openToken = sourceCode.getFirstToken(node);
for (const child of node.children) {
const token = sourceCode.getFirstToken(child, {
includeComments: false,
filter: ast_1.isNotWhitespace
});
offsets.setOffsetToken(token, 1, openToken);
}
},
SvelteKeyBlock(node) {
const [openToken, keyToken] = sourceCode.getFirstTokens(node, {
count: 2,
includeComments: false
});
offsets.setOffsetToken(keyToken, 1, openToken);
const exp = (0, commons_3.getFirstAndLastTokens)(sourceCode, node.expression);
offsets.setOffsetToken(exp.firstToken, 1, keyToken);
const closeOpenTagToken = sourceCode.getTokenAfter(exp.lastToken);
offsets.setOffsetToken(closeOpenTagToken, 0, openToken);
for (const child of node.children) {
const token = sourceCode.getFirstToken(child, {
includeComments: false,
filter: ast_1.isNotWhitespace
});
offsets.setOffsetToken(token, 1, openToken);
}
const [openCloseTagToken, endKeyToken, closeCloseTagToken] = sourceCode.getLastTokens(node, {
count: 3,
includeComments: false
});
offsets.setOffsetToken(openCloseTagToken, 0, openToken);
offsets.setOffsetToken(endKeyToken, 1, openCloseTagToken);
offsets.setOffsetToken(closeCloseTagToken, 0, openCloseTagToken);
},
SvelteSnippetBlock(node) {
const [openToken, snippetToken] = sourceCode.getFirstTokens(node, {
count: 2,
includeComments: false
});
offsets.setOffsetToken(snippetToken, 1, openToken);
const snippetName = sourceCode.getTokenAfter(snippetToken);
offsets.setOffsetToken(snippetName, 1, snippetToken);
const leftParenToken = sourceCode.getTokenBefore(node.params[0] || sourceCode.getLastToken(node), {
filter: eslint_utils_1.isOpeningParenToken,
includeComments: false
});
const rightParenToken = sourceCode.getTokenAfter(node.params[node.params.length - 1] || leftParenToken, {
filter: eslint_utils_1.isClosingParenToken,
includeComments: false
});
offsets.setOffsetToken(leftParenToken, 1, snippetName);
offsets.setOffsetElementList(node.params, leftParenToken, rightParenToken, 1);
const closeOpenTagToken = sourceCode.getTokenAfter(rightParenToken);
offsets.setOffsetToken(closeOpenTagToken, 0, openToken);
for (const child of node.children) {
const token = sourceCode.getFirstToken(child, {
includeComments: false,
filter: ast_1.isNotWhitespace
});
offsets.setOffsetToken(token, 1, openToken);
}
const [openCloseTagToken, endSnippetToken, closeCloseTagToken] = sourceCode.getLastTokens(node, { count: 3, includeComments: false });
offsets.setOffsetToken(openCloseTagToken, 0, openToken);
offsets.setOffsetToken(endSnippetToken, 1, openCloseTagToken);
offsets.setOffsetToken(closeCloseTagToken, 0, openCloseTagToken);
},
// ----------------------------------------------------------------------
// COMMENTS
// ----------------------------------------------------------------------
SvelteHTMLComment(_node) {
// noop
},
// ----------------------------------------------------------------------
// NAMES
// ----------------------------------------------------------------------
SvelteName(_node) {
// noop
},
SvelteMemberExpressionName(_node) {
// noop
}
};
return visitor;
}
/**
* Check whether the given node is not an empty text node.
* @param node The node to check.
* @returns `false` if the token is empty text node.
*/
function isNotEmptyTextNode(node) {
return !(node.type === 'SvelteText' && node.value.trim() === '');
}

View File

@@ -0,0 +1,11 @@
import type { IndentContext } from './commons';
import type { TSNodeListener } from '../../types-for-node';
type NodeListener = TSNodeListener;
/**
* Creates AST event handlers for svelte nodes.
*
* @param context The rule context.
* @returns AST event handlers.
*/
export declare function defineVisitor(context: IndentContext): NodeListener;
export {};

View File

@@ -0,0 +1,885 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.defineVisitor = defineVisitor;
const eslint_utils_1 = require("@eslint-community/eslint-utils");
const commons_1 = require("./commons");
const commons_2 = require("./commons");
/**
* Creates AST event handlers for svelte nodes.
*
* @param context The rule context.
* @returns AST event handlers.
*/
function defineVisitor(context) {
const { offsets, sourceCode } = context;
const visitor = {
TSTypeAnnotation(node) {
// : Type
// => Type
const [colonOrArrowToken, secondToken] = sourceCode.getFirstTokens(node, {
count: 2,
includeComments: false
});
const baseToken = sourceCode.getFirstToken(node.parent);
offsets.setOffsetToken([colonOrArrowToken, secondToken], 1, baseToken);
const before = sourceCode.getTokenBefore(colonOrArrowToken);
if (before && before.value === '?') {
offsets.setOffsetToken(before, 1, baseToken);
}
},
TSAsExpression(node) {
// foo as T
// or
// foo satisfies T
const expressionTokens = (0, commons_2.getFirstAndLastTokens)(sourceCode, node.expression);
const asOrSatisfiesToken = sourceCode.getTokenAfter(expressionTokens.lastToken);
offsets.setOffsetToken([asOrSatisfiesToken, (0, commons_2.getFirstAndLastTokens)(sourceCode, node.typeAnnotation).firstToken], 1, expressionTokens.firstToken);
},
TSSatisfiesExpression(node) {
// foo satisfies T
visitor.TSAsExpression(node);
},
TSTypeReference(node) {
// T<U>
// or
// const ErrorMap = Map<string, Error>
// ^^^^^^^^^^^^^^^^^^
const typeArguments = node.typeArguments ?? node.typeParameters;
if (typeArguments) {
const firstToken = sourceCode.getFirstToken(node);
offsets.setOffsetToken(sourceCode.getFirstToken(typeArguments), 1, firstToken);
}
},
TSInstantiationExpression(node) {
// const ErrorMap = Map<string, Error>
// ^^^^^^^^^^^^^^^^^^
visitor.TSTypeReference(node);
},
TSTypeParameterInstantiation(node) {
// <T>
offsets.setOffsetElementList(node.params, sourceCode.getFirstToken(node), sourceCode.getLastToken(node), 1);
},
TSTypeParameterDeclaration(node) {
// <T>
visitor.TSTypeParameterInstantiation(node);
},
TSTypeAliasDeclaration(node) {
// type T = {}
const typeToken = sourceCode.getFirstToken(node);
const idToken = sourceCode.getFirstToken(node.id);
offsets.setOffsetToken(idToken, 1, typeToken);
let eqToken;
if (node.typeParameters) {
offsets.setOffsetToken(sourceCode.getFirstToken(node.typeParameters), 1, idToken);
eqToken = sourceCode.getTokenAfter(node.typeParameters);
}
else {
eqToken = sourceCode.getTokenAfter(node.id);
}
const initToken = sourceCode.getTokenAfter(eqToken);
offsets.setOffsetToken([eqToken, initToken], 1, idToken);
},
TSFunctionType(node) {
// ()=>void
const firstToken = sourceCode.getFirstToken(node);
// new or < or (
let currToken = firstToken;
if (node.type === 'TSConstructorType') {
// currToken is new token
// < or (
currToken = sourceCode.getTokenAfter(currToken);
offsets.setOffsetToken(currToken, 1, firstToken);
}
if (node.typeParameters) {
// currToken is < token
// (
currToken = sourceCode.getTokenAfter(node.typeParameters);
offsets.setOffsetToken(currToken, 1, firstToken);
}
const leftParenToken = currToken;
const rightParenToken = sourceCode.getTokenAfter(node.params[node.params.length - 1] || leftParenToken, { filter: eslint_utils_1.isClosingParenToken, includeComments: false });
offsets.setOffsetElementList(node.params, leftParenToken, rightParenToken, 1);
const arrowToken = sourceCode.getTokenAfter(rightParenToken);
offsets.setOffsetToken(arrowToken, 1, leftParenToken);
},
TSConstructorType(node) {
// new ()=>void
visitor.TSFunctionType(node);
},
TSTypeLiteral(node) {
// {foo:any}
offsets.setOffsetElementList(node.members, sourceCode.getFirstToken(node), sourceCode.getLastToken(node), 1);
},
TSPropertySignature(node) {
// { target:any }
// ^^^^^^^^^^
const firstToken = sourceCode.getFirstToken(node);
const keyTokens = (0, commons_2.getFirstAndLastTokens)(sourceCode, node.key);
let keyLast;
if (node.computed) {
const closeBracket = sourceCode.getTokenAfter(keyTokens.lastToken);
offsets.setOffsetElementList([node.key], firstToken, closeBracket, 1);
keyLast = closeBracket;
}
else {
keyLast = keyTokens.lastToken;
}
if (node.typeAnnotation) {
const typeAnnotationToken = sourceCode.getFirstToken(node.typeAnnotation);
offsets.setOffsetToken([...sourceCode.getTokensBetween(keyLast, typeAnnotationToken), typeAnnotationToken], 1, firstToken);
}
else if (node.optional) {
const qToken = sourceCode.getLastToken(node);
offsets.setOffsetToken(qToken, 1, firstToken);
}
},
TSIndexSignature(node) {
// { [k: string ]: string[] };
// ^^^^^^^^^^^^^^^^^^^^^^
const leftBracketToken = sourceCode.getFirstToken(node);
const rightBracketToken = sourceCode.getTokenAfter(node.parameters[node.parameters.length - 1] || leftBracketToken, { filter: eslint_utils_1.isClosingBracketToken, includeComments: false });
offsets.setOffsetElementList(node.parameters, leftBracketToken, rightBracketToken, 1);
const keyLast = rightBracketToken;
if (node.typeAnnotation) {
const typeAnnotationToken = sourceCode.getFirstToken(node.typeAnnotation);
offsets.setOffsetToken([...sourceCode.getTokensBetween(keyLast, typeAnnotationToken), typeAnnotationToken], 1, leftBracketToken);
}
},
TSArrayType(node) {
// T[]
const firstToken = sourceCode.getFirstToken(node);
offsets.setOffsetToken(sourceCode.getLastTokens(node, { count: 2, includeComments: false }), 0, firstToken);
},
TSTupleType(node) {
// [T, U]
offsets.setOffsetElementList(node.elementTypes, sourceCode.getFirstToken(node), sourceCode.getLastToken(node), 1);
},
TSQualifiedName(node) {
// A.B
const objectToken = sourceCode.getFirstToken(node);
const dotToken = sourceCode.getTokenBefore(node.right);
const propertyToken = sourceCode.getTokenAfter(dotToken);
offsets.setOffsetToken([dotToken, propertyToken], 1, objectToken);
},
TSIndexedAccessType(node) {
// A[B]
const objectToken = sourceCode.getFirstToken(node);
const leftBracketToken = sourceCode.getTokenBefore(node.indexType, {
filter: eslint_utils_1.isOpeningBracketToken,
includeComments: false
});
const rightBracketToken = sourceCode.getTokenAfter(node.indexType, {
filter: eslint_utils_1.isClosingBracketToken,
includeComments: false
});
offsets.setOffsetToken(leftBracketToken, 1, objectToken);
offsets.setOffsetElementList([node.indexType], leftBracketToken, rightBracketToken, 1);
},
TSUnionType(node) {
// A | B
const firstToken = sourceCode.getFirstToken(node);
const types = [...node.types];
if ((0, commons_2.getFirstAndLastTokens)(sourceCode, types[0]).firstToken === firstToken) {
types.shift();
}
offsets.setOffsetElementList(types, firstToken, null, (0, commons_1.isBeginningOfLine)(sourceCode, firstToken) ? 0 : 1);
},
TSIntersectionType(node) {
// A & B
visitor.TSUnionType(node);
},
TSMappedType(node) {
// {[key in foo]: bar}
const leftBraceToken = sourceCode.getFirstToken(node);
const leftBracketToken = sourceCode.getTokenBefore(node.typeParameter);
const rightBracketToken = sourceCode.getTokenAfter(node.nameType || node.typeParameter);
offsets.setOffsetToken([...sourceCode.getTokensBetween(leftBraceToken, leftBracketToken), leftBracketToken], 1, leftBraceToken);
offsets.setOffsetElementList([node.typeParameter, node.nameType], leftBracketToken, rightBracketToken, 1);
const rightBraceToken = sourceCode.getLastToken(node);
if (node.typeAnnotation) {
const typeAnnotationToken = sourceCode.getFirstToken(node.typeAnnotation);
offsets.setOffsetToken([
...sourceCode.getTokensBetween(rightBracketToken, typeAnnotationToken),
typeAnnotationToken
], 1, leftBraceToken);
}
else {
offsets.setOffsetToken([...sourceCode.getTokensBetween(rightBracketToken, rightBraceToken)], 1, leftBraceToken);
}
offsets.setOffsetToken(rightBraceToken, 0, leftBraceToken);
},
TSTypeParameter(node) {
// {[key in foo]: bar}
// ^^^^^^^^^^
// type T<U extends V = W>
// ^^^^^^^^^^^^^^^
const [firstToken, ...afterTokens] = sourceCode.getTokens(node);
for (const child of [node.constraint, node.default]) {
if (!child) {
continue;
}
const [, ...removeTokens] = sourceCode.getTokens(child);
for (const token of removeTokens) {
const i = afterTokens.indexOf(token);
if (i >= 0) {
afterTokens.splice(i, 1);
}
}
}
const secondToken = afterTokens.shift();
if (!secondToken) {
return;
}
offsets.setOffsetToken(secondToken, 1, firstToken);
if (secondToken.value === 'extends') {
let prevToken = null;
let token = afterTokens.shift();
while (token) {
if (token.value === '=') {
break;
}
offsets.setOffsetToken(token, 1, secondToken);
prevToken = token;
token = afterTokens.shift();
}
while (token) {
offsets.setOffsetToken(token, 1, prevToken || secondToken);
token = afterTokens.shift();
}
}
else {
offsets.setOffsetToken(afterTokens, 1, firstToken);
}
},
TSConditionalType(node) {
// T extends Foo ? T : U
const checkTypeToken = sourceCode.getFirstToken(node);
const extendsToken = sourceCode.getTokenAfter(node.checkType);
const extendsTypeToken = sourceCode.getFirstToken(node.extendsType);
offsets.setOffsetToken(extendsToken, 1, checkTypeToken);
offsets.setOffsetToken(extendsTypeToken, 1, extendsToken);
const questionToken = sourceCode.getTokenAfter(node.extendsType, {
filter: eslint_utils_1.isNotClosingParenToken,
includeComments: false
});
const consequentToken = sourceCode.getTokenAfter(questionToken);
const colonToken = sourceCode.getTokenAfter(node.trueType, {
filter: eslint_utils_1.isNotClosingParenToken,
includeComments: false
});
const alternateToken = sourceCode.getTokenAfter(colonToken);
let baseNode = node;
let parent = baseNode.parent;
while (parent && parent.type === 'TSConditionalType' && parent.falseType === baseNode) {
baseNode = parent;
parent = baseNode.parent;
}
const baseToken = sourceCode.getFirstToken(baseNode);
offsets.setOffsetToken([questionToken, colonToken], 1, baseToken);
offsets.setOffsetToken(consequentToken, 1, questionToken);
offsets.setOffsetToken(alternateToken, 1, colonToken);
},
TSInterfaceDeclaration(node) {
// interface I {}
const interfaceToken = sourceCode.getFirstToken(node);
offsets.setOffsetToken(sourceCode.getFirstToken(node.id), 1, interfaceToken);
if (node.typeParameters != null) {
offsets.setOffsetToken(sourceCode.getFirstToken(node.typeParameters), 1, sourceCode.getFirstToken(node.id));
}
if (node.extends != null && node.extends.length) {
const extendsToken = sourceCode.getTokenBefore(node.extends[0]);
offsets.setOffsetToken(extendsToken, 1, interfaceToken);
offsets.setOffsetElementList(node.extends, extendsToken, null, 1);
}
// It may not calculate the correct location because the visitor key is not provided.
// if (node.implements != null && node.implements.length) {
// const implementsToken = sourceCode.getTokenBefore(node.implements[0])!
// offsets.setOffsetToken(implementsToken, 1, interfaceToken)
// offsets.setOffsetElementList( node.implements, implementsToken, null, 1)
// }
const bodyToken = sourceCode.getFirstToken(node.body);
offsets.setOffsetToken(bodyToken, 0, interfaceToken);
},
TSInterfaceBody(node) {
offsets.setOffsetElementList(node.body, sourceCode.getFirstToken(node), sourceCode.getLastToken(node), 1);
},
TSClassImplements(node) {
// class C implements T {}
// ^
const typeArguments = node.typeArguments ?? node.typeParameters;
if (typeArguments) {
offsets.setOffsetToken(sourceCode.getFirstToken(typeArguments), 1, sourceCode.getFirstToken(node));
}
},
TSInterfaceHeritage(node) {
// interface I extends E implements T {}
// ^ ^
visitor.TSClassImplements(node);
},
TSEnumDeclaration(node) {
// enum E {}
const firstToken = sourceCode.getFirstToken(node);
const idTokens = (0, commons_2.getFirstAndLastTokens)(sourceCode, node.id);
const prefixTokens = sourceCode.getTokensBetween(firstToken, idTokens.firstToken);
offsets.setOffsetToken(prefixTokens, 0, firstToken);
offsets.setOffsetToken(idTokens.firstToken, 1, firstToken);
const leftBraceToken = sourceCode.getTokenAfter(idTokens.lastToken);
const rightBraceToken = sourceCode.getLastToken(node);
offsets.setOffsetToken(leftBraceToken, 0, firstToken);
offsets.setOffsetElementList(node.members, leftBraceToken, rightBraceToken, 1);
},
TSModuleDeclaration(node) {
const firstToken = sourceCode.getFirstToken(node);
const idTokens = (0, commons_2.getFirstAndLastTokens)(sourceCode, node.id);
const prefixTokens = sourceCode.getTokensBetween(firstToken, idTokens.firstToken);
offsets.setOffsetToken(prefixTokens, 0, firstToken);
offsets.setOffsetToken(idTokens.firstToken, 1, firstToken);
if (node.body) {
const bodyFirstToken = sourceCode.getFirstToken(node.body);
offsets.setOffsetToken(bodyFirstToken, (0, eslint_utils_1.isOpeningBraceToken)(bodyFirstToken) ? 0 : 1, firstToken);
}
},
TSModuleBlock(node) {
visitor.TSInterfaceBody(node);
},
TSMethodSignature(node) {
// fn(arg: A): R | null;
const firstToken = sourceCode.getFirstToken(node);
const keyTokens = (0, commons_2.getFirstAndLastTokens)(sourceCode, node.key);
let keyLast;
if (node.computed) {
const closeBracket = sourceCode.getTokenAfter(keyTokens.lastToken);
offsets.setOffsetElementList([node.key], firstToken, closeBracket, 1);
keyLast = closeBracket;
}
else {
keyLast = keyTokens.lastToken;
}
const leftParenToken = sourceCode.getTokenAfter(keyLast, {
filter: eslint_utils_1.isOpeningParenToken,
includeComments: false
});
offsets.setOffsetToken([...sourceCode.getTokensBetween(keyLast, leftParenToken), leftParenToken], 1, firstToken);
const rightParenToken = sourceCode.getTokenAfter(node.params[node.params.length - 1] || leftParenToken, { filter: eslint_utils_1.isClosingParenToken, includeComments: false });
offsets.setOffsetElementList(node.params, leftParenToken, rightParenToken, 1);
if (node.returnType) {
const typeAnnotationToken = sourceCode.getFirstToken(node.returnType);
offsets.setOffsetToken([...sourceCode.getTokensBetween(keyLast, typeAnnotationToken), typeAnnotationToken], 1, firstToken);
}
},
TSCallSignatureDeclaration(node) {
// interface A { <T> (e: E): R }
// ^^^^^^^^^^^^^
const firstToken = sourceCode.getFirstToken(node);
// new or < or (
let currToken = firstToken;
if (node.type === 'TSConstructSignatureDeclaration') {
// currToken is new token
// < or (
currToken = sourceCode.getTokenAfter(currToken);
offsets.setOffsetToken(currToken, 1, firstToken);
}
if (node.typeParameters) {
// currToken is < token
// (
currToken = sourceCode.getTokenAfter(node.typeParameters);
offsets.setOffsetToken(currToken, 1, firstToken);
}
const leftParenToken = currToken;
const rightParenToken = sourceCode.getTokenAfter(node.params[node.params.length - 1] || leftParenToken, { filter: eslint_utils_1.isClosingParenToken, includeComments: false });
offsets.setOffsetElementList(node.params, leftParenToken, rightParenToken, 1);
if (node.returnType) {
const typeAnnotationToken = sourceCode.getFirstToken(node.returnType);
offsets.setOffsetToken([
...sourceCode.getTokensBetween(rightParenToken, typeAnnotationToken),
typeAnnotationToken
], 1, firstToken);
}
},
TSConstructSignatureDeclaration(node) {
// interface A { new <T> (e: E): R }
// ^^^^^^^^^^^^^^^^^
visitor.TSCallSignatureDeclaration(node);
},
TSEmptyBodyFunctionExpression(node) {
const firstToken = sourceCode.getFirstToken(node);
let leftParenToken, bodyBaseToken;
if (firstToken.type === 'Punctuator') {
// method
leftParenToken = firstToken;
bodyBaseToken = sourceCode.getFirstToken(node.parent);
}
else {
let nextToken = sourceCode.getTokenAfter(firstToken);
let nextTokenOffset = 0;
while (nextToken && !(0, eslint_utils_1.isOpeningParenToken)(nextToken) && nextToken.value !== '<') {
if (nextToken.value === '*' || (node.id && nextToken.range[0] === node.id.range[0])) {
nextTokenOffset = 1;
}
offsets.setOffsetToken(nextToken, nextTokenOffset, firstToken);
nextToken = sourceCode.getTokenAfter(nextToken);
}
leftParenToken = nextToken;
bodyBaseToken = firstToken;
}
if (!(0, eslint_utils_1.isOpeningParenToken)(leftParenToken) && node.typeParameters) {
leftParenToken = sourceCode.getTokenAfter(node.typeParameters);
}
const rightParenToken = sourceCode.getTokenAfter(node.params[node.params.length - 1] || leftParenToken, { filter: eslint_utils_1.isClosingParenToken, includeComments: false });
offsets.setOffsetToken(leftParenToken, 1, bodyBaseToken);
offsets.setOffsetElementList(node.params, leftParenToken, rightParenToken, 1);
},
TSDeclareFunction(node) {
// function fn(): void;
visitor.TSEmptyBodyFunctionExpression(node);
},
TSTypeOperator(node) {
// keyof T
const firstToken = sourceCode.getFirstToken(node);
const nextToken = sourceCode.getTokenAfter(firstToken);
offsets.setOffsetToken(nextToken, 1, firstToken);
},
TSTypeQuery(node) {
// type T = typeof a
visitor.TSTypeOperator(node);
},
TSInferType(node) {
// infer U
visitor.TSTypeOperator(node);
},
TSTypePredicate(node) {
// v is T
const firstToken = sourceCode.getFirstToken(node);
const opToken = sourceCode.getTokenAfter(node.parameterName, {
filter: eslint_utils_1.isNotClosingParenToken,
includeComments: false
});
const rightToken = node.typeAnnotation && (0, commons_2.getFirstAndLastTokens)(sourceCode, node.typeAnnotation).firstToken;
offsets.setOffsetToken([opToken, rightToken], 1, (0, commons_2.getFirstAndLastTokens)(sourceCode, firstToken).firstToken);
},
TSAbstractMethodDefinition(node) {
const { keyNode, valueNode } = node.type === 'TSEnumMember'
? { keyNode: node.id, valueNode: node.initializer }
: { keyNode: node.key, valueNode: node.value };
const firstToken = sourceCode.getFirstToken(node);
const keyTokens = (0, commons_2.getFirstAndLastTokens)(sourceCode, keyNode);
const prefixTokens = sourceCode.getTokensBetween(firstToken, keyTokens.firstToken);
if (node.computed) {
prefixTokens.pop(); // pop [
}
offsets.setOffsetToken(prefixTokens, 0, firstToken);
let lastKeyToken;
if (node.computed) {
const leftBracketToken = sourceCode.getTokenBefore(keyTokens.firstToken);
const rightBracketToken = (lastKeyToken = sourceCode.getTokenAfter(keyTokens.lastToken));
offsets.setOffsetToken(leftBracketToken, 0, firstToken);
offsets.setOffsetElementList([keyNode], leftBracketToken, rightBracketToken, 1);
}
else {
offsets.setOffsetToken(keyTokens.firstToken, 0, firstToken);
lastKeyToken = keyTokens.lastToken;
}
if (valueNode != null) {
const initToken = sourceCode.getFirstToken(valueNode);
offsets.setOffsetToken([...sourceCode.getTokensBetween(lastKeyToken, initToken), initToken], 1, lastKeyToken);
}
},
TSAbstractPropertyDefinition(node) {
visitor.TSAbstractMethodDefinition(node);
},
TSEnumMember(node) {
visitor.TSAbstractMethodDefinition(node);
},
TSAbstractAccessorProperty(node) {
visitor.TSAbstractMethodDefinition(node);
},
TSOptionalType(node) {
// [number?]
// ^^^^^^^
offsets.setOffsetToken(sourceCode.getLastToken(node), 1, sourceCode.getFirstToken(node));
},
TSNonNullExpression(node) {
// v!
visitor.TSOptionalType(node);
},
TSJSDocNonNullableType(
// @ts-expect-error -- Missing TSJSDocNonNullableType type
node) {
// T!
visitor.TSOptionalType(node);
},
TSTypeAssertion(node) {
// <const>
const firstToken = sourceCode.getFirstToken(node);
const expressionToken = (0, commons_2.getFirstAndLastTokens)(sourceCode, node.expression).firstToken;
offsets.setOffsetElementList([node.typeAnnotation], firstToken, sourceCode.getTokenBefore(expressionToken), 1);
offsets.setOffsetToken(expressionToken, 1, firstToken);
},
TSImportType(node) {
// import('foo').B
const typeArguments = node.typeArguments ?? node.typeParameters;
const firstToken = sourceCode.getFirstToken(node);
const leftParenToken = sourceCode.getTokenAfter(firstToken, {
filter: eslint_utils_1.isOpeningParenToken,
includeComments: false
});
offsets.setOffsetToken(leftParenToken, 1, firstToken);
const argument = node.argument ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- typescript-eslint<v6 node
node.parameter;
const rightParenToken = sourceCode.getTokenAfter(argument, {
filter: eslint_utils_1.isClosingParenToken,
includeComments: false
});
offsets.setOffsetElementList([argument], leftParenToken, rightParenToken, 1);
if (node.qualifier) {
const dotToken = sourceCode.getTokenBefore(node.qualifier);
const propertyToken = sourceCode.getTokenAfter(dotToken);
offsets.setOffsetToken([dotToken, propertyToken], 1, firstToken);
}
if (typeArguments) {
offsets.setOffsetToken(sourceCode.getFirstToken(typeArguments), 1, firstToken);
}
},
TSParameterProperty(node) {
// constructor(private a)
const firstToken = sourceCode.getFirstToken(node);
const parameterToken = sourceCode.getFirstToken(node.parameter);
offsets.setOffsetToken([...sourceCode.getTokensBetween(firstToken, parameterToken), parameterToken], 1, firstToken);
},
TSImportEqualsDeclaration(node) {
// import foo = require('foo')
const importToken = sourceCode.getFirstToken(node);
const idTokens = (0, commons_2.getFirstAndLastTokens)(sourceCode, node.id);
offsets.setOffsetToken(idTokens.firstToken, 1, importToken);
const opToken = sourceCode.getTokenAfter(idTokens.lastToken);
offsets.setOffsetToken([opToken, sourceCode.getFirstToken(node.moduleReference)], 1, idTokens.lastToken);
},
TSExternalModuleReference(node) {
// require('foo')
const requireToken = sourceCode.getFirstToken(node);
const leftParenToken = sourceCode.getTokenAfter(requireToken, {
filter: eslint_utils_1.isOpeningParenToken,
includeComments: false
});
const rightParenToken = sourceCode.getLastToken(node);
offsets.setOffsetToken(leftParenToken, 1, requireToken);
offsets.setOffsetElementList([node.expression], leftParenToken, rightParenToken, 1);
},
TSExportAssignment(node) {
// export = foo
const exportNode = sourceCode.getFirstToken(node);
const exprTokens = (0, commons_2.getFirstAndLastTokens)(sourceCode, node.expression);
const opToken = sourceCode.getTokenBefore(exprTokens.firstToken);
offsets.setOffsetToken([opToken, exprTokens.firstToken], 1, exportNode);
},
TSNamedTupleMember(node) {
// [a: string, ...b: string[]]
// ^^^^^^^^^
const labelToken = sourceCode.getFirstToken(node);
const elementTokens = (0, commons_2.getFirstAndLastTokens)(sourceCode, node.elementType);
offsets.setOffsetToken([
...sourceCode.getTokensBetween(labelToken, elementTokens.firstToken),
elementTokens.firstToken
], 1, labelToken);
},
TSRestType(node) {
// [a: string, ...b: string[]]
// ^^^^^^^^^^^^^^
const firstToken = sourceCode.getFirstToken(node);
const nextToken = sourceCode.getTokenAfter(firstToken);
offsets.setOffsetToken(nextToken, 1, firstToken);
},
TSNamespaceExportDeclaration(node) {
const firstToken = sourceCode.getFirstToken(node);
const idToken = sourceCode.getFirstToken(node.id);
offsets.setOffsetToken([...sourceCode.getTokensBetween(firstToken, idToken), idToken], 1, firstToken);
},
TSTemplateLiteralType(node) {
const firstToken = sourceCode.getFirstToken(node);
const quasiTokens = node.quasis.slice(1).map((n) => sourceCode.getFirstToken(n));
const expressionToken = node.quasis.slice(0, -1).map((n) => sourceCode.getTokenAfter(n));
offsets.setOffsetToken(quasiTokens, 0, firstToken);
offsets.setOffsetToken(expressionToken, 1, firstToken);
},
// ----------------------------------------------------------------------
// NON-STANDARD NODES
// ----------------------------------------------------------------------
Decorator(node) {
// @Decorator
const [atToken, secondToken] = sourceCode.getFirstTokens(node, {
count: 2,
includeComments: false
});
offsets.setOffsetToken(secondToken, 0, atToken);
const parent = node.parent;
const { decorators } = parent;
if (!decorators || decorators.length === 0) {
return;
}
if (decorators[0] === node) {
if (parent.range[0] === node.range[0]) {
const startParentToken = sourceCode.getTokenAfter(decorators[decorators?.length - 1]);
offsets.setOffsetToken(startParentToken, 0, atToken);
}
else {
const startParentToken = sourceCode.getFirstToken(parent.parent &&
(parent.parent.type === 'ExportDefaultDeclaration' ||
parent.parent.type === 'ExportNamedDeclaration') &&
node.range[0] < parent.parent.range[0]
? parent.parent
: parent);
offsets.copyOffset(atToken.range[0], startParentToken.range[0]);
}
}
else {
offsets.setOffsetToken(atToken, 0, sourceCode.getFirstToken(decorators[0]));
}
},
AccessorProperty(node) {
const keyNode = node.key;
const valueNode = node.value;
const firstToken = sourceCode.getFirstToken(node);
const keyTokens = (0, commons_2.getFirstAndLastTokens)(sourceCode, keyNode);
const prefixTokens = sourceCode.getTokensBetween(firstToken, keyTokens.firstToken);
if (node.computed) {
prefixTokens.pop(); // pop [
}
offsets.setOffsetToken(prefixTokens, 0, firstToken);
let lastKeyToken;
if (node.computed) {
const leftBracketToken = sourceCode.getTokenBefore(keyTokens.firstToken);
const rightBracketToken = (lastKeyToken = sourceCode.getTokenAfter(keyTokens.lastToken));
offsets.setOffsetToken(leftBracketToken, 0, firstToken);
offsets.setOffsetElementList([keyNode], leftBracketToken, rightBracketToken, 1);
}
else {
offsets.setOffsetToken(keyTokens.firstToken, 0, firstToken);
lastKeyToken = keyTokens.lastToken;
}
if (valueNode != null) {
const initToken = sourceCode.getFirstToken(valueNode);
offsets.setOffsetToken([...sourceCode.getTokensBetween(lastKeyToken, initToken), initToken], 1, lastKeyToken);
}
},
StaticBlock(node) {
const firstToken = sourceCode.getFirstToken(node);
let next = sourceCode.getTokenAfter(firstToken);
while (next && (0, eslint_utils_1.isNotOpeningBraceToken)(next)) {
offsets.setOffsetToken(next, 0, firstToken);
next = sourceCode.getTokenAfter(next);
}
offsets.setOffsetToken(next, 0, firstToken);
offsets.setOffsetElementList(node.body, next, sourceCode.getLastToken(node), 1);
},
ImportAttribute(node) {
const firstToken = sourceCode.getFirstToken(node);
const keyTokens = (0, commons_2.getFirstAndLastTokens)(sourceCode, node.key);
const prefixTokens = sourceCode.getTokensBetween(firstToken, keyTokens.firstToken);
offsets.setOffsetToken(prefixTokens, 0, firstToken);
offsets.setOffsetToken(keyTokens.firstToken, 0, firstToken);
const initToken = sourceCode.getFirstToken(node.value);
offsets.setOffsetToken([...sourceCode.getTokensBetween(keyTokens.lastToken, initToken), initToken], 1, keyTokens.lastToken);
},
// ----------------------------------------------------------------------
// SINGLE TOKEN NODES
// ----------------------------------------------------------------------
// VALUES KEYWORD
TSAnyKeyword() {
// noop
},
TSBigIntKeyword() {
// noop
},
TSBooleanKeyword() {
// noop
},
TSNeverKeyword() {
// noop
},
TSNullKeyword() {
// noop
},
TSNumberKeyword() {
// noop
},
TSObjectKeyword() {
// noop
},
TSStringKeyword() {
// noop
},
TSSymbolKeyword() {
// noop
},
TSUndefinedKeyword() {
// noop
},
TSUnknownKeyword() {
// noop
},
TSVoidKeyword() {
// noop
},
// MODIFIERS KEYWORD
TSAbstractKeyword() {
// noop
},
TSAsyncKeyword() {
// noop
},
TSPrivateKeyword() {
// noop
},
TSProtectedKeyword() {
// noop
},
TSPublicKeyword() {
// noop
},
TSReadonlyKeyword() {
// noop
},
TSStaticKeyword() {
// noop
},
// OTHERS KEYWORD
TSDeclareKeyword() {
// noop
},
TSExportKeyword() {
// noop
},
TSIntrinsicKeyword() {
// noop
},
// OTHERS
TSThisType() {
// noop
},
// ----------------------------------------------------------------------
// WRAPPER NODES
// ----------------------------------------------------------------------
TSLiteralType() {
// noop
}
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ignore
const commonsVisitor = {
// Process semicolons.
['TSTypeAliasDeclaration, TSCallSignatureDeclaration, TSConstructSignatureDeclaration, TSImportEqualsDeclaration,' +
'TSAbstractMethodDefinition, TSAbstractPropertyDefinition, AccessorProperty, TSAbstractAccessorProperty, TSEnumMember,' +
'TSPropertySignature, TSIndexSignature, TSMethodSignature,' +
'TSAbstractClassProperty, ClassProperty'](node) {
const firstToken = sourceCode.getFirstToken(node);
const lastToken = sourceCode.getLastToken(node);
if ((0, eslint_utils_1.isSemicolonToken)(lastToken) && firstToken !== lastToken) {
const next = sourceCode.getTokenAfter(lastToken);
if (!next || lastToken.loc.start.line < next.loc.start.line) {
// End of line semicolons
offsets.setOffsetToken(lastToken, 0, firstToken);
}
}
},
'*[type=/^TS/]'(node) {
if (node.type !== 'TSAnyKeyword' &&
node.type !== 'TSArrayType' &&
node.type !== 'TSBigIntKeyword' &&
node.type !== 'TSBooleanKeyword' &&
node.type !== 'TSConditionalType' &&
node.type !== 'TSConstructorType' &&
node.type !== 'TSFunctionType' &&
node.type !== 'TSImportType' &&
node.type !== 'TSIndexedAccessType' &&
node.type !== 'TSInferType' &&
node.type !== 'TSIntersectionType' &&
node.type !== 'TSIntrinsicKeyword' &&
node.type !== 'TSLiteralType' &&
node.type !== 'TSMappedType' &&
node.type !== 'TSNamedTupleMember' &&
node.type !== 'TSNeverKeyword' &&
node.type !== 'TSNullKeyword' &&
node.type !== 'TSNumberKeyword' &&
node.type !== 'TSObjectKeyword' &&
node.type !== 'TSOptionalType' &&
node.type !== 'TSRestType' &&
node.type !== 'TSStringKeyword' &&
node.type !== 'TSSymbolKeyword' &&
node.type !== 'TSTemplateLiteralType' &&
node.type !== 'TSThisType' &&
node.type !== 'TSTupleType' &&
node.type !== 'TSTypeLiteral' &&
node.type !== 'TSTypeOperator' &&
node.type !== 'TSTypePredicate' &&
node.type !== 'TSTypeQuery' &&
node.type !== 'TSTypeReference' &&
node.type !== 'TSUndefinedKeyword' &&
node.type !== 'TSUnionType' &&
node.type !== 'TSUnknownKeyword' &&
node.type !== 'TSVoidKeyword') {
return;
}
const typeNode = node;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ignore
if (typeNode.parent.type === 'TSParenthesizedType') {
return;
}
// Process parentheses.
let leftToken = sourceCode.getTokenBefore(typeNode);
let rightToken = sourceCode.getTokenAfter(typeNode);
let firstToken = sourceCode.getFirstToken(typeNode);
while (leftToken &&
(0, eslint_utils_1.isOpeningParenToken)(leftToken) &&
rightToken &&
(0, eslint_utils_1.isClosingParenToken)(rightToken)) {
offsets.setOffsetToken(firstToken, 1, leftToken);
offsets.setOffsetToken(rightToken, 0, leftToken);
firstToken = leftToken;
leftToken = sourceCode.getTokenBefore(leftToken);
rightToken = sourceCode.getTokenAfter(rightToken);
}
}
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ignore
const extendsESVisitor = {
['ClassDeclaration[implements], ClassDeclaration[typeParameters], ClassDeclaration[superTypeParameters],' +
'ClassExpression[implements], ClassExpression[typeParameters], ClassExpression[superTypeParameters]'](node) {
if (node.typeParameters != null) {
offsets.setOffsetToken(sourceCode.getFirstToken(node.typeParameters), 1, sourceCode.getFirstToken(node.id || node));
}
const superTypeArguments = node.superTypeArguments ?? node.superTypeParameters;
if (superTypeArguments != null && node.superClass != null) {
offsets.setOffsetToken(sourceCode.getFirstToken(superTypeArguments), 1, sourceCode.getFirstToken(node.superClass));
}
if (node.implements != null && node.implements.length) {
const classToken = sourceCode.getFirstToken(node);
const implementsToken = sourceCode.getTokenBefore(node.implements[0]);
offsets.setOffsetToken(implementsToken, 1, classToken);
offsets.setOffsetElementList(node.implements, implementsToken, null, 1);
}
}
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ignore
const deprecatedVisitor = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ignore
TSParenthesizedType(node) {
// (T)
offsets.setOffsetElementList([node.typeAnnotation], sourceCode.getFirstToken(node), sourceCode.getLastToken(node), 1);
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ignore
ClassProperty(node) {
visitor.TSAbstractMethodDefinition(node);
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ignore
TSAbstractClassProperty(node) {
visitor.TSAbstractMethodDefinition(node);
}
};
const v = visitor;
return {
...v,
...commonsVisitor,
...extendsESVisitor,
...deprecatedVisitor
};
}

View File

@@ -0,0 +1,2 @@
declare const _default: import("../types").RuleModule;
export default _default;

View File

@@ -0,0 +1,49 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
const indent_helpers_1 = require("./indent-helpers");
exports.default = (0, utils_1.createRule)('indent', {
meta: {
docs: {
description: 'enforce consistent indentation',
category: 'Stylistic Issues',
recommended: false,
conflictWithPrettier: true
},
fixable: 'whitespace',
schema: [
{
type: 'object',
properties: {
indent: {
anyOf: [{ type: 'integer', minimum: 1 }, { enum: ['tab'] }]
},
indentScript: { type: 'boolean' },
switchCase: { type: 'integer', minimum: 0 },
alignAttributesVertically: { type: 'boolean' },
ignoredNodes: {
type: 'array',
items: {
allOf: [
{ type: 'string' },
{ not: { type: 'string', pattern: ':exit$' } },
{ not: { type: 'string', pattern: '^\\s*$' } }
]
},
uniqueItems: true,
additionalItems: false
}
},
additionalProperties: false
}
],
messages: {
unexpectedChar: 'Expected {{expected}} character, but found {{actual}} character.',
unexpectedIndentation: 'Expected indentation of {{expectedIndent}} {{expectedUnit}}{{expectedIndentPlural}} but found {{actualIndent}} {{actualUnit}}{{actualIndentPlural}}.'
},
type: 'layout' // "problem",
},
create(context) {
return (0, indent_helpers_1.defineVisitor)(context, {});
}
});

View File

@@ -0,0 +1,2 @@
declare const _default: import("../types").RuleModule;
export default _default;

View File

@@ -0,0 +1,323 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const eslint_utils_1 = require("@eslint-community/eslint-utils");
const utils_1 = require("../utils");
const ast_utils_1 = require("../utils/ast-utils");
const svelte_eslint_parser_1 = require("svelte-eslint-parser");
const compat_1 = require("../utils/compat");
/**
* Get usage of `tick`
*/
function extractTickReferences(context) {
const referenceTracker = new eslint_utils_1.ReferenceTracker((0, compat_1.getSourceCode)(context).scopeManager.globalScope);
const a = referenceTracker.iterateEsmReferences({
svelte: {
[eslint_utils_1.ReferenceTracker.ESM]: true,
tick: {
[eslint_utils_1.ReferenceTracker.CALL]: true
}
}
});
return Array.from(a).map(({ node, path }) => {
return {
node: node,
name: path[path.length - 1]
};
});
}
/**
* Get usage of `setTimeout`, `setInterval`, `queueMicrotask`
*/
function extractTaskReferences(context) {
const referenceTracker = new eslint_utils_1.ReferenceTracker((0, compat_1.getSourceCode)(context).scopeManager.globalScope);
const a = referenceTracker.iterateGlobalReferences({
setTimeout: { [eslint_utils_1.ReferenceTracker.CALL]: true },
setInterval: { [eslint_utils_1.ReferenceTracker.CALL]: true },
queueMicrotask: { [eslint_utils_1.ReferenceTracker.CALL]: true }
});
return Array.from(a).map(({ node, path }) => {
return {
node: node,
name: path[path.length - 1]
};
});
}
/**
* If `node` is inside of `maybeAncestorNode`, return true.
*/
function isChildNode(maybeAncestorNode, node) {
let parent = node.parent;
while (parent) {
if (parent === maybeAncestorNode)
return true;
parent = parent.parent;
}
return false;
}
/**
* Return true if `node` is a function call.
*/
function isFunctionCall(node) {
if (node.type !== 'Identifier')
return false;
const { parent } = node;
if (parent?.type !== 'CallExpression')
return false;
return parent.callee.type === 'Identifier' && parent.callee.name === node.name;
}
/**
* Return true if `node` is a reactive variable.
*/
function isReactiveVariableNode(reactiveVariableReferences, node) {
if (node.type !== 'Identifier')
return false;
return reactiveVariableReferences.includes(node);
}
/**
* e.g. foo.bar = baz + 1
* If node is `foo`, return true.
* Otherwise, return false.
*/
function isNodeForAssign(node) {
const { parent } = node;
if (parent?.type === 'AssignmentExpression') {
return parent.left.type === 'Identifier' && parent.left.name === node.name;
}
return (parent?.type === 'MemberExpression' &&
parent.parent?.type === 'AssignmentExpression' &&
parent.parent.left.type === 'MemberExpression' &&
parent.parent.left.object.type === 'Identifier' &&
parent.parent.left.object.name === node.name);
}
/**
* Return true if `node` is inside of `then` or `catch`.
*/
function isPromiseThenOrCatchBody(node) {
if (!getDeclarationBody(node))
return false;
const { parent } = node;
if (parent?.type !== 'CallExpression' || parent?.callee?.type !== 'MemberExpression') {
return false;
}
const { property } = parent.callee;
if (property?.type !== 'Identifier')
return false;
return ['then', 'catch'].includes(property.name);
}
/**
* Get all reactive variable reference.
*/
function getReactiveVariableReferences(context) {
const scopeManager = (0, compat_1.getSourceCode)(context).scopeManager;
// Find the top-level (module or global) scope.
// Any variable defined at the top-level (module scope or global scope) can be made reactive.
const toplevelScope = scopeManager.globalScope?.childScopes.find((scope) => scope.type === 'module') ||
scopeManager.globalScope;
if (!toplevelScope) {
return [];
}
// Extracts all reactive references to variables defined in the top-level scope.
const reactiveVariableNodes = [];
for (const variable of toplevelScope.variables) {
for (const reference of variable.references) {
if (reference.identifier.type === 'Identifier' && !isFunctionCall(reference.identifier)) {
reactiveVariableNodes.push(reference.identifier);
}
}
}
return reactiveVariableNodes;
}
/**
* Get all tracked reactive variables.
*/
function getTrackedVariableNodes(reactiveVariableReferences, ast) {
const reactiveVariableNodes = new Set();
for (const identifier of reactiveVariableReferences) {
if (
// If the identifier is within the reactive statement range,
// it is used within the reactive statement.
ast.range[0] <= identifier.range[0] &&
identifier.range[1] <= ast.range[1]) {
reactiveVariableNodes.add(identifier);
}
}
return reactiveVariableNodes;
}
/** */
function getDeclarationBody(node, functionName) {
if (node.type === 'VariableDeclarator' &&
node.id.type === 'Identifier' &&
(!functionName || node.id.name === functionName)) {
if (node.init?.type === 'ArrowFunctionExpression' || node.init?.type === 'FunctionExpression') {
return node.init.body;
}
}
else if (node.type === 'FunctionDeclaration' &&
node.id?.type === 'Identifier' &&
(!functionName || node.id?.name === functionName)) {
return node.body;
}
else if (!functionName && node.type === 'ArrowFunctionExpression') {
return node.body;
}
return null;
}
/** */
function getFunctionDeclarationNode(context, functionCall) {
const variable = (0, ast_utils_1.findVariable)(context, functionCall);
if (!variable) {
return null;
}
for (const def of variable.defs) {
if (def.type === 'FunctionName') {
if (def.node.type === 'FunctionDeclaration') {
return def.node.body;
}
}
if (def.type === 'Variable') {
if (def.node.init &&
(def.node.init.type === 'FunctionExpression' ||
def.node.init.type === 'ArrowFunctionExpression')) {
return def.node.init.body;
}
}
}
return null;
}
/**
* If the node is inside of a function, return true.
*
* e.g. `$: await foo()`
* if `node` is `foo`, return false because reactive statement is not function.
*
* e.g. `const bar = () => foo()`
* if `node` is `foo`, return true.
*
*/
function isInsideOfFunction(node) {
let parent = node;
while (parent) {
parent = parent.parent;
if (!parent)
break;
if (parent.type === 'FunctionDeclaration' && parent.async)
return true;
if (parent.type === 'VariableDeclarator' &&
(parent.init?.type === 'FunctionExpression' ||
parent.init?.type === 'ArrowFunctionExpression') &&
parent.init?.async) {
return true;
}
}
return false;
}
/** Let's lint! */
function doLint(context, ast, callFuncIdentifiers, tickCallExpressions, taskReferences, reactiveVariableNames, reactiveVariableReferences, pIsSameTask) {
const processed = new Set();
verifyInternal(ast, callFuncIdentifiers, pIsSameTask);
/** verify for node */
function verifyInternal(ast, callFuncIdentifiers, pIsSameTask) {
if (processed.has(ast)) {
// Avoid infinite recursion with recursive references.
return;
}
processed.add(ast);
let isSameMicroTask = pIsSameTask;
const differentMicroTaskEnterNodes = [];
(0, svelte_eslint_parser_1.traverseNodes)(ast, {
enterNode(node) {
// Promise.then() or Promise.catch() is called.
if (isPromiseThenOrCatchBody(node)) {
differentMicroTaskEnterNodes.push(node);
isSameMicroTask = false;
}
// `tick`, `setTimeout`, `setInterval` , `queueMicrotask` is called
for (const { node: callExpression } of [...tickCallExpressions, ...taskReferences]) {
if (isChildNode(callExpression, node)) {
differentMicroTaskEnterNodes.push(node);
isSameMicroTask = false;
}
}
// left side of await block
if (node.parent?.type === 'AssignmentExpression' &&
node.parent?.right.type === 'AwaitExpression' &&
node.parent?.left === node) {
differentMicroTaskEnterNodes.push(node);
isSameMicroTask = false;
}
if (node.type === 'Identifier' && isFunctionCall(node)) {
// traverse used functions body
const functionDeclarationNode = getFunctionDeclarationNode(context, node);
if (functionDeclarationNode) {
verifyInternal(functionDeclarationNode, [...callFuncIdentifiers, node], isSameMicroTask);
}
}
if (!isSameMicroTask) {
if (isReactiveVariableNode(reactiveVariableReferences, node) &&
reactiveVariableNames.includes(node.name) &&
isNodeForAssign(node)) {
context.report({
node,
loc: node.loc,
messageId: 'unexpected'
});
callFuncIdentifiers.forEach((callFuncIdentifier) => {
context.report({
node: callFuncIdentifier,
loc: callFuncIdentifier.loc,
messageId: 'unexpectedCall',
data: {
variableName: node.name
}
});
});
}
}
},
leaveNode(node) {
if (node.type === 'AwaitExpression') {
if (ast.parent?.type === 'SvelteReactiveStatement') {
// MEMO: It checks that `await` is used in reactive statement directly or not.
// If `await` is used in inner function of a reactive statement, result of `isInsideOfFunction` will be `true`.
if (!isInsideOfFunction(node)) {
isSameMicroTask = false;
}
}
else {
isSameMicroTask = false;
}
}
if (differentMicroTaskEnterNodes.includes(node)) {
isSameMicroTask = true;
}
}
});
}
}
exports.default = (0, utils_1.createRule)('infinite-reactive-loop', {
meta: {
docs: {
description: "Svelte runtime prevents calling the same reactive statement twice in a microtask. But between different microtask, it doesn't prevent.",
category: 'Possible Errors',
// TODO Switch to recommended in the major version.
recommended: false
},
schema: [],
messages: {
unexpected: 'Possibly it may occur an infinite reactive loop.',
unexpectedCall: 'Possibly it may occur an infinite reactive loop because this function may update `{{variableName}}`.'
},
type: 'suggestion'
},
create(context) {
return {
['SvelteReactiveStatement']: (ast) => {
const tickCallExpressions = extractTickReferences(context);
const taskReferences = extractTaskReferences(context);
const reactiveVariableReferences = getReactiveVariableReferences(context);
const trackedVariableNodes = getTrackedVariableNodes(reactiveVariableReferences, ast);
doLint(context, ast.body, [], tickCallExpressions, taskReferences, Array.from(trackedVariableNodes).map((node) => node.name), reactiveVariableReferences, true);
}
};
}
});

View File

@@ -0,0 +1,2 @@
declare const _default: import("../types").RuleModule;
export default _default;

View File

@@ -0,0 +1,113 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
const compat_1 = require("../utils/compat");
/**
* Check whether the component is declared in a single line or not.
*/
function isSingleLine(node) {
return node.loc.start.line === node.loc.end.line;
}
/**
* Group attributes line by line.
*/
function groupAttributesByLine(attributes) {
const group = [];
for (const attr of attributes) {
if (group[0]?.[0]?.loc.end.line === attr.loc.start.line) {
group[0].push(attr);
}
else {
group.unshift([attr]);
}
}
return group.reverse();
}
exports.default = (0, utils_1.createRule)('max-attributes-per-line', {
meta: {
docs: {
description: 'enforce the maximum number of attributes per line',
category: 'Stylistic Issues',
recommended: false,
conflictWithPrettier: true
},
fixable: 'whitespace',
schema: [
{
type: 'object',
properties: {
multiline: {
type: 'number',
minimum: 1
},
singleline: {
type: 'number',
minimum: 1
}
},
additionalProperties: false
}
],
messages: {
requireNewline: "'{{name}}' should be on a new line."
},
type: 'layout'
},
create(context) {
const multilineMaximum = context.options[0]?.multiline ?? 1;
const singlelineMaximum = context.options[0]?.singleline ?? 1;
const sourceCode = (0, compat_1.getSourceCode)(context);
/**
* Report attributes
*/
function report(attribute) {
if (!attribute) {
return;
}
let name;
if (attribute.type === 'SvelteAttribute' ||
attribute.type === 'SvelteShorthandAttribute' ||
attribute.type === 'SvelteDirective' ||
attribute.type === 'SvelteStyleDirective' ||
attribute.type === 'SvelteSpecialDirective') {
name = sourceCode.text.slice(...attribute.key.range);
}
else {
// if (attribute.type === "SvelteSpreadAttribute")
name = sourceCode.text.slice(...attribute.range);
}
context.report({
node: attribute,
loc: attribute.loc,
messageId: 'requireNewline',
data: { name },
fix(fixer) {
// Find the closest token before the current attribute
// that is not a white space
const prevToken = sourceCode.getTokenBefore(attribute, {
includeComments: true
});
const range = [prevToken.range[1], attribute.range[0]];
return fixer.replaceTextRange(range, '\n');
}
});
}
return {
SvelteStartTag(node) {
const numberOfAttributes = node.attributes.length;
if (!numberOfAttributes)
return;
if (isSingleLine(node)) {
if (numberOfAttributes > singlelineMaximum) {
report(node.attributes[singlelineMaximum]);
}
}
else {
for (const attrs of groupAttributesByLine(node.attributes)) {
report(attrs[multilineMaximum]);
}
}
}
};
}
});

View File

@@ -0,0 +1,2 @@
declare const _default: import("../types").RuleModule;
export default _default;

View File

@@ -0,0 +1,275 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const eslint_utils_1 = require("@eslint-community/eslint-utils");
const utils_1 = require("../utils");
const ast_utils_1 = require("../utils/ast-utils");
const compat_1 = require("../utils/compat");
const VALUE_SCHEMA = { enum: ['never', 'always'] };
/**
* Normalize options.
*/
function parseOptions(options) {
return {
textExpressions: options?.textExpressions || 'never',
attributesAndProps: options?.attributesAndProps || 'never',
directiveExpressions: options?.directiveExpressions || 'never',
tags: {
openingBrace: options?.tags?.openingBrace || 'never',
closingBrace: options?.tags?.closingBrace || 'never'
}
};
}
exports.default = (0, utils_1.createRule)('mustache-spacing', {
meta: {
docs: {
description: 'enforce unified spacing in mustache',
category: 'Stylistic Issues',
recommended: false,
conflictWithPrettier: true
},
fixable: 'code',
schema: [
{
type: 'object',
properties: {
textExpressions: VALUE_SCHEMA,
attributesAndProps: VALUE_SCHEMA,
directiveExpressions: VALUE_SCHEMA,
tags: {
type: 'object',
properties: {
openingBrace: VALUE_SCHEMA,
closingBrace: {
enum: ['never', 'always', 'always-after-expression']
}
},
additionalProperties: false
}
},
additionalProperties: false
}
],
messages: {
expectedOpening: "Expected 1 space after '{', but not found.",
expectedClosing: "Expected 1 space before '}', but not found.",
unexpectedOpening: "Expected no space after '{', but found.",
unexpectedClosing: "Expected no space before '}', but found."
},
type: 'layout'
},
create(context) {
const options = parseOptions(context.options[0]);
const sourceCode = (0, compat_1.getSourceCode)(context);
/** Verify */
function verifyBraces(openingBrace, closingBrace, openingOption, closingOption, hasExpression) {
const firstToken = sourceCode.getTokenAfter(openingBrace, {
includeComments: true
});
if (openingOption === 'always') {
if (openingBrace.range[1] === firstToken.range[0]) {
context.report({
node: openingBrace,
messageId: 'expectedOpening',
fix: (fixer) => fixer.insertTextAfter(openingBrace, ' ')
});
}
}
else if (openingBrace.range[1] !== firstToken.range[0]) {
context.report({
loc: {
start: openingBrace.loc.start,
end: firstToken.loc.start
},
messageId: 'unexpectedOpening',
fix: (fixer) => fixer.removeRange([openingBrace.range[1], firstToken.range[0]])
});
}
if (!closingBrace) {
return;
}
const lastToken = sourceCode.getTokenBefore(closingBrace, {
includeComments: true
});
if (closingOption === 'always' ||
(closingOption === 'always-after-expression' && hasExpression)) {
if (closingBrace.range[0] === lastToken.range[1]) {
context.report({
node: closingBrace,
messageId: 'expectedClosing',
fix: (fixer) => fixer.insertTextBefore(closingBrace, ' ')
});
}
}
else if (closingBrace.range[0] !== lastToken.range[1]) {
context.report({
loc: {
start: lastToken.loc.end,
end: closingBrace.loc.end
},
messageId: 'unexpectedClosing',
fix: (fixer) => fixer.removeRange([lastToken.range[1], closingBrace.range[0]])
});
}
}
/** Verify for {expr} */
function verifyExpression(node, option) {
const mustacheTokens = (0, ast_utils_1.getMustacheTokens)(node, sourceCode);
if (!mustacheTokens) {
return;
}
verifyBraces(mustacheTokens.openToken, mustacheTokens.closeToken, option, option);
}
return {
SvelteMustacheTag(node) {
if (node.kind === 'raw') {
const mustacheTokens = (0, ast_utils_1.getMustacheTokens)(node, sourceCode);
verifyBraces(mustacheTokens.openToken, mustacheTokens.closeToken, options.tags.openingBrace, options.tags.closingBrace, true);
return;
}
let option;
if (node.parent.type === 'SvelteAttribute') {
option = options.attributesAndProps;
}
else if (node.parent.type === 'SvelteStyleDirective') {
option = options.directiveExpressions;
}
else {
option = options.textExpressions;
}
verifyExpression(node, option);
},
SvelteShorthandAttribute(node) {
verifyExpression(node, options.attributesAndProps);
},
SvelteSpreadAttribute(node) {
verifyExpression(node, options.attributesAndProps);
},
SvelteDirective(node) {
verifyExpression(node, options.directiveExpressions);
},
SvelteSpecialDirective(node) {
verifyExpression(node, options.directiveExpressions);
},
SvelteDebugTag(node) {
const mustacheTokens = (0, ast_utils_1.getMustacheTokens)(node, sourceCode);
verifyBraces(mustacheTokens.openToken, mustacheTokens.closeToken, options.tags.openingBrace, options.tags.closingBrace, true);
},
SvelteRenderTag(node) {
const mustacheTokens = (0, ast_utils_1.getMustacheTokens)(node, sourceCode);
verifyBraces(mustacheTokens.openToken, mustacheTokens.closeToken, options.tags.openingBrace, options.tags.closingBrace, true);
},
SvelteIfBlock(node) {
const openBlockOpeningToken = sourceCode.getFirstToken(node);
const openBlockClosingToken = sourceCode.getTokenAfter(node.expression, {
includeComments: false,
filter: eslint_utils_1.isClosingBraceToken
});
verifyBraces(openBlockOpeningToken, openBlockClosingToken, options.tags.openingBrace, options.tags.closingBrace, true);
if (node.elseif) {
return;
}
const closeBlockClosingToken = sourceCode.getLastToken(node);
const closeBlockOpeningToken = sourceCode.getTokenBefore(closeBlockClosingToken, {
includeComments: false,
filter: eslint_utils_1.isOpeningBraceToken
});
verifyBraces(closeBlockOpeningToken, closeBlockClosingToken, options.tags.openingBrace, options.tags.closingBrace, false);
},
SvelteElseBlock(node) {
if (node.elseif) {
return;
}
const openToken = sourceCode.getFirstToken(node);
const closeToken = sourceCode.getTokenAfter(openToken, {
includeComments: false,
filter: eslint_utils_1.isClosingBraceToken
});
verifyBraces(openToken, closeToken, options.tags.openingBrace, options.tags.closingBrace, false);
},
SvelteEachBlock(node) {
const openBlockOpeningToken = sourceCode.getFirstToken(node);
const openBlockClosingToken = sourceCode.getTokenAfter(node.key || node.index || node.context, {
includeComments: false,
filter: eslint_utils_1.isClosingBraceToken
});
verifyBraces(openBlockOpeningToken, openBlockClosingToken, options.tags.openingBrace, options.tags.closingBrace, true);
const closeBlockClosingToken = sourceCode.getLastToken(node);
const closeBlockOpeningToken = sourceCode.getTokenBefore(closeBlockClosingToken, {
includeComments: false,
filter: eslint_utils_1.isOpeningBraceToken
});
verifyBraces(closeBlockOpeningToken, closeBlockClosingToken, options.tags.openingBrace, options.tags.closingBrace, false);
},
SvelteKeyBlock(node) {
const openBlockOpeningToken = sourceCode.getFirstToken(node);
const openBlockClosingToken = sourceCode.getTokenAfter(node.expression, {
includeComments: false,
filter: eslint_utils_1.isClosingBraceToken
});
verifyBraces(openBlockOpeningToken, openBlockClosingToken, options.tags.openingBrace, options.tags.closingBrace, true);
const closeBlockClosingToken = sourceCode.getLastToken(node);
const closeBlockOpeningToken = sourceCode.getTokenBefore(closeBlockClosingToken, {
includeComments: false,
filter: eslint_utils_1.isOpeningBraceToken
});
verifyBraces(closeBlockOpeningToken, closeBlockClosingToken, options.tags.openingBrace, options.tags.closingBrace, false);
},
SvelteAwaitBlock(node) {
const closeBlockClosingToken = sourceCode.getLastToken(node);
const closeBlockOpeningToken = sourceCode.getTokenBefore(closeBlockClosingToken, {
includeComments: false,
filter: eslint_utils_1.isOpeningBraceToken
});
verifyBraces(closeBlockOpeningToken, closeBlockClosingToken, options.tags.openingBrace, options.tags.closingBrace, false);
},
SvelteAwaitPendingBlock(node) {
const openBlockOpeningToken = sourceCode.getFirstToken(node);
const openBlockClosingToken = sourceCode.getTokenAfter(node.parent.expression, {
includeComments: false,
filter: eslint_utils_1.isClosingBraceToken
});
verifyBraces(openBlockOpeningToken, openBlockClosingToken, options.tags.openingBrace, options.tags.closingBrace, true);
},
SvelteAwaitThenBlock(node) {
const openBlockOpeningToken = sourceCode.getFirstToken(node);
const openBlockLast = node.value || (node.awaitThen ? node.parent.expression : null);
const openBlockClosingToken = openBlockLast
? sourceCode.getTokenAfter(openBlockLast, {
includeComments: false,
filter: eslint_utils_1.isClosingBraceToken
})
: null;
verifyBraces(openBlockOpeningToken, openBlockClosingToken, options.tags.openingBrace, options.tags.closingBrace, Boolean(openBlockClosingToken &&
openBlockLast &&
openBlockClosingToken === sourceCode.getTokenAfter(openBlockLast)));
},
SvelteAwaitCatchBlock(node) {
const openBlockOpeningToken = sourceCode.getFirstToken(node);
const openBlockLast = node.error || (node.awaitCatch ? node.parent.expression : null);
const openBlockClosingToken = openBlockLast
? sourceCode.getTokenAfter(openBlockLast, {
includeComments: false,
filter: eslint_utils_1.isClosingBraceToken
})
: null;
verifyBraces(openBlockOpeningToken, openBlockClosingToken, options.tags.openingBrace, options.tags.closingBrace, Boolean(openBlockClosingToken &&
openBlockLast &&
openBlockClosingToken === sourceCode.getTokenAfter(openBlockLast)));
},
SvelteSnippetBlock(node) {
const openBlockOpeningToken = sourceCode.getFirstToken(node);
const openBlockClosingToken = sourceCode.getTokenAfter(node.params[node.params.length - 1] || node.id, {
includeComments: false,
filter: eslint_utils_1.isClosingBraceToken
});
verifyBraces(openBlockOpeningToken, openBlockClosingToken, options.tags.openingBrace, options.tags.closingBrace, true);
const closeBlockClosingToken = sourceCode.getLastToken(node);
const closeBlockOpeningToken = sourceCode.getTokenBefore(closeBlockClosingToken, {
includeComments: false,
filter: eslint_utils_1.isOpeningBraceToken
});
verifyBraces(closeBlockOpeningToken, closeBlockClosingToken, options.tags.openingBrace, options.tags.closingBrace, false);
}
};
}
});

View File

@@ -0,0 +1,2 @@
declare const _default: import("../types").RuleModule;
export default _default;

View File

@@ -0,0 +1,28 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
exports.default = (0, utils_1.createRule)('no-at-debug-tags', {
meta: {
docs: {
description: 'disallow the use of `{@debug}`',
category: 'Best Practices',
recommended: true,
default: 'warn'
},
schema: [],
messages: {
unexpected: 'Unexpected `{@debug}`.'
},
type: 'problem'
},
create(context) {
return {
SvelteDebugTag(node) {
context.report({
node,
messageId: 'unexpected'
});
}
};
}
});

View File

@@ -0,0 +1,2 @@
declare const _default: import("../types").RuleModule;
export default _default;

View File

@@ -0,0 +1,27 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
exports.default = (0, utils_1.createRule)('no-at-html-tags', {
meta: {
docs: {
description: 'disallow use of `{@html}` to prevent XSS attack',
category: 'Security Vulnerability',
recommended: true
},
schema: [],
messages: {
unexpected: '`{@html}` can lead to XSS attack.'
},
type: 'suggestion' // "problem",
},
create(context) {
return {
'SvelteMustacheTag[kind=raw]'(node) {
context.report({
node,
messageId: 'unexpected'
});
}
};
}
});

View File

@@ -0,0 +1,2 @@
declare const _default: import("../types").RuleModule;
export default _default;

View File

@@ -0,0 +1,118 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
const ast_utils_1 = require("../utils/ast-utils");
const eslint_utils_1 = require("@eslint-community/eslint-utils");
const DOM_MANIPULATING_METHODS = new Set([
'appendChild', // https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild
'insertBefore', // https://developer.mozilla.org/en-US/docs/Web/API/Node/insertBefore
'normalize', // https://developer.mozilla.org/en-US/docs/Web/API/Node/normalize
'removeChild', // https://developer.mozilla.org/en-US/docs/Web/API/Node/removeChild
'replaceChild', // https://developer.mozilla.org/en-US/docs/Web/API/Node/replaceChild
'after', // https://developer.mozilla.org/en-US/docs/Web/API/Element/after
'append', // https://developer.mozilla.org/en-US/docs/Web/API/Element/append
'before', // https://developer.mozilla.org/en-US/docs/Web/API/Element/before
'insertAdjacentElement', // https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentElement
'insertAdjacentHTML', // https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML
'insertAdjacentText', // https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentText
'prepend', // https://developer.mozilla.org/en-US/docs/Web/API/Element/prepend
'remove', // https://developer.mozilla.org/en-US/docs/Web/API/Element/remove
'replaceChildren', // https://developer.mozilla.org/en-US/docs/Web/API/Element/replaceChildren
'replaceWith' // https://developer.mozilla.org/en-US/docs/Web/API/Element/replaceWith
]);
const DOM_MANIPULATING_PROPERTIES = new Set([
'textContent', // https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent
'innerHTML', // https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML
'outerHTML', // https://developer.mozilla.org/en-US/docs/Web/API/Element/outerHTML
'innerText', // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/innerText
'outerText' // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/outerText
]);
exports.default = (0, utils_1.createRule)('no-dom-manipulating', {
meta: {
docs: {
description: 'disallow DOM manipulating',
category: 'Possible Errors',
recommended: false
},
schema: [],
messages: {
disallowManipulateDOM: "Don't manipulate the DOM directly. The Svelte runtime can get confused if there is a difference between the actual DOM and the DOM expected by the Svelte runtime."
},
type: 'problem'
},
create(context) {
const domVariables = new Set();
/**
* Verify DOM variable identifier node
*/
function verifyIdentifier(node) {
const member = node.parent;
if (member?.type !== 'MemberExpression' || member.object !== node) {
return;
}
const name = (0, eslint_utils_1.getPropertyName)(member);
if (!name) {
return;
}
let target = member;
let parent = target.parent;
while (parent?.type === 'ChainExpression') {
target = parent;
parent = parent.parent;
}
if (!parent) {
return;
}
if (parent.type === 'CallExpression') {
if (parent.callee !== target || !DOM_MANIPULATING_METHODS.has(name)) {
return;
}
}
else if (parent.type === 'AssignmentExpression') {
if (parent.left !== target || !DOM_MANIPULATING_PROPERTIES.has(name)) {
return;
}
}
else {
return;
}
context.report({
node: member,
messageId: 'disallowManipulateDOM'
});
}
return {
"SvelteDirective[kind='Binding']"(node) {
if (node.key.name.name !== 'this' ||
!node.expression ||
node.expression.type !== 'Identifier') {
// not bind:this={id}
return;
}
const element = node.parent.parent;
if (element.type !== 'SvelteElement' || !isHTMLElement(element)) {
// not HTML element
return;
}
const variable = (0, ast_utils_1.findVariable)(context, node.expression);
if (!variable || (variable.scope.type !== 'module' && variable.scope.type !== 'global')) {
return;
}
domVariables.add(variable);
},
'Program:exit'() {
for (const variable of domVariables) {
for (const reference of variable.references) {
verifyIdentifier(reference.identifier);
}
}
}
};
/**
* Checks whether the given node is a HTML element or not.
*/
function isHTMLElement(node) {
return (node.kind === 'html' || (node.kind === 'special' && (0, ast_utils_1.getNodeName)(node) === 'svelte:element'));
}
}
});

View File

@@ -0,0 +1,2 @@
declare const _default: import("../types").RuleModule;
export default _default;

View File

@@ -0,0 +1,133 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
const ast_utils_1 = require("../utils/ast-utils");
const compat_1 = require("../utils/compat");
/**
* Splits the given node by the given logical operator.
* @param operator Logical operator `||` or `&&`.
* @param node The node to split.
* @returns Array of conditions that makes the node when joined by the operator.
*/
function splitByLogicalOperator(operator, node) {
if (node.type === 'LogicalExpression' && node.operator === operator) {
return [
...splitByLogicalOperator(operator, node.left),
...splitByLogicalOperator(operator, node.right)
];
}
return [node];
}
/**
* Split with ||.
*/
function splitByOr(node) {
return splitByLogicalOperator('||', node);
}
/**
* Split with &&.
*/
function splitByAnd(node) {
return splitByLogicalOperator('&&', node);
}
/**
* Build OrOperands
*/
function buildOrOperands(node) {
const orOperands = splitByOr(node);
return {
node,
operands: orOperands.map((orOperand) => {
const andOperands = splitByAnd(orOperand);
return {
node: orOperand,
operands: andOperands
};
})
};
}
exports.default = (0, utils_1.createRule)('no-dupe-else-if-blocks', {
meta: {
docs: {
description: 'disallow duplicate conditions in `{#if}` / `{:else if}` chains',
category: 'Possible Errors',
recommended: true
},
schema: [],
messages: {
unexpected: 'This branch can never execute. Its condition is a duplicate or covered by previous conditions in the `{#if}` / `{:else if}` chain.'
},
type: 'problem' // "problem",
},
create(context) {
const sourceCode = (0, compat_1.getSourceCode)(context);
/**
* Determines whether the two given nodes are considered to be equal. In particular, given that the nodes
* represent expressions in a boolean context, `||` and `&&` can be considered as commutative operators.
* @param a First node.
* @param b Second node.
* @returns `true` if the nodes are considered to be equal.
*/
function equal(a, b) {
if (a.type !== b.type) {
return false;
}
if (a.type === 'LogicalExpression' &&
b.type === 'LogicalExpression' &&
(a.operator === '||' || a.operator === '&&') &&
a.operator === b.operator) {
return ((equal(a.left, b.left) && equal(a.right, b.right)) ||
(equal(a.left, b.right) && equal(a.right, b.left)));
}
return (0, ast_utils_1.equalTokens)(a, b, sourceCode);
}
/**
* Determines whether the first given AndOperands is a subset of the second given AndOperands.
*
* e.g. A: (a && b), B: (a && b && c): B is a subset of A.
*
* @param operandsA The AndOperands to compare from.
* @param operandsB The AndOperands to compare against.
* @returns `true` if the `andOperandsA` is a subset of the `andOperandsB`.
*/
function isSubset(operandsA, operandsB) {
return operandsA.operands.every((operandA) => operandsB.operands.some((operandB) => equal(operandA, operandB)));
}
/** Iterate SvelteIfBlock nodes */
function* iterateIfElseIf(node) {
let target = node;
while (target.parent.type === 'SvelteElseBlock' &&
target.parent.children.includes(target) &&
target.parent.parent.type === 'SvelteIfBlock') {
yield target.parent.parent;
target = target.parent.parent;
}
}
return {
SvelteIfBlock(node) {
const test = node.expression;
const conditionsToCheck = test.type === 'LogicalExpression' && test.operator === '&&'
? [...splitByAnd(test), test]
: [test];
const listToCheck = conditionsToCheck.map(buildOrOperands);
for (const currentIdBlock of iterateIfElseIf(node)) {
if (currentIdBlock.expression) {
const currentOrOperands = buildOrOperands(currentIdBlock.expression);
for (const condition of listToCheck) {
const operands = (condition.operands = condition.operands.filter((orOperand) => {
return !currentOrOperands.operands.some((currentOrOperand) => isSubset(currentOrOperand, orOperand));
}));
if (!operands.length) {
context.report({
node: condition.node,
messageId: 'unexpected'
});
return;
}
}
}
}
}
};
}
});

View File

@@ -0,0 +1,2 @@
declare const _default: import("../types").RuleModule;
export default _default;

View File

@@ -0,0 +1,73 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
const ast_utils_1 = require("../utils/ast-utils");
const compat_1 = require("../utils/compat");
exports.default = (0, utils_1.createRule)('no-dupe-on-directives', {
meta: {
docs: {
description: 'disallow duplicate `on:` directives',
category: 'Possible Errors',
recommended: false
},
schema: [],
messages: {
duplication: 'This `on:{{type}}` directive is the same and duplicate directives in L{{lineNo}}.'
},
type: 'problem'
},
create(context) {
const sourceCode = (0, compat_1.getSourceCode)(context);
const directiveDataMap = new Map();
return {
SvelteDirective(node) {
if (node.kind !== 'EventHandler')
return;
const directiveDataList = directiveDataMap.get(node.key.name.name);
if (!directiveDataList) {
directiveDataMap.set(node.key.name.name, [
{
expression: node.expression,
nodes: [node]
}
]);
return;
}
const directiveData = directiveDataList.find((data) => {
if (!data.expression || !node.expression) {
return data.expression === node.expression;
}
return (0, ast_utils_1.equalTokens)(data.expression, node.expression, sourceCode);
});
if (!directiveData) {
directiveDataList.push({
expression: node.expression,
nodes: [node]
});
return;
}
directiveData.nodes.push(node);
},
'SvelteStartTag:exit'() {
for (const [type, directiveDataList] of directiveDataMap) {
for (const { nodes } of directiveDataList) {
if (nodes.length < 2) {
continue;
}
for (const node of nodes) {
context.report({
node,
messageId: 'duplication',
data: {
type,
lineNo: String((nodes[0] !== node ? nodes[0] : nodes[1]).loc.start.line)
}
});
}
}
}
directiveDataMap.clear();
}
};
}
});

View File

@@ -0,0 +1,2 @@
declare const _default: import("../types").RuleModule;
export default _default;

View File

@@ -0,0 +1,91 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
const css_utils_1 = require("../utils/css-utils");
exports.default = (0, utils_1.createRule)('no-dupe-style-properties', {
meta: {
docs: {
description: 'disallow duplicate style properties',
category: 'Possible Errors',
recommended: true
},
schema: [],
messages: {
unexpected: "Duplicate property '{{name}}'."
},
type: 'problem'
},
create(context) {
return {
SvelteStartTag(node) {
const reported = new Set();
const beforeDeclarations = new Map();
for (const { decls } of iterateStyleDeclSetFromAttrs(node.attributes)) {
for (const decl of decls) {
const already = beforeDeclarations.get(decl.prop);
if (already) {
for (const report of [already, decl].filter((n) => !reported.has(n))) {
context.report({
node,
loc: report.loc,
messageId: 'unexpected',
data: { name: report.prop }
});
reported.add(report);
}
}
}
for (const decl of decls) {
beforeDeclarations.set(decl.prop, decl);
}
}
}
};
/** Iterate the style decl set from attrs */
function* iterateStyleDeclSetFromAttrs(attrs) {
for (const attr of attrs) {
if (attr.type === 'SvelteStyleDirective') {
yield {
decls: [{ prop: attr.key.name.name, loc: attr.key.name.loc }]
};
}
else if (attr.type === 'SvelteAttribute') {
if (attr.key.name !== 'style') {
continue;
}
const root = (0, css_utils_1.parseStyleAttributeValue)(attr, context);
if (!root) {
continue;
}
yield* iterateStyleDeclSetFromStyleRoot(root);
}
}
}
/** Iterate the style decl set from style root */
function* iterateStyleDeclSetFromStyleRoot(root) {
for (const child of root.nodes) {
if (child.type === 'decl') {
yield {
decls: [
{
prop: child.prop.name,
get loc() {
return child.prop.loc;
}
}
]
};
}
else if (child.type === 'inline') {
const decls = [];
for (const root of child.getAllInlineStyles().values()) {
for (const set of iterateStyleDeclSetFromStyleRoot(root)) {
decls.push(...set.decls);
}
}
yield { decls };
}
}
}
}
});

View File

@@ -0,0 +1,2 @@
declare const _default: import("../types").RuleModule;
export default _default;

View File

@@ -0,0 +1,74 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
const ast_utils_1 = require("../utils/ast-utils");
const compat_1 = require("../utils/compat");
exports.default = (0, utils_1.createRule)('no-dupe-use-directives', {
meta: {
docs: {
description: 'disallow duplicate `use:` directives',
category: 'Possible Errors',
recommended: false
},
schema: [],
messages: {
duplication: 'This `{{keyText}}` directive is the same and duplicate directives in L{{lineNo}}.'
},
type: 'problem'
},
create(context) {
const sourceCode = (0, compat_1.getSourceCode)(context);
const directiveDataMap = new Map();
return {
SvelteDirective(node) {
if (node.kind !== 'Action')
return;
const keyText = (0, ast_utils_1.getAttributeKeyText)(node, context);
const directiveDataList = directiveDataMap.get(keyText);
if (!directiveDataList) {
directiveDataMap.set(keyText, [
{
expression: node.expression,
nodes: [node]
}
]);
return;
}
const directiveData = directiveDataList.find((data) => {
if (!data.expression || !node.expression) {
return data.expression === node.expression;
}
return (0, ast_utils_1.equalTokens)(data.expression, node.expression, sourceCode);
});
if (!directiveData) {
directiveDataList.push({
expression: node.expression,
nodes: [node]
});
return;
}
directiveData.nodes.push(node);
},
'SvelteStartTag:exit'() {
for (const [keyText, directiveDataList] of directiveDataMap) {
for (const { nodes } of directiveDataList) {
if (nodes.length < 2) {
continue;
}
for (const node of nodes) {
context.report({
node,
messageId: 'duplication',
data: {
keyText,
lineNo: String((nodes[0] !== node ? nodes[0] : nodes[1]).loc.start.line)
}
});
}
}
}
directiveDataMap.clear();
}
};
}
});

View File

@@ -0,0 +1,2 @@
declare const _default: import("../types").RuleModule;
export default _default;

View File

@@ -0,0 +1,81 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
const ast_utils_1 = require("../utils/ast-utils");
const compat_1 = require("../utils/compat");
exports.default = (0, utils_1.createRule)('no-dynamic-slot-name', {
meta: {
docs: {
description: 'disallow dynamic slot name',
category: 'Possible Errors',
recommended: true
},
fixable: 'code',
schema: [],
messages: {
unexpected: '`<slot>` name cannot be dynamic.',
requireValue: '`<slot>` name requires a value.'
},
type: 'problem'
},
create(context) {
const sourceCode = (0, compat_1.getSourceCode)(context);
return {
"SvelteElement[name.name='slot'] > SvelteStartTag.startTag > SvelteAttribute[key.name='name']"(node) {
if (node.value.length === 0) {
context.report({
node,
messageId: 'requireValue'
});
return;
}
for (const vNode of node.value) {
if (vNode.type === 'SvelteMustacheTag') {
context.report({
node: vNode,
messageId: 'unexpected',
fix(fixer) {
const text = getStaticText(vNode.expression);
if (text == null) {
return null;
}
if (node.value.length === 1) {
const range = (0, ast_utils_1.getAttributeValueQuoteAndRange)(node, sourceCode).range;
return fixer.replaceTextRange(range, `"${text}"`);
}
const range = vNode.range;
return fixer.replaceTextRange(range, text);
}
});
}
}
}
};
/**
* Get static text from given expression
*/
function getStaticText(node) {
const expr = findRootExpression(node);
return (0, ast_utils_1.getStringIfConstant)(expr);
}
/** Find data expression */
function findRootExpression(node, already = new Set()) {
if (node.type !== 'Identifier' || already.has(node)) {
return node;
}
already.add(node);
const variable = (0, ast_utils_1.findVariable)(context, node);
if (!variable || variable.defs.length !== 1) {
return node;
}
const def = variable.defs[0];
if (def.type === 'Variable') {
if (def.parent.kind === 'const' && def.node.init) {
const init = def.node.init;
return findRootExpression(init, already);
}
}
return node;
}
}
});

View File

@@ -0,0 +1,2 @@
declare const _default: import("../types").RuleModule;
export default _default;

View File

@@ -0,0 +1,46 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
const svelte_kit_1 = require("../utils/svelte-kit");
exports.default = (0, utils_1.createRule)('no-export-load-in-svelte-module-in-kit-pages', {
meta: {
docs: {
description: 'disallow exporting load functions in `*.svelte` module in SvelteKit page components.',
category: 'Possible Errors',
// TODO Switch to recommended in the major version.
recommended: false
},
schema: [],
messages: {
unexpected: 'disallow exporting load functions in `*.svelte` module in SvelteKit page components.'
},
type: 'problem'
},
create(context) {
if (!(0, svelte_kit_1.isKitPageComponent)(context)) {
return {};
}
let isModule = false;
return {
// <script context="module">
[`Program > SvelteScriptElement > SvelteStartTag > SvelteAttribute[key.name="context"] > SvelteLiteral[value="module"]`]: () => {
isModule = true;
},
// </script>
'Program > SvelteScriptElement:exit': () => {
isModule = false;
},
// export function load() {}
// export const load = () => {}
[`:matches(ExportNamedDeclaration > FunctionDeclaration, ExportNamedDeclaration > VariableDeclaration > VariableDeclarator) > Identifier.id[name="load"]`]: (node) => {
if (!isModule)
return {};
return context.report({
node,
loc: node.loc,
messageId: 'unexpected'
});
}
};
}
});

View File

@@ -0,0 +1,2 @@
declare const _default: import("../types").RuleModule;
export default _default;

View File

@@ -0,0 +1,51 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
const compat_1 = require("../utils/compat");
exports.default = (0, utils_1.createRule)('no-extra-reactive-curlies', {
meta: {
docs: {
description: 'disallow wrapping single reactive statements in curly braces',
category: 'Stylistic Issues',
recommended: false,
conflictWithPrettier: false
},
hasSuggestions: true,
schema: [],
messages: {
extraCurlies: `Do not wrap reactive statements in curly braces unless necessary.`,
removeExtraCurlies: `Remove the unnecessary curly braces.`
},
type: 'suggestion'
},
create(context) {
return {
// $: { foo = "bar"; }
[`SvelteReactiveStatement > BlockStatement[body.length=1]`]: (node) => {
const source = (0, compat_1.getSourceCode)(context);
return context.report({
node,
loc: node.loc,
messageId: 'extraCurlies',
suggest: [
{
messageId: 'removeExtraCurlies',
fix(fixer) {
const tokens = source.getTokens(node, { includeComments: true });
// Remove everything up to the second token, and the entire last token since
// those are known to be "{" and "}"
return [
fixer.removeRange([tokens[0].range[0], tokens[1].range[0]]),
fixer.removeRange([
tokens[tokens.length - 2].range[1],
tokens[tokens.length - 1].range[1]
])
];
}
}
]
});
}
};
}
});

View File

@@ -0,0 +1,2 @@
declare const _default: import("../types").RuleModule;
export default _default;

View File

@@ -0,0 +1,108 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
const eslint_utils_1 = require("@eslint-community/eslint-utils");
const compat_1 = require("../utils/compat");
const ast_utils_1 = require("../utils/ast-utils");
exports.default = (0, utils_1.createRule)('no-goto-without-base', {
meta: {
docs: {
description: 'disallow using goto() without the base path',
category: 'SvelteKit',
recommended: false
},
schema: [],
messages: {
isNotPrefixedWithBasePath: "Found a goto() call with a url that isn't prefixed with the base path."
},
type: 'suggestion'
},
create(context) {
return {
Program() {
const referenceTracker = new eslint_utils_1.ReferenceTracker((0, compat_1.getSourceCode)(context).scopeManager.globalScope);
const basePathNames = extractBasePathReferences(referenceTracker, context);
for (const gotoCall of extractGotoReferences(referenceTracker)) {
if (gotoCall.arguments.length < 1) {
continue;
}
const path = gotoCall.arguments[0];
switch (path.type) {
case 'BinaryExpression':
checkBinaryExpression(context, path, basePathNames);
break;
case 'Literal':
checkLiteral(context, path);
break;
case 'TemplateLiteral':
checkTemplateLiteral(context, path, basePathNames);
break;
default:
context.report({ loc: path.loc, messageId: 'isNotPrefixedWithBasePath' });
}
}
}
};
}
});
function checkBinaryExpression(context, path, basePathNames) {
if (path.left.type !== 'Identifier' || !basePathNames.has(path.left)) {
context.report({ loc: path.loc, messageId: 'isNotPrefixedWithBasePath' });
}
}
function checkTemplateLiteral(context, path, basePathNames) {
const startingIdentifier = extractStartingIdentifier(path);
if (startingIdentifier === undefined || !basePathNames.has(startingIdentifier)) {
context.report({ loc: path.loc, messageId: 'isNotPrefixedWithBasePath' });
}
}
function checkLiteral(context, path) {
const absolutePathRegex = /^(?:[+a-z]+:)?\/\//i;
if (!absolutePathRegex.test(path.value?.toString() ?? '')) {
context.report({ loc: path.loc, messageId: 'isNotPrefixedWithBasePath' });
}
}
function extractStartingIdentifier(templateLiteral) {
const literalParts = [...templateLiteral.expressions, ...templateLiteral.quasis].sort((a, b) => a.range[0] < b.range[0] ? -1 : 1);
for (const part of literalParts) {
if (part.type === 'TemplateElement' && part.value.raw === '') {
// Skip empty quasi in the begining
continue;
}
if (part.type === 'Identifier') {
return part;
}
return undefined;
}
return undefined;
}
function extractGotoReferences(referenceTracker) {
return Array.from(referenceTracker.iterateEsmReferences({
'$app/navigation': {
[eslint_utils_1.ReferenceTracker.ESM]: true,
goto: {
[eslint_utils_1.ReferenceTracker.CALL]: true
}
}
}), ({ node }) => node);
}
function extractBasePathReferences(referenceTracker, context) {
const set = new Set();
for (const { node } of referenceTracker.iterateEsmReferences({
'$app/paths': {
[eslint_utils_1.ReferenceTracker.ESM]: true,
base: {
[eslint_utils_1.ReferenceTracker.READ]: true
}
}
})) {
const variable = (0, ast_utils_1.findVariable)(context, node.local);
if (!variable)
continue;
for (const reference of variable.references) {
if (reference.identifier.type === 'Identifier')
set.add(reference.identifier);
}
}
return set;
}

View File

@@ -0,0 +1,2 @@
declare const _default: import("../types").RuleModule;
export default _default;

View File

@@ -0,0 +1,29 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
exports.default = (0, utils_1.createRule)('no-ignored-unsubscribe', {
meta: {
docs: {
description: 'disallow ignoring the unsubscribe method returned by the `subscribe()` on Svelte stores.',
category: 'Best Practices',
recommended: false
},
fixable: undefined,
hasSuggestions: false,
messages: {
forbidden: 'Ignoring returned value of the subscribe method is forbidden.'
},
schema: [],
type: 'problem'
},
create: (context) => {
return {
"ExpressionStatement > CallExpression > MemberExpression.callee[property.name='subscribe']": (node) => {
context.report({
messageId: 'forbidden',
node: node.property
});
}
};
}
});

View File

@@ -0,0 +1,2 @@
declare const _default: import("../types").RuleModule;
export default _default;

View File

@@ -0,0 +1,180 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
const ast_utils_1 = require("../utils/ast-utils");
const compat_1 = require("../utils/compat");
exports.default = (0, utils_1.createRule)('no-immutable-reactive-statements', {
meta: {
docs: {
description: "disallow reactive statements that don't reference reactive values.",
category: 'Best Practices',
// TODO Switch to recommended in the major version.
recommended: false
},
schema: [],
messages: {
immutable: 'This statement is not reactive because all variables referenced in the reactive statement are immutable.'
},
type: 'suggestion'
},
create(context) {
const scopeManager = (0, compat_1.getSourceCode)(context).scopeManager;
const globalScope = scopeManager.globalScope;
const toplevelScope = globalScope?.childScopes.find((scope) => scope.type === 'module') || globalScope;
if (!globalScope || !toplevelScope) {
return {};
}
const cacheMutableVariable = new WeakMap();
/**
* Checks whether the given reference is a mutable variable or not.
*/
function isMutableVariableReference(reference) {
if (reference.identifier.name.startsWith('$')) {
// It is reactive store reference.
return true;
}
if (!reference.resolved) {
// Unknown variable
return true;
}
return isMutableVariable(reference.resolved);
}
/**
* Checks whether the given variable is a mutable variable or not.
*/
function isMutableVariable(variable) {
const cache = cacheMutableVariable.get(variable);
if (cache != null) {
return cache;
}
if (variable.defs.length === 0) {
// Global variables are assumed to be immutable.
return true;
}
const isMutableDefine = variable.defs.some((def) => {
if (def.type === 'ImportBinding') {
return false;
}
if (def.node.type === 'AssignmentExpression') {
// Reactive values
return true;
}
if (def.type === 'Variable') {
const parent = def.parent;
if (parent.kind === 'const') {
if (def.node.init &&
(def.node.init.type === 'FunctionExpression' ||
def.node.init.type === 'ArrowFunctionExpression' ||
def.node.init.type === 'Literal')) {
return false;
}
}
else {
const pp = parent.parent;
if (pp && pp.type === 'ExportNamedDeclaration' && pp.declaration === parent) {
// Props
return true;
}
}
return hasWrite(variable);
}
return false;
});
cacheMutableVariable.set(variable, isMutableDefine);
return isMutableDefine;
}
/** Checks whether the given variable has a write or reactive store reference or not. */
function hasWrite(variable) {
const defIds = variable.defs.map((def) => def.name);
for (const reference of variable.references) {
if (reference.isWrite() &&
!defIds.some((defId) => defId.range[0] <= reference.identifier.range[0] &&
reference.identifier.range[1] <= defId.range[1])) {
return true;
}
if (hasWriteMember(reference.identifier)) {
return true;
}
}
return false;
}
/** Checks whether the given expression has writing to a member or not. */
function hasWriteMember(expr) {
if (expr.type === 'JSXIdentifier')
return false;
const parent = expr.parent;
if (parent.type === 'AssignmentExpression') {
return parent.left === expr;
}
if (parent.type === 'UpdateExpression') {
return parent.argument === expr;
}
if (parent.type === 'UnaryExpression') {
return parent.operator === 'delete' && parent.argument === expr;
}
if (parent.type === 'MemberExpression') {
return parent.object === expr && hasWriteMember(parent);
}
if (parent.type === 'SvelteDirective') {
return parent.kind === 'Binding' && parent.expression === expr;
}
if (parent.type === 'SvelteEachBlock') {
return parent.expression === expr && hasWriteReference(parent.context);
}
return false;
}
/** Checks whether the given pattern has writing or not. */
function hasWriteReference(pattern) {
for (const id of (0, ast_utils_1.iterateIdentifiers)(pattern)) {
const variable = (0, ast_utils_1.findVariable)(context, id);
if (variable && hasWrite(variable))
return true;
}
return false;
}
/**
* Iterates through references to top-level variables in the given range.
*/
function* iterateRangeReferences(scope, range) {
for (const variable of scope.variables) {
for (const reference of variable.references) {
if (range[0] <= reference.identifier.range[0] &&
reference.identifier.range[1] <= range[1]) {
yield reference;
}
}
}
}
return {
SvelteReactiveStatement(node) {
for (const reference of iterateRangeReferences(toplevelScope, node.range)) {
if (reference.isWriteOnly()) {
continue;
}
if (isMutableVariableReference(reference)) {
return;
}
}
for (const through of toplevelScope.through.filter((reference) => node.range[0] <= reference.identifier.range[0] &&
reference.identifier.range[1] <= node.range[1])) {
if (through.identifier.name.startsWith('$$')) {
// Builtin `$$` vars
return;
}
if (through.resolved == null) {
// Do not report if there are missing references.
return;
}
}
context.report({
node: node.body.type === 'ExpressionStatement' &&
node.body.expression.type === 'AssignmentExpression' &&
node.body.expression.operator === '='
? node.body.expression.right
: node.body,
messageId: 'immutable'
});
}
};
}
});

View File

@@ -0,0 +1,2 @@
declare const _default: import("../types").RuleModule;
export default _default;

View File

@@ -0,0 +1,52 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
exports.default = (0, utils_1.createRule)('no-inline-styles', {
meta: {
docs: {
description: 'disallow attributes and directives that produce inline styles',
category: 'Best Practices',
recommended: false
},
schema: [
{
type: 'object',
properties: {
allowTransitions: {
type: 'boolean'
}
},
additionalProperties: false
}
],
messages: {
hasStyleAttribute: 'Found disallowed style attribute.',
hasStyleDirective: 'Found disallowed style directive.',
hasTransition: 'Found disallowed transition.'
},
type: 'suggestion'
},
create(context) {
const allowTransitions = context.options[0]?.allowTransitions ?? false;
return {
SvelteElement(node) {
if (node.kind !== 'html') {
return;
}
for (const attribute of node.startTag.attributes) {
if (attribute.type === 'SvelteStyleDirective') {
context.report({ loc: attribute.loc, messageId: 'hasStyleDirective' });
}
if (attribute.type === 'SvelteAttribute' && attribute.key.name === 'style') {
context.report({ loc: attribute.loc, messageId: 'hasStyleAttribute' });
}
if (!allowTransitions &&
attribute.type === 'SvelteDirective' &&
attribute.kind === 'Transition') {
context.report({ loc: attribute.loc, messageId: 'hasTransition' });
}
}
}
};
}
});

View File

@@ -0,0 +1,2 @@
declare const _default: import("../types").RuleModule;
export default _default;

View File

@@ -0,0 +1,36 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
const eslint_core_1 = require("../utils/eslint-core");
const coreRule = (0, eslint_core_1.getCoreRule)('no-inner-declarations');
exports.default = (0, utils_1.createRule)('no-inner-declarations', {
meta: {
...coreRule.meta,
docs: {
description: 'disallow variable or `function` declarations in nested blocks',
category: 'Extension Rules',
recommended: true,
extensionRule: 'no-inner-declarations'
},
fixable: coreRule.meta.fixable,
schema: coreRule.meta.schema,
messages: coreRule.meta.messages,
type: coreRule.meta.type
},
create(context) {
return (0, eslint_core_1.defineWrapperListener)(coreRule, context, {
createListenerProxy(coreListener) {
return (0, eslint_core_1.buildProxyListener)(coreListener, (node) => {
return (0, eslint_core_1.getProxyNode)(node, {
get parent() {
if (node.parent?.type === 'SvelteScriptElement') {
return node.parent.parent;
}
return node.parent;
}
});
});
}
});
}
});

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