Skip to content

Commit

Permalink
[v4.0] Expose parser (#5169)
Browse files Browse the repository at this point in the history
* Expose parser as separate API

* Ensure key and value of a shorthand property have different references
  • Loading branch information
lukastaegert committed Oct 5, 2023
1 parent cade24f commit 4e8e5b4
Show file tree
Hide file tree
Showing 31 changed files with 208 additions and 31 deletions.
2 changes: 1 addition & 1 deletion browser/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@rollup/browser",
"version": "4.0.0-24",
"version": "4.0.0-25",
"description": "Next-generation ES module bundler browser build",
"main": "dist/rollup.browser.js",
"module": "dist/es/rollup.browser.js",
Expand Down
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
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "rollup",
"version": "4.0.0-24",
"version": "4.0.0-25",
"description": "Next-generation ES module bundler",
"main": "dist/rollup.js",
"module": "dist/es/rollup.js",
Expand Down 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

0 comments on commit 4e8e5b4

Please sign in to comment.