/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ (function (factory) { if (typeof module === "object" && typeof module.exports === "object") { var v = factory(require, exports); if (v !== undefined) module.exports = v; } else if (typeof define === "function" && define.amd) { define(["require", "exports", "../parser/htmlScanner", "../utils/strings", "vscode-uri", "../htmlLanguageTypes"], factory); } })(function (require, exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.HTMLDocumentLinks = void 0; const htmlScanner_1 = require("../parser/htmlScanner"); const strings = require("../utils/strings"); const vscode_uri_1 = require("vscode-uri"); const htmlLanguageTypes_1 = require("../htmlLanguageTypes"); function normalizeRef(url) { const first = url[0]; const last = url[url.length - 1]; if (first === last && (first === '\'' || first === '\"')) { url = url.substring(1, url.length - 1); } return url; } function validateRef(url, languageId) { if (!url.length) { return false; } if (languageId === 'handlebars' && /{{|}}/.test(url)) { return false; } return /\b(w[\w\d+.-]*:\/\/)?[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|\/?))/.test(url); } function getWorkspaceUrl(documentUri, tokenContent, documentContext, base) { if (/^\s*javascript\:/i.test(tokenContent) || /[\n\r]/.test(tokenContent)) { return undefined; } tokenContent = tokenContent.replace(/^\s*/g, ''); const match = tokenContent.match(/^(\w[\w\d+.-]*):/); if (match) { // Absolute link that needs no treatment const schema = match[1].toLowerCase(); if (schema === 'http' || schema === 'https' || schema === 'file') { return tokenContent; } return undefined; } if (/^\#/i.test(tokenContent)) { return documentUri + tokenContent; } if (/^\/\//i.test(tokenContent)) { // Absolute link (that does not name the protocol) const pickedScheme = strings.startsWith(documentUri, 'https://') ? 'https' : 'http'; return pickedScheme + ':' + tokenContent.replace(/^\s*/g, ''); } if (documentContext) { return documentContext.resolveReference(tokenContent, base || documentUri); } return tokenContent; } function createLink(document, documentContext, attributeValue, startOffset, endOffset, base) { const tokenContent = normalizeRef(attributeValue); if (!validateRef(tokenContent, document.languageId)) { return undefined; } if (tokenContent.length < attributeValue.length) { startOffset++; endOffset--; } const workspaceUrl = getWorkspaceUrl(document.uri, tokenContent, documentContext, base); if (!workspaceUrl || !isValidURI(workspaceUrl)) { return undefined; } return { range: htmlLanguageTypes_1.Range.create(document.positionAt(startOffset), document.positionAt(endOffset)), target: workspaceUrl }; } function isValidURI(uri) { try { vscode_uri_1.URI.parse(uri); return true; } catch (e) { return false; } } class HTMLDocumentLinks { constructor(dataManager) { this.dataManager = dataManager; } findDocumentLinks(document, documentContext) { const newLinks = []; const scanner = (0, htmlScanner_1.createScanner)(document.getText(), 0); let token = scanner.scan(); let lastAttributeName = undefined; let lastTagName = undefined; let afterBase = false; let base = void 0; const idLocations = {}; while (token !== htmlLanguageTypes_1.TokenType.EOS) { switch (token) { case htmlLanguageTypes_1.TokenType.StartTag: lastTagName = scanner.getTokenText().toLowerCase(); if (!base) { afterBase = lastTagName === 'base'; } break; case htmlLanguageTypes_1.TokenType.AttributeName: lastAttributeName = scanner.getTokenText().toLowerCase(); break; case htmlLanguageTypes_1.TokenType.AttributeValue: if (lastTagName && lastAttributeName && this.dataManager.isPathAttribute(lastTagName, lastAttributeName)) { const attributeValue = scanner.getTokenText(); if (!afterBase) { // don't highlight the base link itself const link = createLink(document, documentContext, attributeValue, scanner.getTokenOffset(), scanner.getTokenEnd(), base); if (link) { newLinks.push(link); } } if (afterBase && typeof base === 'undefined') { base = normalizeRef(attributeValue); if (base && documentContext) { base = documentContext.resolveReference(base, document.uri); } } afterBase = false; lastAttributeName = undefined; } else if (lastAttributeName === 'id') { const id = normalizeRef(scanner.getTokenText()); idLocations[id] = scanner.getTokenOffset(); } break; } token = scanner.scan(); } // change local links with ids to actual positions for (const link of newLinks) { const localWithHash = document.uri + '#'; if (link.target && strings.startsWith(link.target, localWithHash)) { const target = link.target.substring(localWithHash.length); const offset = idLocations[target]; if (offset !== undefined) { const pos = document.positionAt(offset); link.target = `${localWithHash}${pos.line + 1},${pos.character + 1}`; } } } return newLinks; } } exports.HTMLDocumentLinks = HTMLDocumentLinks; });