astro-ghostcms/.pnpm-store/v3/files/ad/17b8ae5f00662ef6a893b5f0122...

272 lines
9.3 KiB
Plaintext
Raw Normal View History

2024-02-14 19:45:06 +00:00
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.extractScriptTags = void 0;
const utils_1 = require("@astrojs/compiler/utils");
const language_core_1 = require("@volar/language-core");
function extractScriptTags(snapshot, htmlDocument, ast) {
const embeddedJSCodes = findModuleScripts(snapshot, htmlDocument.roots);
const javascriptContexts = [
...findClassicScripts(htmlDocument, snapshot),
...findEventAttributes(ast),
].sort((a, b) => a.startOffset - b.startOffset);
if (javascriptContexts.length > 0) {
// classic scripts share the same scope
// merging them brings about redeclaration errors
embeddedJSCodes.push(mergeJSContexts(javascriptContexts));
}
return embeddedJSCodes;
}
exports.extractScriptTags = extractScriptTags;
function getScriptType(scriptTag) {
// script tags without attributes are processed and converted into module scripts
if (!scriptTag.attributes || Object.entries(scriptTag.attributes).length === 0)
return 'processed module';
// even when it is not processed by vite, scripts with type=module remain modules
if (scriptTag.attributes['type']?.includes('module') === true)
return 'module';
// whenever there are attributes, is:inline is implied and in the absence of type=module, the script is classic
return 'classic';
}
/**
* Get all the isolated scripts in the HTML document
* Isolated scripts are scripts that are hoisted by Astro and as such, are isolated from the rest of the code because of the implicit `type="module"`
* All the isolated scripts are passed to the TypeScript language server as separate `.mts` files.
*/
function findModuleScripts(snapshot, roots) {
const embeddedScripts = [];
let scriptIndex = 0;
getEmbeddedScriptsInNodes(roots);
function getEmbeddedScriptsInNodes(nodes) {
for (const [_, node] of nodes.entries()) {
if (node.tag === 'script' &&
node.startTagEnd !== undefined &&
node.endTagStart !== undefined &&
getScriptType(node) !== 'classic') {
const scriptText = snapshot.getText(node.startTagEnd, node.endTagStart);
const extension = getScriptType(node) === 'processed module' ? 'mts' : 'mjs';
const languageId = getScriptType(node) === 'processed module' ? 'typescript' : 'javascript';
embeddedScripts.push({
id: `${scriptIndex}.${extension}`,
languageId: languageId,
snapshot: {
getText: (start, end) => scriptText.substring(start, end),
getLength: () => scriptText.length,
getChangeRange: () => undefined,
},
mappings: [
{
sourceOffsets: [node.startTagEnd],
generatedOffsets: [0],
lengths: [scriptText.length],
data: {
verification: true,
completion: true,
semantic: true,
navigation: true,
structure: true,
format: false,
},
},
],
embeddedCodes: [],
});
scriptIndex++;
}
if (node.children)
getEmbeddedScriptsInNodes(node.children);
}
}
return embeddedScripts;
}
/**
* Get all the inline scripts in the HTML document
* Inline scripts are scripts that are not hoisted by Astro and as such, are not isolated from the rest of the code.
* All the inline scripts are concatenated into a single `.mjs` file and passed to the TypeScript language server.
*/
function findClassicScripts(htmlDocument, snapshot) {
const inlineScripts = [];
getInlineScriptsInNodes(htmlDocument.roots);
function getInlineScriptsInNodes(nodes) {
for (const [_, node] of nodes.entries()) {
if (node.tag === 'script' &&
node.startTagEnd !== undefined &&
node.endTagStart !== undefined &&
!isJSON(node.attributes?.type) &&
getScriptType(node) === 'classic') {
const scriptText = snapshot.getText(node.startTagEnd, node.endTagStart);
inlineScripts.push({
startOffset: node.startTagEnd,
content: scriptText,
});
}
if (node.children)
getInlineScriptsInNodes(node.children);
}
}
return inlineScripts;
}
/**
* Include both MIME JSON types and `importmap` and `speculationrules` script types
* See MIME Types -> https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
* See Script Types -> https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type
*/
const JSON_TYPES = ['application/json', 'application/ld+json', 'importmap', 'speculationrules'];
/**
* Check if the script has a type, and if it's included in JSON_TYPES above.
* @param type Found in the `type` attribute of the script tag
*/
function isJSON(type) {
if (!type)
return false;
// HTML attributes are quoted, slice " and ' at the start and end of the string
return JSON_TYPES.includes(type.slice(1, -1));
}
function findEventAttributes(ast) {
const eventAttrs = [];
// `@astrojs/compiler`'s `walk` method is async, so we can't use it here. Arf
function walkDown(parent) {
if (!parent.children)
return;
parent.children.forEach((child) => {
if (utils_1.is.element(child)) {
const eventAttribute = child.attributes.find((attr) => htmlEventAttributes.includes(attr.name) && attr.kind === 'quoted');
if (eventAttribute && eventAttribute.position) {
eventAttrs.push({
// Add a semicolon to the end of the event attribute to attempt to prevent errors from spreading to the rest of the document
// This is not perfect, but it's better than nothing
// See: https://github.com/microsoft/vscode/blob/e8e04769ec817a3374c3eaa26a08d3ae491820d5/extensions/html-language-features/server/src/modes/embeddedSupport.ts#L192
content: eventAttribute.value + ';',
startOffset: eventAttribute.position.start.offset + `${eventAttribute.name}="`.length,
});
}
}
if (utils_1.is.parent(child)) {
walkDown(child);
}
});
}
walkDown(ast);
return eventAttrs;
}
/**
* Merge all the inline and non-hoisted scripts into a single `.mjs` file
*/
function mergeJSContexts(javascriptContexts) {
const codes = [];
for (const javascriptContext of javascriptContexts) {
codes.push([
javascriptContext.content,
undefined,
javascriptContext.startOffset,
{
verification: true,
completion: true,
semantic: true,
navigation: true,
structure: true,
format: false,
},
]);
}
const mappings = (0, language_core_1.buildMappings)(codes);
const text = (0, language_core_1.toString)(codes);
return {
id: 'inline.mjs',
languageId: 'javascript',
snapshot: {
getText: (start, end) => text.substring(start, end),
getLength: () => text.length,
getChangeRange: () => undefined,
},
embeddedCodes: [],
mappings,
};
}
const htmlEventAttributes = [
'onabort',
'onafterprint',
'onauxclick',
'onbeforematch',
'onbeforeprint',
'onbeforeunload',
'onblur',
'oncancel',
'oncanplay',
'oncanplaythrough',
'onchange',
'onclick',
'onclose',
'oncontextlost',
'oncontextmenu',
'oncontextrestored',
'oncopy',
'oncuechange',
'oncut',
'ondblclick',
'ondrag',
'ondragend',
'ondragenter',
'ondragleave',
'ondragover',
'ondragstart',
'ondrop',
'ondurationchange',
'onemptied',
'onended',
'onerror',
'onfocus',
'onformdata',
'onhashchange',
'oninput',
'oninvalid',
'onkeydown',
'onkeypress',
'onkeyup',
'onlanguagechange',
'onload',
'onloadeddata',
'onloadedmetadata',
'onloadstart',
'onmessage',
'onmessageerror',
'onmousedown',
'onmouseenter',
'onmouseleave',
'onmousemove',
'onmouseout',
'onmouseover',
'onmouseup',
'onoffline',
'ononline',
'onpagehide',
'onpageshow',
'onpaste',
'onpause',
'onplay',
'onplaying',
'onpopstate',
'onprogress',
'onratechange',
'onrejectionhandled',
'onreset',
'onresize',
'onscroll',
'onscrollend',
'onsecuritypolicyviolation',
'onseeked',
'onseeking',
'onselect',
'onslotchange',
'onstalled',
'onstorage',
'onsubmit',
'onsuspend',
'ontimeupdate',
'ontoggle',
'onunhandledrejection',
'onunload',
'onvolumechange',
'onwaiting',
'onwheel',
];
//# sourceMappingURL=parseJS.js.map