-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: use new SourceParser class
- Loading branch information
Showing
9 changed files
with
204 additions
and
139 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
// Import Third-party Dependencies | ||
import * as meriyah from "meriyah"; | ||
|
||
// CONSTANTS | ||
const kParsingOptions = { | ||
next: true, | ||
loc: true, | ||
raw: true, | ||
jsx: true | ||
}; | ||
|
||
export class SourceParser { | ||
/** | ||
* @param {!string} source | ||
* @param {object} options | ||
* @param {boolean} [options.removeHTMLComments=false] | ||
*/ | ||
constructor(source, options = {}) { | ||
if (typeof source !== "string") { | ||
throw new TypeError("source must be a string"); | ||
} | ||
const { removeHTMLComments = false } = options; | ||
|
||
this.raw = source; | ||
|
||
/** | ||
* if the file start with a shebang then we remove it because meriyah.parseScript fail to parse it. | ||
* @example | ||
* #!/usr/bin/env node | ||
*/ | ||
const rawNoShebang = source.charAt(0) === "#" ? | ||
source.slice(source.indexOf("\n") + 1) : source; | ||
|
||
this.source = removeHTMLComments ? | ||
this.#removeHTMLComment(rawNoShebang) : rawNoShebang; | ||
} | ||
|
||
#removeHTMLComment(str) { | ||
return str.replace(/<!--[\s\S]*?(?:-->)/g, ""); | ||
Check failure Code scanning / CodeQL Polynomial regular expression used on uncontrolled data High
This
regular expression Error loading related location Loading library input Error loading related location Loading Check failure Code scanning / CodeQL Incomplete multi-character sanitization High
This string may still contain
<!-- Error loading related location Loading |
||
} | ||
|
||
/** | ||
* @param {object} options | ||
* @param {boolean} options.isEcmaScriptModule | ||
*/ | ||
parseScript(options = {}) { | ||
const { | ||
isEcmaScriptModule | ||
} = options; | ||
|
||
try { | ||
const { body } = meriyah.parseScript( | ||
this.source, | ||
{ | ||
...kParsingOptions, | ||
module: isEcmaScriptModule, | ||
globalReturn: !isEcmaScriptModule | ||
} | ||
); | ||
|
||
return body; | ||
} | ||
catch (error) { | ||
const isIllegalReturn = error.description.includes("Illegal return statement"); | ||
|
||
if (error.name === "SyntaxError" && ( | ||
error.description.includes("The import keyword") || | ||
error.description.includes("The export keyword") || | ||
isIllegalReturn | ||
)) { | ||
const { body } = meriyah.parseScript( | ||
this.source, | ||
{ | ||
...kParsingOptions, | ||
module: true, | ||
globalReturn: isIllegalReturn | ||
} | ||
); | ||
|
||
return body; | ||
} | ||
|
||
throw error; | ||
} | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
// Import Node.js Dependencies | ||
import { describe, it } from "node:test"; | ||
import assert from "node:assert"; | ||
|
||
// Import Internal Dependencies | ||
import { SourceParser } from "../src/SourceParser.js"; | ||
|
||
describe("SourceParser", () => { | ||
describe("constructor", () => { | ||
it("should throw a TypeError if source is not a string", () => { | ||
assert.throws( | ||
() => new SourceParser(10), | ||
{ message: "source must be a string" } | ||
); | ||
}); | ||
|
||
it("should not update the content of raw", () => { | ||
const expectedStr = "hello world"; | ||
|
||
assert.strictEqual( | ||
new SourceParser(expectedStr).raw, | ||
expectedStr | ||
); | ||
|
||
assert.strictEqual( | ||
new SourceParser(expectedStr, { removeHTMLComments: true }).raw, | ||
expectedStr | ||
); | ||
}); | ||
|
||
it("should remove shebang at the start of the file", () => { | ||
const sp = new SourceParser("#!/usr/bin/env node\nconst hello = \"world\";"); | ||
|
||
assert.strictEqual( | ||
sp.source, | ||
"const hello = \"world\";" | ||
); | ||
}); | ||
|
||
it("should not remove shebang if not at the start (that's an illegal code)", () => { | ||
const source = "const hello = \"world\";\n#!/usr/bin/env node"; | ||
const sp = new SourceParser(source); | ||
|
||
assert.strictEqual( | ||
sp.source, | ||
source | ||
); | ||
}); | ||
|
||
it("should remove singleline HTML comment from source code when removeHTMLComments is enabled", () => { | ||
const sp = new SourceParser("<!-- const yo = 5; -->", { | ||
removeHTMLComments: true | ||
}); | ||
|
||
assert.strictEqual(sp.source, ""); | ||
}); | ||
|
||
it("should remove multiline HTML comment from source code when removeHTMLComments is enabled", () => { | ||
const sp = new SourceParser(` | ||
<!-- | ||
// == fake comment == // | ||
const yo = 5; | ||
//--> | ||
`, { | ||
removeHTMLComments: true | ||
}); | ||
|
||
assert.strictEqual(sp.source.trim(), ""); | ||
}); | ||
}); | ||
|
||
describe("parseScript", () => { | ||
it("should not crash even if isEcmaScriptModule 'false' is provided (import keyword)", () => { | ||
new SourceParser("import * as foo from \"foo\";").parseScript({ | ||
isEcmaScriptModule: false | ||
}); | ||
}); | ||
|
||
it("should not crash even if isEcmaScriptModule 'false' is provided (export keyword)", () => { | ||
new SourceParser("export const foo = 5;").parseScript({ | ||
isEcmaScriptModule: false | ||
}); | ||
}); | ||
|
||
it("should not crash with a source code containing JSX", () => { | ||
const code = `const Dropzone = forwardRef(({ children, ...params }, ref) => { | ||
const { open, ...props } = useDropzone(params); | ||
useImperativeHandle(ref, () => ({ open }), [open]); | ||
return <Fragment>{children({ ...props, open })}</Fragment>; | ||
});`; | ||
|
||
new SourceParser(code).parseScript({ | ||
isEcmaScriptModule: false | ||
}); | ||
}); | ||
}); | ||
}); |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.