start adding analyze function

tidy-up-analysis
Rich Harris 1 year ago
parent 825828d8d1
commit 4686a35dc9

@ -6,8 +6,93 @@ import { getLocator } from 'locate-character';
import { walk } from 'zimmerframe'; import { walk } from 'zimmerframe';
import { validate_component_options, validate_module_options } from './validate-options.js'; import { validate_component_options, validate_module_options } from './validate-options.js';
import { convert } from './legacy.js'; import { convert } from './legacy.js';
import { transform_warnings } from './utils/warnings.js';
export { default as preprocess } from './preprocess/index.js'; export { default as preprocess } from './preprocess/index.js';
/**
* @param {string} source
* @param {string | undefined} filename
* @param {Function} fn
*/
function handle_error(source, filename, fn) {
try {
return fn();
} catch (e) {
if (/** @type {any} */ (e).name === 'CompileError') {
const error = /** @type {import('#compiler').CompileError} */ (e);
error.filename = filename;
if (error.position) {
// TODO this is reused with warnings — DRY out
const locator = getLocator(source, { offsetLine: 1 });
const start = locator(error.position[0]);
const end = locator(error.position[1]);
error.start = start;
error.end = end;
}
}
throw e;
}
}
/**
* The parse function parses a component, returning only its abstract syntax tree.
*
* The `modern` option (`false` by default in Svelte 5) makes the parser return a modern AST instead of the legacy AST.
* `modern` will become `true` by default in Svelte 6, and the option will be removed in Svelte 7.
*
* https://svelte.dev/docs/svelte-compiler#svelte-parse
* @param {string} source
* @param {{ filename?: string; modern?: boolean }} [options]
* @returns {import('#compiler').SvelteNode | import('./types/legacy-nodes.js').LegacySvelteNode}
*/
export function parse(source, options = {}) {
return handle_error(source, undefined, () => {
/** @type {import('#compiler').Root} */
const ast = _parse(source);
if (options.modern) {
// remove things that we don't want to treat as public API
return walk(/** @type {import('#compiler').SvelteNode} */ (ast), null, {
_(node, { next }) {
// @ts-ignore
delete node.parent;
// @ts-ignore
delete node.metadata;
next();
}
});
}
return convert(source, ast);
});
}
/**
* @param {string} source
* @param {TODO} options
*/
export function analyze(source, options = {}) {
return handle_error(source, options.filename, () => {
const validated = validate_component_options(options, '');
const parsed = _parse(source);
const combined_options = /** @type {import('#compiler').ValidatedCompileOptions} */ ({
...validated,
...parsed.options
});
const analysis = analyze_component(parsed, combined_options);
return {
warnings: transform_warnings(source, options.filename, analysis.warnings)
};
});
}
/** /**
* `compile` converts your `.svelte` source code into a JavaScript module that exports a component * `compile` converts your `.svelte` source code into a JavaScript module that exports a component
* *
@ -17,7 +102,7 @@ export { default as preprocess } from './preprocess/index.js';
* @returns {import('#compiler').CompileResult} * @returns {import('#compiler').CompileResult}
*/ */
export function compile(source, options) { export function compile(source, options) {
try { return handle_error(source, options.filename, () => {
const validated = validate_component_options(options, ''); const validated = validate_component_options(options, '');
const parsed = _parse(source); const parsed = _parse(source);
@ -29,17 +114,7 @@ export function compile(source, options) {
const analysis = analyze_component(parsed, combined_options); const analysis = analyze_component(parsed, combined_options);
const result = transform_component(analysis, source, combined_options); const result = transform_component(analysis, source, combined_options);
return result; return result;
} catch (e) { });
if (/** @type {any} */ (e).name === 'CompileError') {
handle_compile_error(
/** @type {import('#compiler').CompileError} */ (e),
options.filename,
source
);
}
throw e;
}
} }
/** /**
@ -51,86 +126,11 @@ export function compile(source, options) {
* @returns {import('#compiler').CompileResult} * @returns {import('#compiler').CompileResult}
*/ */
export function compileModule(source, options) { export function compileModule(source, options) {
try { return handle_error(source, options.filename, () => {
const validated = validate_module_options(options, ''); const validated = validate_module_options(options, '');
const analysis = analyze_module(parse_acorn(source), validated); const analysis = analyze_module(parse_acorn(source), validated);
return transform_module(analysis, source, validated); return transform_module(analysis, source, validated);
} catch (e) { });
if (/** @type {any} */ (e).name === 'CompileError') {
handle_compile_error(
/** @type {import('#compiler').CompileError} */ (e),
options.filename,
source
);
}
throw e;
}
}
/**
* @param {import('#compiler').CompileError} error
* @param {string | undefined} filename
* @param {string} source
*/
function handle_compile_error(error, filename, source) {
error.filename = filename;
if (error.position) {
// TODO this is reused with warnings — DRY out
const locator = getLocator(source, { offsetLine: 1 });
const start = locator(error.position[0]);
const end = locator(error.position[1]);
error.start = start;
error.end = end;
}
throw error;
}
/**
* The parse function parses a component, returning only its abstract syntax tree.
*
* The `modern` option (`false` by default in Svelte 5) makes the parser return a modern AST instead of the legacy AST.
* `modern` will become `true` by default in Svelte 6, and the option will be removed in Svelte 7.
*
* https://svelte.dev/docs/svelte-compiler#svelte-parse
* @param {string} source
* @param {{ filename?: string; modern?: boolean }} [options]
* @returns {import('#compiler').SvelteNode | import('./types/legacy-nodes.js').LegacySvelteNode}
*/
export function parse(source, options = {}) {
/** @type {import('#compiler').Root} */
let ast;
try {
ast = _parse(source);
} catch (e) {
if (/** @type {any} */ (e).name === 'CompileError') {
handle_compile_error(
/** @type {import('#compiler').CompileError} */ (e),
options.filename,
source
);
}
throw e;
}
if (options.modern) {
// remove things that we don't want to treat as public API
return walk(/** @type {import('#compiler').SvelteNode} */ (ast), null, {
_(node, { next }) {
// @ts-ignore
delete node.parent;
// @ts-ignore
delete node.metadata;
next();
}
});
}
return convert(source, ast);
} }
/** /**

@ -3,6 +3,7 @@ import { VERSION } from '../../../version.js';
import { server_component, server_module } from './server/transform-server.js'; import { server_component, server_module } from './server/transform-server.js';
import { client_component, client_module } from './client/transform-client.js'; import { client_component, client_module } from './client/transform-client.js';
import { getLocator } from 'locate-character'; import { getLocator } from 'locate-character';
import { transform_warnings } from '../../utils/warnings.js';
/** /**
* @param {import('../types').ComponentAnalysis} analysis * @param {import('../types').ComponentAnalysis} analysis
@ -102,38 +103,3 @@ export function transform_module(analysis, source, options) {
} }
}; };
} }
/**
* @param {string} source
* @param {string | undefined} name
* @param {import('../types').RawWarning[]} warnings
* @returns {import('#compiler').Warning[]}
*/
function transform_warnings(source, name, warnings) {
if (warnings.length === 0) return [];
const locate = getLocator(source, { offsetLine: 1 });
/** @type {import('#compiler').Warning[]} */
const result = [];
for (const warning of warnings) {
const start =
warning.position &&
/** @type {import('locate-character').Location} */ (locate(warning.position[0]));
const end =
warning.position &&
/** @type {import('locate-character').Location} */ (locate(warning.position[1]));
result.push({
start,
end,
filename: name,
message: warning.message,
code: warning.code
});
}
return result;
}

@ -2,6 +2,7 @@ import type {
BindDirective, BindDirective,
Binding, Binding,
Fragment, Fragment,
RawWarning,
RegularElement, RegularElement,
SvelteElement, SvelteElement,
SvelteNode, SvelteNode,
@ -33,12 +34,6 @@ export interface BindingGroup {
directives: BindDirective[]; directives: BindDirective[];
} }
export interface RawWarning {
code: string;
message: string;
position: [number, number] | undefined;
}
/** /**
* Analysis common to modules and components * Analysis common to modules and components
*/ */

@ -285,3 +285,9 @@ export { Css };
// TODO this chain is a bit weird // TODO this chain is a bit weird
export { ReactiveStatement } from '../phases/types.js'; export { ReactiveStatement } from '../phases/types.js';
export interface RawWarning {
code: string;
message: string;
position: [number, number] | undefined;
}

@ -0,0 +1,36 @@
import { getLocator } from 'locate-character';
/**
* @param {string} source
* @param {string | undefined} name
* @param {import('#compiler').RawWarning[]} warnings
* @returns {import('#compiler').Warning[]}
*/
export function transform_warnings(source, name, warnings) {
if (warnings.length === 0) return [];
const locate = getLocator(source, { offsetLine: 1 });
/** @type {import('#compiler').Warning[]} */
const result = [];
for (const warning of warnings) {
const start =
warning.position &&
/** @type {import('locate-character').Location} */ (locate(warning.position[0]));
const end =
warning.position &&
/** @type {import('locate-character').Location} */ (locate(warning.position[1]));
result.push({
start,
end,
filename: name,
message: warning.message,
code: warning.code
});
}
return result;
}
Loading…
Cancel
Save