- ✅ Ticket 1.1: Estructura Clean Architecture en backend - ✅ Ticket 1.2: Schemas Zod compartidos - ✅ Ticket 1.3: Refactorización drugs.ts (1362 → 8 archivos modulares) - ✅ Ticket 1.4: Refactorización procedures.ts (3583 → 6 archivos modulares) - ✅ Ticket 1.5: Eliminación de duplicidades (~50 líneas) Cambios principales: - Creada estructura Clean Architecture en backend/src/ - Schemas Zod compartidos en backend/src/shared/schemas/ - Refactorización modular de drugs y procedures - Utilidades genéricas en src/utils/ (filter, validation) - Eliminados scripts obsoletos y documentación antigua - Corregidos errores: QueryClient, import test-error-handling - Build verificado y funcionando correctamente
346 lines
12 KiB
JavaScript
346 lines
12 KiB
JavaScript
import BrowserErrorCaptureEnum from '../browser/enums/BrowserErrorCaptureEnum.js';
|
|
import WindowBrowserContext from '../window/WindowBrowserContext.js';
|
|
import * as PropertySymbol from '../PropertySymbol.js';
|
|
/**
|
|
* Statement regexp.
|
|
*
|
|
* Group 1: Dynamic import function call.
|
|
*/
|
|
const STATEMENT_REGEXP = /import\s*\(([^)]+)\)/gm;
|
|
/**
|
|
* Syntax regexp.
|
|
*
|
|
* Group 1: Slash (RegExp or comment).
|
|
* Group 2: Parentheses.
|
|
* Group 3: Curly braces.
|
|
* Group 4: Square brackets.
|
|
* Group 5: Escape template string (${).
|
|
* Group 6: Template string apostrophe (`).
|
|
* Group 7: String apostrophe (').
|
|
* Group 8: String apostrophe (").
|
|
* Group 9: Line feed character.
|
|
*/
|
|
const SYNTAX_REGEXP = /(\/)|(\(|\))|({|})|(\[|\])|(\${)|(`)|(')|(")|(\n)/gm;
|
|
/**
|
|
* Valid preceding token before a statement.
|
|
*/
|
|
const PRECEDING_STATEMENT_TOKEN_REGEXP = /['"`(){}\s;=><\[\]+-,:&]/;
|
|
/**
|
|
* Valid preceding token before a regexp.
|
|
*/
|
|
const PRECEDING_REGEXP_TOKEN_REGEXP = /['"`({};=><\[+-,:&]/;
|
|
/**
|
|
* ECMAScript module compiler.
|
|
*/
|
|
export default class JavaScriptCompiler {
|
|
window;
|
|
count = {
|
|
comment: 0,
|
|
singleLineComment: 0,
|
|
parentheses: 0,
|
|
curlyBraces: 0,
|
|
squareBrackets: 0,
|
|
regExp: 0,
|
|
regExpSquareBrackets: 0,
|
|
escapeTemplateString: 0,
|
|
simpleString: 0,
|
|
doubleString: 0
|
|
};
|
|
templateString = [];
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param window Window.
|
|
* @param url Module URL.
|
|
*/
|
|
constructor(window) {
|
|
this.window = window;
|
|
}
|
|
/**
|
|
* Compiles code.
|
|
*
|
|
* @param sourceURL Source URL.
|
|
* @param code Code.
|
|
* @returns Result.
|
|
*/
|
|
compile(sourceURL, code) {
|
|
const browserSettings = new WindowBrowserContext(this.window).getSettings();
|
|
if (!browserSettings) {
|
|
return { execute: () => { } };
|
|
}
|
|
const regExp = new RegExp(STATEMENT_REGEXP);
|
|
const count = this.count;
|
|
let newCode = '(function anonymous($happy_dom) {';
|
|
let match = null;
|
|
let precedingToken;
|
|
let textBetweenStatements;
|
|
let lastIndex = 0;
|
|
const importStartIndex = -1;
|
|
if (!browserSettings.disableErrorCapturing &&
|
|
browserSettings.errorCapture === BrowserErrorCaptureEnum.tryAndCatch) {
|
|
newCode += 'try {';
|
|
}
|
|
while ((match = regExp.exec(code))) {
|
|
precedingToken = code[match.index - 1] || ' ';
|
|
textBetweenStatements = code.substring(lastIndex, match.index);
|
|
this.parseSyntax(textBetweenStatements, lastIndex);
|
|
if (importStartIndex === -1) {
|
|
newCode += textBetweenStatements;
|
|
}
|
|
if (match[1] &&
|
|
count.simpleString === 0 &&
|
|
count.doubleString === 0 &&
|
|
count.comment === 0 &&
|
|
count.singleLineComment === 0 &&
|
|
count.regExp === 0 &&
|
|
(this.templateString.length === 0 || this.templateString[0] > 0) &&
|
|
PRECEDING_STATEMENT_TOKEN_REGEXP.test(precedingToken)) {
|
|
// Dynamic import function call
|
|
newCode += `$happy_dom.dynamicImport(${match[1]})`;
|
|
}
|
|
else {
|
|
// No valid statement found
|
|
// This happens when there is a statement inside a string or comment
|
|
// E.g. "const str = 'import defaultExport from invalid;';"
|
|
newCode += match[0];
|
|
this.parseSyntax(match[0], match.index);
|
|
}
|
|
lastIndex = regExp.lastIndex;
|
|
}
|
|
if (lastIndex === 0) {
|
|
newCode += code;
|
|
}
|
|
else {
|
|
newCode += code.substring(lastIndex);
|
|
}
|
|
if (!browserSettings.disableErrorCapturing &&
|
|
browserSettings.errorCapture === BrowserErrorCaptureEnum.tryAndCatch) {
|
|
newCode += '} catch (error) { $happy_dom.dispatchError(error); }';
|
|
}
|
|
newCode += '})';
|
|
try {
|
|
return {
|
|
execute: this.window[PropertySymbol.evaluateScript](newCode, {
|
|
filename: sourceURL
|
|
})
|
|
};
|
|
}
|
|
catch (error) {
|
|
error.message =
|
|
`Failed to parse JavaScript in '${sourceURL}': ${error.message}`;
|
|
if (browserSettings.disableErrorCapturing ||
|
|
browserSettings.errorCapture !== BrowserErrorCaptureEnum.tryAndCatch) {
|
|
throw error;
|
|
}
|
|
else {
|
|
this.window[PropertySymbol.dispatchError](error);
|
|
return {
|
|
execute: () => { }
|
|
};
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Parses syntax.
|
|
*
|
|
* @param code Code.
|
|
* @param index Index.
|
|
*/
|
|
parseSyntax(code, index) {
|
|
const regExp = new RegExp(SYNTAX_REGEXP);
|
|
const count = this.count;
|
|
let match = null;
|
|
let precedingToken;
|
|
let isEscaped;
|
|
index++;
|
|
while ((match = regExp.exec(code))) {
|
|
precedingToken = code[match.index - 1] || ' ';
|
|
isEscaped = precedingToken === '\\' && code[match.index - 2] !== '\\';
|
|
if (match[1]) {
|
|
// Slash (RegExp or Comment)
|
|
if (count.simpleString === 0 &&
|
|
count.doubleString === 0 &&
|
|
count.singleLineComment === 0 &&
|
|
count.regExpSquareBrackets === 0 &&
|
|
(this.templateString.length === 0 || this.templateString[0] > 0)) {
|
|
if (count.comment === 1) {
|
|
if (precedingToken === '*') {
|
|
count.comment = 0;
|
|
}
|
|
}
|
|
else {
|
|
if (count.regExp === 0) {
|
|
if (code[match.index + 1] === '*') {
|
|
count.comment = 1;
|
|
}
|
|
else if (code[match.index + 1] === '/') {
|
|
count.singleLineComment = 1;
|
|
}
|
|
else {
|
|
if (PRECEDING_REGEXP_TOKEN_REGEXP.test(this.getNonSpacePrecedingToken(code, match.index))) {
|
|
count.regExp = 1;
|
|
}
|
|
}
|
|
}
|
|
else if (!isEscaped) {
|
|
count.regExp = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (match[2]) {
|
|
// Parentheses
|
|
if (count.simpleString === 0 &&
|
|
count.doubleString === 0 &&
|
|
count.regExp === 0 &&
|
|
count.comment === 0 &&
|
|
count.singleLineComment === 0 &&
|
|
(this.templateString.length === 0 || this.templateString[0] > 0)) {
|
|
if (match[2] === '(') {
|
|
count.parentheses++;
|
|
}
|
|
else if (match[2] === ')' && count.parentheses > 0) {
|
|
count.parentheses--;
|
|
}
|
|
}
|
|
}
|
|
else if (match[3]) {
|
|
// Curly braces
|
|
if (count.simpleString === 0 &&
|
|
count.doubleString === 0 &&
|
|
count.regExp === 0 &&
|
|
count.comment === 0 &&
|
|
count.singleLineComment === 0 &&
|
|
(this.templateString.length === 0 || this.templateString[0] > 0)) {
|
|
if (match[3] === '{') {
|
|
if (this.templateString.length) {
|
|
this.templateString[0]++;
|
|
}
|
|
count.curlyBraces++;
|
|
}
|
|
else if (match[3] === '}') {
|
|
if (this.templateString.length && this.templateString[0] > 0) {
|
|
this.templateString[0]--;
|
|
}
|
|
if (count.curlyBraces > 0) {
|
|
count.curlyBraces--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (match[4]) {
|
|
// Square brackets
|
|
if (count.simpleString === 0 &&
|
|
count.doubleString === 0 &&
|
|
count.comment === 0 &&
|
|
count.singleLineComment === 0 &&
|
|
(this.templateString.length === 0 || this.templateString[0] > 0)) {
|
|
// We need to check for square brackets in RegExp as well to know when the RegExp ends
|
|
if (count.regExp === 1) {
|
|
if (!isEscaped) {
|
|
if (match[4] === '[' && count.regExpSquareBrackets === 0) {
|
|
count.regExpSquareBrackets = 1;
|
|
}
|
|
else if (match[4] === ']' && count.regExpSquareBrackets === 1) {
|
|
count.regExpSquareBrackets = 0;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (match[4] === '[') {
|
|
count.squareBrackets++;
|
|
}
|
|
else if (match[4] === ']' && count.squareBrackets > 0) {
|
|
count.squareBrackets--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (match[5]) {
|
|
// Escape template string (${)
|
|
if (count.simpleString === 0 &&
|
|
count.doubleString === 0 &&
|
|
count.comment === 0 &&
|
|
count.singleLineComment === 0 &&
|
|
count.regExp === 0 &&
|
|
!isEscaped) {
|
|
if (this.templateString[0] === 0) {
|
|
this.templateString[0] = 1;
|
|
count.curlyBraces++;
|
|
}
|
|
}
|
|
}
|
|
else if (match[6]) {
|
|
// Template string
|
|
if (count.simpleString === 0 &&
|
|
count.doubleString === 0 &&
|
|
count.comment === 0 &&
|
|
count.singleLineComment === 0 &&
|
|
count.regExp === 0 &&
|
|
!isEscaped) {
|
|
if (this.templateString?.[0] === 0) {
|
|
this.templateString.shift();
|
|
}
|
|
else {
|
|
this.templateString.unshift(0);
|
|
}
|
|
}
|
|
}
|
|
else if (match[7]) {
|
|
// String apostrophe (')
|
|
if (count.doubleString === 0 &&
|
|
count.comment === 0 &&
|
|
count.singleLineComment === 0 &&
|
|
count.regExp === 0 &&
|
|
!isEscaped &&
|
|
(this.templateString.length === 0 || this.templateString[0] > 0)) {
|
|
if (count.simpleString === 0) {
|
|
count.simpleString = 1;
|
|
}
|
|
else {
|
|
count.simpleString = 0;
|
|
}
|
|
}
|
|
}
|
|
else if (match[8]) {
|
|
// String apostrophe (")
|
|
if (count.simpleString === 0 &&
|
|
count.comment === 0 &&
|
|
count.singleLineComment === 0 &&
|
|
count.regExp === 0 &&
|
|
!isEscaped &&
|
|
(this.templateString.length === 0 || this.templateString[0] > 0)) {
|
|
if (count.doubleString === 0) {
|
|
count.doubleString = 1;
|
|
}
|
|
else {
|
|
count.doubleString = 0;
|
|
}
|
|
}
|
|
}
|
|
else if (match[9]) {
|
|
// Line feed character
|
|
count.singleLineComment = 0;
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Get the non-space preceding token.
|
|
*
|
|
* @param code Code.
|
|
* @param index Index.
|
|
* @returns Non-space preceding token.
|
|
*/
|
|
getNonSpacePrecedingToken(code, index) {
|
|
index--;
|
|
let nonSpacePrecedingToken = code[index];
|
|
while (nonSpacePrecedingToken === ' ' ||
|
|
nonSpacePrecedingToken === '\n' ||
|
|
nonSpacePrecedingToken === '\t') {
|
|
index--;
|
|
nonSpacePrecedingToken = code[index];
|
|
}
|
|
return nonSpacePrecedingToken || ';';
|
|
}
|
|
}
|
|
//# sourceMappingURL=JavaScriptCompiler.js.map
|