Skip to content
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

[v4.0] Expose parser #5169

Merged
merged 2 commits into from
Oct 5, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion build-plugins/copy-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export function copyNodeTypes(): Plugin[] {
'cli/run/loadConfigFileType.d.ts',
'../../src/rollup/types'
),
copyRollupType('getLogFilter.d.ts', 'src/utils/getLogFilterType.d.ts', '../rollup/types')
copyRollupType('getLogFilter.d.ts', 'src/utils/getLogFilterType.d.ts', '../rollup/types'),
copyRollupType('parseAst.d.ts', 'src/utils/parseAstType.d.ts', '../rollup/types')
];
}
33 changes: 33 additions & 0 deletions docs/javascript-api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -336,3 +336,36 @@ export default {
}
};
```

## Accessing the parser

In order to parse arbitrary code using Rollup's parser, plugins can use [`this.parse`](../plugin-development/index.md#this-parse). To use this functionality outside the context of a Rollup build, the parser is also exposed as a separate export. It has the same signature as `this.parse`:

```js
import { parseAst } from 'rollup/parseAst';
import assert from 'node:assert';

assert.deepEqual(
parseAst('return 42;', { allowReturnOutsideFunction: true }),
{
type: 'Program',
start: 0,
end: 10,
body: [
{
type: 'ReturnStatement',
start: 0,
end: 10,
argument: {
type: 'Literal',
start: 7,
end: 9,
raw: '42',
value: 42
}
}
],
sourceType: 'module'
}
);
```
2 changes: 1 addition & 1 deletion docs/plugin-development/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -1669,7 +1669,7 @@ interface ParseOptions {
}
```
Use Rollup's internal SWC-based parser to parse code to an AST.
Use Rollup's internal SWC-based parser to parse code to an [ESTree-compatible](https://github.com/estree/estree) AST.
- `allowReturnOutsideFunction`: When `true` this allows return statements to be outside functions to e.g. support parsing CommonJS code.
Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,11 @@
"require": "./dist/getLogFilter.js",
"import": "./dist/es/getLogFilter.js"
},
"./parseAst": {
"types": "./dist/parseAst.d.ts",
"require": "./dist/parseAst.js",
"import": "./dist/es/parseAst.js"
},
"./dist/*": "./dist/*"
}
}
2 changes: 2 additions & 0 deletions rollup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export default async function (
input: {
'getLogFilter.js': 'src/utils/getLogFilter.ts',
'loadConfigFile.js': 'cli/run/loadConfigFile.ts',
'parseAst.js': 'src/utils/parseAst.ts',
'rollup.js': 'src/node-entry.ts'
},
onwarn,
Expand Down Expand Up @@ -112,6 +113,7 @@ export default async function (
...commonJSBuild,
input: {
'getLogFilter.js': 'src/utils/getLogFilter.ts',
'parseAst.js': 'src/utils/parseAst.ts',
'rollup.js': 'src/node-entry.ts'
},
output: {
Expand Down
3 changes: 2 additions & 1 deletion src/Bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
} from './utils/logs';
import type { OutputBundleWithPlaceholders } from './utils/outputBundle';
import { getOutputBundle, removeUnreferencedAssets } from './utils/outputBundle';
import { parseAst } from './utils/parseAst';
import { isAbsolute } from './utils/path';
import { renderChunks } from './utils/renderChunks';
import { timeEnd, timeStart } from './utils/timers';
Expand Down Expand Up @@ -150,7 +151,7 @@ export default class Bundle {
for (const file of Object.values(bundle)) {
if ('code' in file) {
try {
this.graph.contextParse(file.code);
parseAst(file.code);
} catch (error_: any) {
this.inputOptions.onLog(LOGLEVEL_WARN, logChunkInvalid(file, error_));
}
Expand Down
12 changes: 0 additions & 12 deletions src/Graph.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import flru from 'flru';
import { parse } from '../native';
import type ExternalModule from './ExternalModule';
import Module from './Module';
import { ModuleLoader, type UnresolvedModule } from './ModuleLoader';
Expand All @@ -18,9 +17,7 @@ import type {
import { PluginDriver } from './utils/PluginDriver';
import Queue from './utils/Queue';
import { BuildPhase } from './utils/buildPhase';
import { convertProgram, type ProgramAst } from './utils/convert-ast';
import { analyseModuleExecution } from './utils/executionOrder';
import getReadStringFunction from './utils/getReadStringFunction';
import { LOGLEVEL_WARN } from './utils/logging';
import {
error,
Expand Down Expand Up @@ -123,15 +120,6 @@ export default class Graph {
this.phase = BuildPhase.GENERATE;
}

contextParse(
code: string,
{ allowReturnOutsideFunction = false }: { allowReturnOutsideFunction?: boolean } = {}
): ProgramAst {
const astBuffer = parse(code, allowReturnOutsideFunction);
const readString = getReadStringFunction(astBuffer);
return convertProgram(astBuffer.buffer, readString);
}

getCache(): RollupCache {
// handle plugin cache eviction
for (const name in this.pluginCache) {
Expand Down
7 changes: 4 additions & 3 deletions src/Module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,12 @@ import {
logInvalidFormatForTopLevelAwait,
logInvalidSourcemapForError,
logMissingExport,
logModuleParseError,
logNamespaceConflict,
logParseError,
logShimmedExport,
logSyntheticNamedExportsNeedNamespaceExport
} from './utils/logs';
import { parseAst } from './utils/parseAst';
import {
doAttributesDiffer,
getAttributesFromImportExportDeclaration
Expand Down Expand Up @@ -1328,9 +1329,9 @@ export default class Module {

private tryParse(): ProgramAst {
try {
return this.graph.contextParse(this.info.code!);
return parseAst(this.info.code!) as ProgramAst;
} catch (error_: any) {
return this.error(logParseError(error_, this.id), error_.pos);
return this.error(logModuleParseError(error_, this.id), error_.pos);
}
}
}
Expand Down
7 changes: 6 additions & 1 deletion src/rollup/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,11 @@ type LoggingFunctionWithPosition = (
pos?: number | { column: number; line: number }
) => void;

export type ParseAst = (
input: string,
options?: { allowReturnOutsideFunction?: boolean }
) => AstNode;

export interface PluginContext extends MinimalPluginContext {
addWatchFile: (id: string) => void;
cache: PluginCache;
Expand All @@ -215,7 +220,7 @@ export interface PluginContext extends MinimalPluginContext {
load: (
options: { id: string; resolveDependencies?: boolean } & Partial<PartialNull<ModuleOptions>>
) => Promise<ModuleInfo>;
parse: (input: string, options?: { allowReturnOutsideFunction?: boolean }) => AstNode;
parse: ParseAst;
resolve: (
source: string,
importer?: string,
Expand Down
3 changes: 2 additions & 1 deletion src/utils/PluginContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { getLogHandler } from './logHandler';
import { LOGLEVEL_DEBUG, LOGLEVEL_INFO, LOGLEVEL_WARN } from './logging';
import { error, logInvalidRollupPhaseForAddWatchFile, logPluginError } from './logs';
import { normalizeLog } from './options/options';
import { parseAst } from './parseAst';
import { ANONYMOUS_OUTPUT_PLUGIN_PREFIX, ANONYMOUS_PLUGIN_PREFIX } from './pluginUtils';

export function getPluginContext(
Expand Down Expand Up @@ -76,7 +77,7 @@ export function getPluginContext(
rollupVersion,
watchMode: graph.watchMode
},
parse: graph.contextParse.bind(graph),
parse: parseAst,
resolve(source, importer, { attributes, custom, isEntry, skipSelf } = BLANK) {
skipSelf ??= true;
return graph.moduleLoader.resolveId(
Expand Down
9 changes: 3 additions & 6 deletions src/utils/convert-ast.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type * as estree from 'estree';
import type { AstNode } from '../rollup/types';
import { FIXED_STRINGS } from './convert-ast-strings';
import { error } from './logs';
import { error, logParseError } from './logs';

type ReadString = (start: number, length: number) => string;

Expand Down Expand Up @@ -862,7 +862,7 @@ const nodeConverters: ((position: number, buffer: Uint32Array, readString: ReadS
kind,
method,
shorthand,
value: valuePosition ? convertNode(valuePosition, buffer, readString) : key
value: valuePosition ? convertNode(valuePosition, buffer, readString) : { ...key }
};
},
// index:55; PropertyDefinition
Expand Down Expand Up @@ -1156,10 +1156,7 @@ const nodeConverters: ((position: number, buffer: Uint32Array, readString: ReadS
(position, buffer, readString): never => {
const pos = buffer[position++];
const message = convertString(position, buffer, readString);
error({
pos,
message
});
error(logParseError(message, pos));
}
];

Expand Down
6 changes: 5 additions & 1 deletion src/utils/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -809,7 +809,11 @@ export function logOptimizeChunkStatus(
};
}

export function logParseError(error: Error, moduleId: string): RollupLog {
export function logParseError(message: string, pos: number): RollupLog {
return { code: PARSE_ERROR, message, pos };
}

export function logModuleParseError(error: Error, moduleId: string): RollupLog {
let message = error.message.replace(/ \(\d+:\d+\)$/, '');
if (moduleId.endsWith('.json')) {
message += ' (Note that you need @rollup/plugin-json to import JSON files)';
Expand Down
10 changes: 10 additions & 0 deletions src/utils/parseAst.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { parse } from '../../native';
import type { ParseAst } from '../rollup/types';
import { convertProgram } from './convert-ast';
import getReadStringFunction from './getReadStringFunction';

export const parseAst: ParseAst = (input, { allowReturnOutsideFunction = false } = {}) => {
const astBuffer = parse(input, allowReturnOutsideFunction);
const readString = getReadStringFunction(astBuffer);
return convertProgram(astBuffer.buffer, readString);
};
3 changes: 3 additions & 0 deletions src/utils/parseAstType.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import type { ParseAst } from '../rollup/types';

export const parseAst: ParseAst;
1 change: 1 addition & 0 deletions test/function/samples/double-default-export/_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module.exports = defineTest({
description: 'throws on double default exports',
error: {
cause: {
code: 'PARSE_ERROR',
message: 'the name `default` is exported multiple times',
pos: 18
},
Expand Down
1 change: 1 addition & 0 deletions test/function/samples/double-named-export/_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module.exports = defineTest({
description: 'throws on duplicate named exports',
error: {
cause: {
code: 'PARSE_ERROR',
message: 'the name `foo` is exported multiple times',
pos: 38
},
Expand Down
1 change: 1 addition & 0 deletions test/function/samples/double-named-reexport/_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module.exports = defineTest({
description: 'throws on duplicate named exports',
error: {
cause: {
code: 'PARSE_ERROR',
message: 'the name `foo` is exported multiple times',
pos: 38
},
Expand Down
1 change: 1 addition & 0 deletions test/function/samples/duplicate-import-fails/_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module.exports = defineTest({
description: 'disallows duplicate imports',
error: {
cause: {
code: 'PARSE_ERROR',
message: 'the name `a` is defined multiple times',
pos: 36
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module.exports = defineTest({
description: 'disallows duplicate import specifiers',
error: {
cause: {
code: 'PARSE_ERROR',
message: 'the name `a` is defined multiple times',
pos: 12
},
Expand Down
1 change: 1 addition & 0 deletions test/function/samples/error-parse-json/_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module.exports = defineTest({
'throws with an extended error message when failing to parse a file with ".json" extension',
error: {
cause: {
code: 'PARSE_ERROR',
pos: 10,
message: "Expected ';', '}' or <eof>"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module.exports = defineTest({
'throws with an extended error message when failing to parse a file without .(m)js extension',
error: {
cause: {
code: 'PARSE_ERROR',
pos: 0,
message: 'Expression expected'
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module.exports = defineTest({
description: 'disallows non-top-level exports',
error: {
cause: {
code: 'PARSE_ERROR',
pos: 19,
message: "'import', and 'export' cannot be used outside of module code"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module.exports = defineTest({
description: 'disallows non-top-level imports',
error: {
cause: {
code: 'PARSE_ERROR',
pos: 19,
message: "'import', and 'export' cannot be used outside of module code"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ module.exports = defineTest({
expectedError = error;
}
compareError(expectedError, {
code: 'PARSE_ERROR',
message: 'Return statement is not allowed here',
pos: 0
});
Expand All @@ -46,6 +47,7 @@ module.exports = defineTest({
expectedError = error;
}
compareError(expectedError, {
code: 'PARSE_ERROR',
message: 'Return statement is not allowed here',
pos: 0
});
Expand Down
1 change: 1 addition & 0 deletions test/function/samples/reassign-import-fails/_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module.exports = defineTest({
error: {
code: 'PARSE_ERROR',
cause: {
code: 'PARSE_ERROR',
message: 'cannot reassign to an imported binding',
pos: 113
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module.exports = defineTest({
error: {
code: 'PARSE_ERROR',
cause: {
code: 'PARSE_ERROR',
message: 'cannot reassign to an imported binding',
pos: 95
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module.exports = defineTest({
error: {
code: 'PARSE_ERROR',
cause: {
code: 'PARSE_ERROR',
message: 'cannot reassign to an imported binding',
pos: 28
},
Expand Down
1 change: 1 addition & 0 deletions test/misc/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require('./bundle-information');
require('./get-log-filter');
require('./parse-ast');
require('./iife');
require('./in-memory-sourcemaps');
require('./misc');
Expand Down