Skip to content

chore: runtime linting #12314

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,38 @@
import svelte_config from '@sveltejs/eslint-config';
import lube from 'eslint-plugin-lube';

const no_compiler_imports = {
meta: {
type: /** @type {const} */ ('problem'),
docs: {
description:
'Enforce that there are no imports to the compiler in runtime code. ' +
'This prevent accidental inclusion of the compiler runtime and ' +
"ensures that TypeScript does not pick up more ambient types (for example from Node) that shouldn't be available in the browser."
}
},
create(context) {
return {
Program: () => {
// Do a simple string search because ESLint doesn't provide a way to check JSDoc comments.
// The string search could in theory yield false positives, but in practice it's unlikely.
const text = context.sourceCode.getText();
const idx = Math.max(text.indexOf('../compiler/'), text.indexOf('#compiler'));
if (idx !== -1) {
context.report({
loc: {
start: context.sourceCode.getLocFromIndex(idx),
end: context.sourceCode.getLocFromIndex(idx + 12)
},
message:
'References to compiler code are forbidden in runtime code (both for type and value imports)'
});
}
}
};
}
};

/** @type {import('eslint').Linter.FlatConfig[]} */
export default [
...svelte_config,
Expand All @@ -11,7 +43,8 @@ export default [
}
},
plugins: {
lube
lube,
custom: { rules: { no_compiler_imports } }
},
rules: {
'@typescript-eslint/await-thenable': 'error',
Expand All @@ -37,6 +70,13 @@ export default [
'no-console': 'off'
}
},
{
files: ['packages/svelte/src/**/*'],
ignores: ['packages/svelte/src/compiler/**/*'],
rules: {
'custom/no_compiler_imports': 'error'
}
},
{
ignores: [
'**/*.d.ts',
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@
"scripts": {
"build": "node scripts/process-messages && rollup -c && pnpm generate:types && node scripts/check-treeshakeability.js",
"dev": "node scripts/process-messages && rollup -cw",
"check": "tsc && cd ./tests/types && tsc",
"check": "tsc --project tsconfig.runtime.json && tsc && cd ./tests/types && tsc",
"check:watch": "tsc --watch",
"generate:version": "node ./scripts/generate-version.js",
"generate:types": "node ./scripts/generate-types.js && tsc -p tsconfig.generated.json",
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/src/compiler/phases/1-parse/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { isIdentifierStart, isIdentifierChar } from 'acorn';
import fragment from './state/fragment.js';
import { regex_whitespace } from '../patterns.js';
import { reserved } from './utils/names.js';
import { reserved } from '../../../constants.js';
import full_char_code_at from './utils/full_char_code_at.js';
import * as e from '../../errors.js';
import { create_fragment } from './utils/create.js';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/** @import { Parser } from '../index.js' */
/** @import * as Compiler from '#compiler' */
import { is_void } from '../utils/names.js';
import { is_void } from '../../../../constants.js';
import read_expression from '../read/expression.js';
import { read_script } from '../read/script.js';
import read_style from '../read/style.js';
Expand Down
74 changes: 0 additions & 74 deletions packages/svelte/src/compiler/phases/1-parse/utils/names.js

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type {
import type { Namespace, SvelteNode, ValidatedCompileOptions } from '#compiler';
import type { TransformState } from '../types.js';
import type { ComponentAnalysis } from '../../types.js';
import type { Location } from 'locate-character';
import type { SourceLocation } from '#shared';

export interface ClientTransformState extends TransformState {
readonly private_state: Map<string, StateField>;
Expand All @@ -24,10 +24,6 @@ export interface ClientTransformState extends TransformState {
readonly legacy_reactive_statements: Map<LabeledStatement, Statement>;
}

export type SourceLocation =
| [line: number, column: number]
| [line: number, column: number, SourceLocation[]];

export interface ComponentClientTransformState extends ClientTransformState {
readonly analysis: ComponentAnalysis;
readonly options: ValidatedCompileOptions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1051,7 +1051,7 @@ function serialize_bind_this(bind_this, context, node) {
}

/**
* @param {import('../types.js').SourceLocation[]} locations
* @param {import('#shared').SourceLocation[]} locations
*/
function serialize_locations(locations) {
return b.array(
Expand Down Expand Up @@ -1905,7 +1905,7 @@ export const template_visitors = {
state.init.push(b.stmt(b.call('$.transition', ...args)));
},
RegularElement(node, context) {
/** @type {import('../types.js').SourceLocation} */
/** @type {import('#shared').SourceLocation} */
let location = [-1, -1];

if (context.state.options.dev) {
Expand Down Expand Up @@ -2133,7 +2133,7 @@ export const template_visitors = {

context.state.template.push('>');

/** @type {import('../types.js').SourceLocation[]} */
/** @type {import('#shared').SourceLocation[]} */
const child_locations = [];

/** @type {import('../types').ComponentClientTransformState} */
Expand Down
75 changes: 75 additions & 0 deletions packages/svelte/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -293,3 +293,78 @@ export function is_capture_event(name, mode = 'exclude-on') {
? name !== 'gotpointercapture' && name !== 'lostpointercapture'
: name !== 'ongotpointercapture' && name !== 'onlostpointercapture';
}

export const reserved = [
'arguments',
'await',
'break',
'case',
'catch',
'class',
'const',
'continue',
'debugger',
'default',
'delete',
'do',
'else',
'enum',
'eval',
'export',
'extends',
'false',
'finally',
'for',
'function',
'if',
'implements',
'import',
'in',
'instanceof',
'interface',
'let',
'new',
'null',
'package',
'private',
'protected',
'public',
'return',
'static',
'super',
'switch',
'this',
'throw',
'true',
'try',
'typeof',
'var',
'void',
'while',
'with',
'yield'
];

const void_element_names = [
'area',
'base',
'br',
'col',
'command',
'embed',
'hr',
'img',
'input',
'keygen',
'link',
'meta',
'param',
'source',
'track',
'wbr'
];

/** @param {string} name */
export function is_void(name) {
return void_element_names.includes(name) || name.toLowerCase() === '!doctype';
}
2 changes: 1 addition & 1 deletion packages/svelte/src/internal/client/dev/elements.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/** @import { SourceLocation } from '../../../compiler/phases/3-transform/client/types.js' */
/** @import { SourceLocation } from '#shared' */
import { HYDRATION_END, HYDRATION_START } from '../../../constants.js';
import { hydrating } from '../dom/hydration.js';

Expand Down
3 changes: 2 additions & 1 deletion packages/svelte/src/internal/client/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,10 +224,11 @@ function handle_error(error, effect, component_context) {
let current_context = component_context;

while (current_context !== null) {
/** @type {string} */
var filename = current_context.function?.filename;

if (filename) {
const file = filename.split('/').at(-1);
const file = filename.split('/').pop();
component_stack.push(file);
}

Expand Down
4 changes: 4 additions & 0 deletions packages/svelte/src/internal/shared/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@ export type Store<V> = {
subscribe: (run: (value: V) => void) => () => void;
set(value: V): void;
};

export type SourceLocation =
| [line: number, column: number]
| [line: number, column: number, SourceLocation[]];
2 changes: 1 addition & 1 deletion packages/svelte/src/internal/shared/validate.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { is_void } from '../../compiler/phases/1-parse/utils/names.js';
import { is_void } from '../../constants.js';
import * as w from './warnings.js';
import * as e from './errors.js';

Expand Down
15 changes: 15 additions & 0 deletions packages/svelte/tsconfig.runtime.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
// Ensure we don't use any methods that are not available in all browser for a long period of time
// so that people don't need to polyfill them. Example array.at(...) was introduced to Safari only in 2022
"target": "es2021",
"lib": ["es2021", "DOM", "DOM.Iterable"],
"types": [] // prevent automatic inclusion of @types/node
},
"include": ["./src/"],
// Compiler is allowed to use more recent methods; people using it in the browser are expected to know
// how to polyfill missing methods. Also make sure to not include test files as these include Vitest
// which then loads node globals.
"exclude": ["./src/compiler/**/*", "./src/**/*.test.ts"]
}
Loading