deno-musings/junk/module_inspector.js

132 lines
4.3 KiB
JavaScript
Raw Normal View History

2024-10-11 13:04:47 -04:00
/**
* @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<string>} 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);
};