Skip to content

Commit

Permalink
fix: use acorn to strip comments (unjs#279)
Browse files Browse the repository at this point in the history
  • Loading branch information
cjpearson committed Oct 7, 2024
1 parent 918e835 commit c4ab502
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 13 deletions.
48 changes: 36 additions & 12 deletions src/syntax.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { promises as fsp } from "node:fs";
import { parse } from "acorn";
import { extname } from "pathe";
import { readPackageJSON } from "pkg-types";
import { ResolveOptions, resolvePath } from "./resolve";
Expand All @@ -10,9 +11,6 @@ const ESM_RE =
const CJS_RE =
/([\s;]|^)(module.exports\b|exports\.\w|require\s*\(|global\.\w)/m;

const MULTI_LINE_COMMENT_RE = /\/\*.+?\*\//gs;
const SINGLE_LINE_COMMENT_RE = /\/\/.*/g;

const BUILTIN_EXTENSIONS = new Set([".mjs", ".cjs", ".node", ".wasm"]);

/**
Expand All @@ -26,6 +24,38 @@ export type DetectSyntaxOptions = {
stripComments?: boolean;
};

interface TokenLocation {
start: number;
end: number;
}

function _getCommentLocations(code: string) {
const locations: TokenLocation[] = [];
parse(code, {
ecmaVersion: "latest",
allowHashBang: true,
allowAwaitOutsideFunction: true,
allowImportExportEverywhere: true,
onComment(_isBlock, _text, start, end) {
locations.push({ start, end });
},
});
return locations;
}

/**
* Strip comments from a string of source code
*
* @param code - The source code to remove comments from.
*/
export function stripComments(code: string) {
const locations = _getCommentLocations(code);
for (const location of locations.reverse()) {
code = code.slice(0, location.start) + code.slice(location.end);
}
return code;
}

/**
* Determines if a given code string contains ECMAScript module syntax.
*
Expand All @@ -38,9 +68,7 @@ export function hasESMSyntax(
opts: DetectSyntaxOptions = {},
): boolean {
if (opts.stripComments) {
code = code
.replace(SINGLE_LINE_COMMENT_RE, "")
.replace(MULTI_LINE_COMMENT_RE, "");
code = stripComments(code);
}
return ESM_RE.test(code);
}
Expand All @@ -57,9 +85,7 @@ export function hasCJSSyntax(
opts: DetectSyntaxOptions = {},
): boolean {
if (opts.stripComments) {
code = code
.replace(SINGLE_LINE_COMMENT_RE, "")
.replace(MULTI_LINE_COMMENT_RE, "");
code = stripComments(code);
}
return CJS_RE.test(code);
}
Expand All @@ -73,9 +99,7 @@ export function hasCJSSyntax(
*/
export function detectSyntax(code: string, opts: DetectSyntaxOptions = {}) {
if (opts.stripComments) {
code = code
.replace(SINGLE_LINE_COMMENT_RE, "")
.replace(MULTI_LINE_COMMENT_RE, "");
code = stripComments(code);
}
// We strip comments once hence not passing opts down to hasESMSyntax and hasCJSSyntax
const hasESM = hasESMSyntax(code, {});
Expand Down
23 changes: 22 additions & 1 deletion test/syntax.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { join } from "pathe";
import { describe, it, expect } from "vitest";
import { detectSyntax, isValidNodeImport } from "../src";
import { detectSyntax, isValidNodeImport, stripComments } from "../src";

const staticTests = {
// ESM
Expand Down Expand Up @@ -94,6 +94,19 @@ const staticTestsWithComments = {
{ hasESM: false, hasCJS: true, isMixed: false },
"/* export * */": { hasESM: false, hasCJS: false, isMixed: false },
"/* \n export * \n */": { hasESM: false, hasCJS: false, isMixed: false },
"/* \n export * \n */ export * from 'foo' /* \n export * \n */": {
hasESM: true,
hasCJS: false,
isMixed: false,
},
};

const commentStrippingTests = {
'// They\'re exposed using "export import" so that types are passed along as expected\nmodule.exports={};':
"\nmodule.exports={};",
"/* export * */": "",
"/* \n export * \n */": "",
"/* \n export * \n */ export * from 'foo' /* \n export * \n */": ` export * from 'foo' `,
};

describe("detectSyntax", () => {
Expand All @@ -114,6 +127,14 @@ describe("detectSyntax (with comment)", () => {
}
});

describe("stripComments", () => {
for (const [input, result] of Object.entries(commentStrippingTests)) {
it(input, () => {
expect(stripComments(input)).to.deep.equal(result);
});
}
});

const nodeImportTests = {
"node:fs": true,
fs: true,
Expand Down

0 comments on commit c4ab502

Please sign in to comment.