Skip to content

Commit

Permalink
feat: Ignore imports that are inside comments
Browse files Browse the repository at this point in the history
  • Loading branch information
jespertheend committed Feb 19, 2022
1 parent 2e3fe28 commit 247887e
Show file tree
Hide file tree
Showing 4 changed files with 306 additions and 5 deletions.
56 changes: 56 additions & 0 deletions src/parseComments.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* @typedef CommentLocation
* @property {number} start
* @property {number} end
*/

/**
* @param {string} scriptSource
*/
export function getCommentLocations(scriptSource) {
/** @type {CommentLocation[]} */
const commentLocations = [];
let totalIndex = 0;
for (const line of scriptSource.split("\n")) {
const commentIndex = line.indexOf("//");
if (commentIndex >= 0) {
commentLocations.push({
start: totalIndex + commentIndex,
end: totalIndex + line.length,
});
}
totalIndex += line.length + "\n".length;
}

const blockCommentRegex = /\/\*[\s\S]*\*\//g;
for (const match of scriptSource.matchAll(blockCommentRegex)) {
if (match.index == undefined) continue;
commentLocations.push({
start: match.index,
end: match.index + match[0].length,
});
}

commentLocations.sort((a, b) => a.start - b.start);

// Merge overlapping comments
const mergedCommentLocations = [];
let lastCommentLocation = null;
for (const location of commentLocations) {
if (lastCommentLocation) {
if (location.start >= lastCommentLocation.end) {
mergedCommentLocations.push(location);
lastCommentLocation = location;
} else {
lastCommentLocation.end = Math.max(
lastCommentLocation.end,
location.end,
);
}
} else {
mergedCommentLocations.push(location);
lastCommentLocation = location;
}
}
return mergedCommentLocations;
}
32 changes: 27 additions & 5 deletions src/parseImports.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { getCommentLocations } from "./parseComments.js";

/**
* @typedef {Object} ImportLocation
* @property {number} start
Expand All @@ -7,14 +9,14 @@

/**
* @param {RegExpMatchArray} match
* @param {ImportLocation[]} imports
* @param {Set<ImportLocation>} imports
*/
function parseMatch(match, imports) {
const url = match.groups?.url;
// @ts-expect-error indices does not exist but all browsers support it
const start = match.indices.groups?.url[0];
if (url && start !== undefined) {
imports.push({
imports.add({
start,
length: url.length,
url,
Expand All @@ -28,8 +30,8 @@ function parseMatch(match, imports) {
* @param {string} scriptSource
*/
export function parseImports(scriptSource) {
/** @type {ImportLocation[]} */
const imports = [];
/** @type {Set<ImportLocation>} */
const imports = new Set();
const staticImportRegex = /(?:^|;)\s*import[\s\S]+?["'](?<url>.+?)["']/gmd;
for (const match of scriptSource.matchAll(staticImportRegex)) {
parseMatch(match, imports);
Expand All @@ -38,5 +40,25 @@ export function parseImports(scriptSource) {
for (const match of scriptSource.matchAll(dynamicImportRegex)) {
parseMatch(match, imports);
}
return imports;

const commentLocation = getCommentLocations(scriptSource);

/** @type {ImportLocation[]} */
const overlappingImports = [];
for (const importLocation of imports) {
for (const comment of commentLocation) {
const importStart = importLocation.start;
const importEnd = importLocation.start + importLocation.length;
// If import overlaps with the comment
if (importEnd > comment.start && importStart < comment.end) {
overlappingImports.push(importLocation);
}
}
}

for (const overlapping of overlappingImports) {
imports.delete(overlapping);
}

return Array.from(imports);
}
167 changes: 167 additions & 0 deletions test/unit/src/parseComments.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import { assertEquals } from "https://deno.land/[email protected]/testing/asserts.ts";
import { getCommentLocations } from "../../../src/parseComments.js";

Deno.test({
name: "single line comment",
fn() {
const source = `// comment`;

const result = getCommentLocations(source);

assertEquals(result, [
{ start: 0, end: 10 },
]);
},
});

Deno.test({
name: "line comment after non comment",
fn() {
const source = `
not a comment // comment
`;

const result = getCommentLocations(source);

assertEquals(result, [
{ start: 21, end: 31 },
]);
},
});

Deno.test({
name: "line comment in between non comments",
fn() {
const source = `
not a comment
not a comment // comment
not a comment
`;

const result = getCommentLocations(source);

assertEquals(result, [
{ start: 41, end: 51 },
]);
},
});

Deno.test({
name: "multiple line comments",
fn() {
const source = `
// comment
not a comment
not a comment // comment
not a comment // comment
not a comment
not a comment // comment
`;

const result = getCommentLocations(source);

assertEquals(result, [
{ start: 7, end: 17 },
{ start: 58, end: 68 },
{ start: 89, end: 99 },
{ start: 140, end: 150 },
]);
},
});

Deno.test({
name: "block comment",
fn() {
const source = `/* comment */`;

const result = getCommentLocations(source);

assertEquals(result, [
{ start: 0, end: 13 },
]);
},
});

Deno.test({
name: "multi line block comment",
fn() {
const source = `
/*
comment
*/
`;

const result = getCommentLocations(source);

assertEquals(result, [
{ start: 7, end: 34 },
]);
},
});

Deno.test({
name: "jsdoc style comment",
fn() {
const source = `
/**
* @fileoverview test
*/
`;

const result = getCommentLocations(source);

assertEquals(result, [
{ start: 7, end: 48 },
]);
},
});

Deno.test({
name: "line comment inside block comment",
fn() {
const source = `
/*
// comment
*/
`;

const result = getCommentLocations(source);

assertEquals(result, [
{ start: 7, end: 35 },
]);
},
});

Deno.test({
name: "block comment between non comments",
fn() {
const source = `
not a comment
/* comment */
not a comment
`;

const result = getCommentLocations(source);

assertEquals(result, [
{ start: 27, end: 40 },
]);
},
});

Deno.test({
name: "block comment inside line comment",
fn() {
const source = `
// comment /* comment */
not a comment
`;

const result = getCommentLocations(source);

assertEquals(result, [
{ start: 7, end: 31 },
]);
},
});
56 changes: 56 additions & 0 deletions test/unit/src/parseImports.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,59 @@ Deno.test("Dynamic", () => {
},
]);
});

Deno.test({
name: "Doesn't import inside comments",
fn() {
const scriptSources = [
`
// import './script.js';
`,
`
// import "./script.js";
`,
`
// import {named} from "./script.js";
`,
`
/* import './script.js'; */
`,
`
/*
* import "./script.js";
*/
`,
`
// import("./script.js")
`,
`
// ;import "./script.js"
`,
`
/*
import "./script.js";
*/
`,
`
/*
import("./script.js")
*/
`,
`
/*
* import("./script.js")
*/
`,
];

for (const source of scriptSources) {
const imports = parseImports(source);

assertEquals(
imports,
[],
`The following should not have imports: ${source}`,
);
}
},
});

0 comments on commit 247887e

Please sign in to comment.