/** * @typedef {function(string): boolean} GlyphCheck */ /** * @type {GlyphCheck} */ const isAlphaLike = (inGlyph) => { const inCode = inGlyph.charCodeAt(0); if (inCode >= 97 && inCode <= 122) { return true; } if (inCode >= 65 && inCode <= 90) { return true; } return `$_.`.includes(inGlyph); }; /** * @type {GlyphCheck} */ const isWhiteSpace = (inGlyph) => `\n\r\t `.includes(inGlyph); /** * @type {GlyphCheck} */ const isQuote = (inGlyph) => `"'\``.includes(inGlyph); /** * @param {GlyphCheck} inCheck * @returns {GlyphCheck} */ const isNot = (inCheck) => (inGlyph) => !inCheck(inGlyph); /** * @param {string} inText * @param {number} inStart * @param {GlyphCheck} inTest * @returns {number} */ const contiguous = (inText, inStart, inTest) => { let ok = true; let index = inStart; let count = 0; while (ok && count < inText.length) { count++; ok = inTest(inText.charAt(index++)); } return index - 1; }; /** * @param {string} inFile * @param {number} [inIndex=0] * @param {Array<{internal: string, external: string}>} inLocal * @param {Array} inForeign * @returns {number|boolean} */ const findNextExport = (inFile, inIndex = 0, inLocal, inForeign) => { const pos = inFile.indexOf("export", inIndex); if (pos !== -1) { if (!isAlphaLike(inFile.charAt(pos - 1)) || !isAlphaLike(inFile.charAt(pos + 6))) { const nextCharInd = contiguous(inFile, pos + 6, isWhiteSpace); const nextChar = inFile[nextCharInd]; if (nextChar === "*") { const firstQuoteInd = contiguous(inFile, nextCharInd + 1, isNot(isQuote)); const secondQuoteInd = contiguous(inFile, firstQuoteInd + 1, isNot(isQuote)); inForeign.push(inFile.substring(nextCharInd, secondQuoteInd + 1)); } else if (nextChar == "{") { const endBracketInd = contiguous(inFile, nextCharInd, (inGlyph) => inGlyph !== "}"); const nextLetterInd = contiguous(inFile, endBracketInd + 1, isWhiteSpace); if (inFile.substring(nextLetterInd, nextLetterInd + 4) == "from") { const firstQuoteInd = contiguous(inFile, nextLetterInd + 4, isNot(isQuote)); const secondQuoteInd = contiguous(inFile, firstQuoteInd + 1, isNot(isQuote)); inForeign.push(inFile.substring(nextCharInd, secondQuoteInd + 1)); } else { const members = inFile.substring(nextCharInd + 1, endBracketInd).replaceAll(" as ", "|||").replace(/\s/g, ''); members.split(",").forEach(part => { const renamed = part.split("|||"); inLocal.push({ internal: renamed, external: renamed || renamed }); }); } } else if (isAlphaLike(nextChar)) { const keywordEndInd = contiguous(inFile, nextCharInd, isAlphaLike); const keyword = inFile.substring(nextCharInd, keywordEndInd); if (keyword === "default") { inLocal.push({ internal: keyword, external: keyword }); } else if (["const", "let", "var", "function", "class"].includes(keyword)) { const varStartInd = contiguous(inFile, keywordEndInd + 1, isWhiteSpace); const varEndInd = contiguous(inFile, varStartInd + 1, isAlphaLike); const keyword = inFile.substring(varStartInd, varEndInd); inLocal.push({ internal: keyword, external: keyword }); } } } return pos + 7; } else { return false; } }; /** * @param {string} inFile * @returns {[local: string[], foreign: string[]]} */ export const Exports = (inFile) => { let match = /** @type {number|boolean} */ (0); let count = 0; const local = /** @type {string[]} */ ([]); const foreign = /** @type {string[]} */ ([]); while (match !== false && count < 200) { count++; match = findNextExport(inFile, match, local, foreign); } return [local, foreign]; }; /** * @param {string|URL} inURL * @returns {Promise<[local: string[], foreign: string[]]>} */ export const FileExports = async (inURL) => { const resp = await fetch(inURL); const text = await resp.text(); return Exports(text); };