mirror of
https://github.com/Sevichecc/Urara-Blog.git
synced 2025-05-02 03:39:30 +08:00
1057 lines
35 KiB
Text
1057 lines
35 KiB
Text
import { createSystem, createFSBackedSystem, createVirtualTypeScriptEnvironment } from '@typescript/vfs';
|
|
|
|
function _extends() {
|
|
_extends = Object.assign || function (target) {
|
|
for (var i = 1; i < arguments.length; i++) {
|
|
var source = arguments[i];
|
|
|
|
for (var key in source) {
|
|
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
target[key] = source[key];
|
|
}
|
|
}
|
|
}
|
|
|
|
return target;
|
|
};
|
|
|
|
return _extends.apply(this, arguments);
|
|
}
|
|
|
|
function _inheritsLoose(subClass, superClass) {
|
|
subClass.prototype = Object.create(superClass.prototype);
|
|
subClass.prototype.constructor = subClass;
|
|
|
|
_setPrototypeOf(subClass, superClass);
|
|
}
|
|
|
|
function _getPrototypeOf(o) {
|
|
_getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
|
|
return o.__proto__ || Object.getPrototypeOf(o);
|
|
};
|
|
return _getPrototypeOf(o);
|
|
}
|
|
|
|
function _setPrototypeOf(o, p) {
|
|
_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
|
|
o.__proto__ = p;
|
|
return o;
|
|
};
|
|
|
|
return _setPrototypeOf(o, p);
|
|
}
|
|
|
|
function _isNativeReflectConstruct() {
|
|
if (typeof Reflect === "undefined" || !Reflect.construct) return false;
|
|
if (Reflect.construct.sham) return false;
|
|
if (typeof Proxy === "function") return true;
|
|
|
|
try {
|
|
Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
|
|
return true;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function _construct(Parent, args, Class) {
|
|
if (_isNativeReflectConstruct()) {
|
|
_construct = Reflect.construct;
|
|
} else {
|
|
_construct = function _construct(Parent, args, Class) {
|
|
var a = [null];
|
|
a.push.apply(a, args);
|
|
var Constructor = Function.bind.apply(Parent, a);
|
|
var instance = new Constructor();
|
|
if (Class) _setPrototypeOf(instance, Class.prototype);
|
|
return instance;
|
|
};
|
|
}
|
|
|
|
return _construct.apply(null, arguments);
|
|
}
|
|
|
|
function _isNativeFunction(fn) {
|
|
return Function.toString.call(fn).indexOf("[native code]") !== -1;
|
|
}
|
|
|
|
function _wrapNativeSuper(Class) {
|
|
var _cache = typeof Map === "function" ? new Map() : undefined;
|
|
|
|
_wrapNativeSuper = function _wrapNativeSuper(Class) {
|
|
if (Class === null || !_isNativeFunction(Class)) return Class;
|
|
|
|
if (typeof Class !== "function") {
|
|
throw new TypeError("Super expression must either be null or a function");
|
|
}
|
|
|
|
if (typeof _cache !== "undefined") {
|
|
if (_cache.has(Class)) return _cache.get(Class);
|
|
|
|
_cache.set(Class, Wrapper);
|
|
}
|
|
|
|
function Wrapper() {
|
|
return _construct(Class, arguments, _getPrototypeOf(this).constructor);
|
|
}
|
|
|
|
Wrapper.prototype = Object.create(Class.prototype, {
|
|
constructor: {
|
|
value: Wrapper,
|
|
enumerable: false,
|
|
writable: true,
|
|
configurable: true
|
|
}
|
|
});
|
|
return _setPrototypeOf(Wrapper, Class);
|
|
};
|
|
|
|
return _wrapNativeSuper(Class);
|
|
}
|
|
|
|
function _unsupportedIterableToArray(o, minLen) {
|
|
if (!o) return;
|
|
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
|
|
var n = Object.prototype.toString.call(o).slice(8, -1);
|
|
if (n === "Object" && o.constructor) n = o.constructor.name;
|
|
if (n === "Map" || n === "Set") return Array.from(o);
|
|
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
|
|
}
|
|
|
|
function _arrayLikeToArray(arr, len) {
|
|
if (len == null || len > arr.length) len = arr.length;
|
|
|
|
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
|
|
|
|
return arr2;
|
|
}
|
|
|
|
function _createForOfIteratorHelperLoose(o, allowArrayLike) {
|
|
var it;
|
|
|
|
if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) {
|
|
if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
|
|
if (it) o = it;
|
|
var i = 0;
|
|
return function () {
|
|
if (i >= o.length) return {
|
|
done: true
|
|
};
|
|
return {
|
|
done: false,
|
|
value: o[i++]
|
|
};
|
|
};
|
|
}
|
|
|
|
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
}
|
|
|
|
it = o[Symbol.iterator]();
|
|
return it.next.bind(it);
|
|
}
|
|
|
|
function parsePrimitive(value, type) {
|
|
switch (type) {
|
|
case "number":
|
|
return +value;
|
|
|
|
case "string":
|
|
return value;
|
|
|
|
case "boolean":
|
|
return value.toLowerCase() === "true" || value.length === 0;
|
|
}
|
|
|
|
throw new TwoslashError("Unknown primitive value in compiler flag", "The only recognized primitives are number, string and boolean. Got " + type + " with " + value + ".", "This is likely a typo.");
|
|
}
|
|
function cleanMarkdownEscaped(code) {
|
|
code = code.replace(/¨D/g, "$");
|
|
code = code.replace(/¨T/g, "~");
|
|
return code;
|
|
}
|
|
function typesToExtension(types) {
|
|
var map = {
|
|
js: "js",
|
|
javascript: "js",
|
|
ts: "ts",
|
|
typescript: "ts",
|
|
tsx: "tsx",
|
|
jsx: "jsx",
|
|
json: "json",
|
|
jsn: "json"
|
|
};
|
|
if (map[types]) return map[types];
|
|
throw new TwoslashError("Unknown TypeScript extension given to Twoslash", "Received " + types + " but Twoslash only accepts: " + Object.keys(map) + " ", "");
|
|
}
|
|
function getIdentifierTextSpans(ts, sourceFile) {
|
|
var textSpans = [];
|
|
checkChildren(sourceFile);
|
|
return textSpans;
|
|
|
|
function checkChildren(node) {
|
|
ts.forEachChild(node, function (child) {
|
|
if (ts.isIdentifier(child)) {
|
|
var start = child.getStart(sourceFile, false);
|
|
textSpans.push({
|
|
span: ts.createTextSpan(start, child.end - start),
|
|
text: child.getText(sourceFile)
|
|
});
|
|
}
|
|
|
|
checkChildren(child);
|
|
});
|
|
}
|
|
}
|
|
/** Came from https://ourcodeworld.com/articles/read/223/how-to-retrieve-the-closest-word-in-a-string-with-a-given-index-in-javascript */
|
|
|
|
function getClosestWord(str, pos) {
|
|
// Make copies
|
|
str = String(str);
|
|
pos = Number(pos) >>> 0; // Search for the word's beginning and end.
|
|
|
|
var left = str.slice(0, pos + 1).search(/\S+$/),
|
|
right = str.slice(pos).search(/\s/); // The last word in the string is a special case.
|
|
|
|
if (right < 0) {
|
|
return {
|
|
word: str.slice(left),
|
|
startPos: left
|
|
};
|
|
} // Return the word, using the located bounds to extract it from the string.
|
|
|
|
|
|
return {
|
|
word: str.slice(left, right + pos),
|
|
startPos: left
|
|
};
|
|
}
|
|
|
|
/** To ensure that errors are matched up right */
|
|
|
|
function validateCodeForErrors(relevantErrors, handbookOptions, extension, originalCode, vfsRoot) {
|
|
var inErrsButNotFoundInTheHeader = relevantErrors.filter(function (e) {
|
|
return !handbookOptions.errors.includes(e.code);
|
|
});
|
|
var errorsFound = Array.from(new Set(inErrsButNotFoundInTheHeader.map(function (e) {
|
|
return e.code;
|
|
}))).join(" ");
|
|
|
|
if (inErrsButNotFoundInTheHeader.length) {
|
|
var errorsToShow = new Set(relevantErrors.map(function (e) {
|
|
return e.code;
|
|
}));
|
|
var codeToAdd = "// @errors: " + Array.from(errorsToShow).join(" ");
|
|
var missing = handbookOptions.errors.length ? "\nThe existing annotation specified " + handbookOptions.errors.join(" ") : "\nExpected: " + codeToAdd; // These get filled by below
|
|
|
|
var filesToErrors = {};
|
|
var noFiles = [];
|
|
inErrsButNotFoundInTheHeader.forEach(function (d) {
|
|
var _d$file;
|
|
|
|
var fileRef = ((_d$file = d.file) == null ? void 0 : _d$file.fileName) && d.file.fileName.replace(vfsRoot, "");
|
|
if (!fileRef) noFiles.push(d);else {
|
|
var existing = filesToErrors[fileRef];
|
|
if (existing) existing.push(d);else filesToErrors[fileRef] = [d];
|
|
}
|
|
});
|
|
|
|
var showDiagnostics = function showDiagnostics(title, diags) {
|
|
return title + "\n " + diags.map(function (e) {
|
|
var msg = typeof e.messageText === "string" ? e.messageText : e.messageText.messageText;
|
|
return "[" + e.code + "] " + e.start + " - " + msg;
|
|
}).join("\n ");
|
|
};
|
|
|
|
var innerDiags = [];
|
|
|
|
if (noFiles.length) {
|
|
innerDiags.push(showDiagnostics("Ambient Errors", noFiles));
|
|
}
|
|
|
|
Object.keys(filesToErrors).forEach(function (filepath) {
|
|
innerDiags.push(showDiagnostics(filepath, filesToErrors[filepath]));
|
|
});
|
|
var allMessages = innerDiags.join("\n\n");
|
|
var newErr = new TwoslashError("Errors were thrown in the sample, but not included in an errors tag", "These errors were not marked as being expected: " + errorsFound + ". " + missing, "Compiler Errors:\n\n" + allMessages);
|
|
newErr.code = "## Code\n\n'''" + extension + "\n" + originalCode + "\n'''";
|
|
throw newErr;
|
|
}
|
|
}
|
|
/** Mainly to warn myself, I've lost a good few minutes to this before */
|
|
|
|
function validateInput(code) {
|
|
if (code.includes("// @errors ")) {
|
|
throw new TwoslashError("You have '// @errors ' (with a space)", "You want '// @errors: ' (with a colon)", "This is a pretty common typo");
|
|
}
|
|
|
|
if (code.includes("// @filename ")) {
|
|
throw new TwoslashError("You have '// @filename ' (with a space)", "You want '// @filename: ' (with a colon)", "This is a pretty common typo");
|
|
}
|
|
}
|
|
|
|
var hasLocalStorage = false;
|
|
|
|
try {
|
|
hasLocalStorage = typeof localStorage !== "undefined";
|
|
} catch (error) {}
|
|
|
|
var hasProcess = typeof process !== "undefined";
|
|
var shouldDebug = hasLocalStorage && /*#__PURE__*/localStorage.getItem("DEBUG") || hasProcess && process.env.DEBUG;
|
|
var log = shouldDebug ? console.log : function (_message) {
|
|
return "";
|
|
};
|
|
var TwoslashError = /*#__PURE__*/function (_Error) {
|
|
_inheritsLoose(TwoslashError, _Error);
|
|
|
|
function TwoslashError(title, description, recommendation, code) {
|
|
var _this;
|
|
|
|
var message = "\n## " + title + "\n\n" + description + "\n";
|
|
|
|
if (recommendation) {
|
|
message += "\n" + recommendation;
|
|
}
|
|
|
|
if (code) {
|
|
message += "\n" + code;
|
|
}
|
|
|
|
_this = _Error.call(this, message) || this;
|
|
_this.title = void 0;
|
|
_this.description = void 0;
|
|
_this.recommendation = void 0;
|
|
_this.code = void 0;
|
|
_this.title = title;
|
|
_this.description = description;
|
|
_this.recommendation = recommendation;
|
|
_this.code = code;
|
|
return _this;
|
|
}
|
|
|
|
return TwoslashError;
|
|
}( /*#__PURE__*/_wrapNativeSuper(Error));
|
|
|
|
function filterHighlightLines(codeLines) {
|
|
var highlights = [];
|
|
var queries = [];
|
|
var nextContentOffset = 0;
|
|
var contentOffset = 0;
|
|
var removedLines = 0;
|
|
|
|
var _loop = function _loop(_i) {
|
|
var line = codeLines[_i];
|
|
|
|
var moveForward = function moveForward() {
|
|
contentOffset = nextContentOffset;
|
|
nextContentOffset += line.length + 1;
|
|
};
|
|
|
|
var stripLine = function stripLine(logDesc) {
|
|
log("Removing line " + _i + " for " + logDesc);
|
|
removedLines++;
|
|
codeLines.splice(_i, 1);
|
|
_i--;
|
|
}; // We only need to run regexes over lines with comments
|
|
|
|
|
|
if (!line.includes("//")) {
|
|
moveForward();
|
|
} else {
|
|
var highlightMatch = /^\s*\/\/\s*\^+( .+)?$/.exec(line);
|
|
var queryMatch = /^\s*\/\/\s*\^\?\s*$/.exec(line); // https://regex101.com/r/2yDsRk/1
|
|
|
|
var removePrettierIgnoreMatch = /^\s*\/\/ prettier-ignore$/.exec(line);
|
|
var completionsQuery = /^\s*\/\/\s*\^\|$/.exec(line);
|
|
|
|
if (queryMatch !== null) {
|
|
var start = line.indexOf("^");
|
|
queries.push({
|
|
kind: "query",
|
|
offset: start,
|
|
text: undefined,
|
|
docs: undefined,
|
|
line: _i + removedLines - 1
|
|
});
|
|
stripLine("having a query");
|
|
} else if (highlightMatch !== null) {
|
|
var _start = line.indexOf("^");
|
|
|
|
var length = line.lastIndexOf("^") - _start + 1;
|
|
var description = highlightMatch[1] ? highlightMatch[1].trim() : "";
|
|
highlights.push({
|
|
kind: "highlight",
|
|
offset: _start + contentOffset,
|
|
length: length,
|
|
text: description,
|
|
line: _i + removedLines - 1,
|
|
start: _start
|
|
});
|
|
stripLine("having a highlight");
|
|
} else if (removePrettierIgnoreMatch !== null) {
|
|
stripLine("being a prettier ignore");
|
|
} else if (completionsQuery !== null) {
|
|
var _start2 = line.indexOf("^"); // prettier-ignore
|
|
|
|
|
|
queries.push({
|
|
kind: "completion",
|
|
offset: _start2,
|
|
text: undefined,
|
|
docs: undefined,
|
|
line: _i + removedLines - 1
|
|
});
|
|
stripLine("having a completion query");
|
|
} else {
|
|
moveForward();
|
|
}
|
|
}
|
|
|
|
i = _i;
|
|
};
|
|
|
|
for (var i = 0; i < codeLines.length; i++) {
|
|
_loop(i);
|
|
}
|
|
|
|
return {
|
|
highlights: highlights,
|
|
queries: queries
|
|
};
|
|
}
|
|
|
|
function getOptionValueFromMap(name, key, optMap) {
|
|
var result = optMap.get(key.toLowerCase());
|
|
log("Get " + name + " mapped option: " + key + " => " + result);
|
|
|
|
if (result === undefined) {
|
|
var keys = Array.from(optMap.keys());
|
|
throw new TwoslashError("Invalid inline compiler value", "Got " + key + " for " + name + " but it is not a supported value by the TS compiler.", "Allowed values: " + keys.join(","));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function setOption(name, value, opts, ts) {
|
|
log("Setting " + name + " to " + value);
|
|
|
|
var _loop2 = function _loop2() {
|
|
var opt = _step.value;
|
|
|
|
if (opt.name.toLowerCase() === name.toLowerCase()) {
|
|
switch (opt.type) {
|
|
case "number":
|
|
case "string":
|
|
case "boolean":
|
|
opts[opt.name] = parsePrimitive(value, opt.type);
|
|
break;
|
|
|
|
case "list":
|
|
var elementType = opt.element.type;
|
|
var strings = value.split(",");
|
|
|
|
if (typeof elementType === "string") {
|
|
opts[opt.name] = strings.map(function (v) {
|
|
return parsePrimitive(v, elementType);
|
|
});
|
|
} else {
|
|
opts[opt.name] = strings.map(function (v) {
|
|
return getOptionValueFromMap(opt.name, v, elementType);
|
|
});
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
// It's a map!
|
|
var optMap = opt.type;
|
|
opts[opt.name] = getOptionValueFromMap(opt.name, value, optMap);
|
|
break;
|
|
}
|
|
|
|
return {
|
|
v: void 0
|
|
};
|
|
}
|
|
};
|
|
|
|
for (var _iterator = _createForOfIteratorHelperLoose(ts.optionDeclarations), _step; !(_step = _iterator()).done;) {
|
|
var _ret = _loop2();
|
|
|
|
if (typeof _ret === "object") return _ret.v;
|
|
}
|
|
|
|
throw new TwoslashError("Invalid inline compiler flag", "There isn't a TypeScript compiler flag called '" + name + "'.", "This is likely a typo, you can check all the compiler flags in the TSConfig reference, or check the additional Twoslash flags in the npm page for @typescript/twoslash.");
|
|
}
|
|
|
|
var booleanConfigRegexp = /^\/\/\s?@(\w+)$/; // https://regex101.com/r/8B2Wwh/1
|
|
|
|
var valuedConfigRegexp = /^\/\/\s?@(\w+):\s?(.+)$/;
|
|
|
|
function filterCompilerOptions(codeLines, defaultCompilerOptions, ts) {
|
|
var options = _extends({}, defaultCompilerOptions);
|
|
|
|
for (var _i2 = 0; _i2 < codeLines.length;) {
|
|
var match = void 0;
|
|
|
|
if (match = booleanConfigRegexp.exec(codeLines[_i2])) {
|
|
options[match[1]] = true;
|
|
setOption(match[1], "true", options, ts);
|
|
} else if (match = valuedConfigRegexp.exec(codeLines[_i2])) {
|
|
// Skip a filename tag, which should propagate through this stage
|
|
if (match[1] === "filename") {
|
|
_i2++;
|
|
continue;
|
|
}
|
|
|
|
setOption(match[1], match[2], options, ts);
|
|
} else {
|
|
_i2++;
|
|
continue;
|
|
}
|
|
|
|
codeLines.splice(_i2, 1);
|
|
}
|
|
|
|
return options;
|
|
}
|
|
|
|
function filterCustomTags(codeLines, customTags) {
|
|
var tags = [];
|
|
|
|
for (var _i3 = 0; _i3 < codeLines.length;) {
|
|
var match = void 0;
|
|
|
|
if (match = valuedConfigRegexp.exec(codeLines[_i3])) {
|
|
if (customTags.includes(match[1])) {
|
|
tags.push({
|
|
name: match[1],
|
|
line: _i3,
|
|
annotation: codeLines[_i3].split("@" + match[1] + ": ")[1]
|
|
});
|
|
codeLines.splice(_i3, 1);
|
|
}
|
|
}
|
|
|
|
_i3++;
|
|
}
|
|
|
|
return tags;
|
|
} // Keys in this object are used to filter out handbook options
|
|
// before compiler options are set.
|
|
|
|
|
|
var defaultHandbookOptions = {
|
|
errors: [],
|
|
noErrors: false,
|
|
showEmit: false,
|
|
showEmittedFile: undefined,
|
|
noStaticSemanticInfo: false,
|
|
emit: false,
|
|
noErrorValidation: false
|
|
};
|
|
|
|
function filterHandbookOptions(codeLines) {
|
|
var options = _extends({}, defaultHandbookOptions);
|
|
|
|
for (var _i4 = 0; _i4 < codeLines.length; _i4++) {
|
|
var match = void 0;
|
|
|
|
if (match = booleanConfigRegexp.exec(codeLines[_i4])) {
|
|
if (match[1] in options) {
|
|
options[match[1]] = true;
|
|
log("Setting options." + match[1] + " to true");
|
|
codeLines.splice(_i4, 1);
|
|
_i4--;
|
|
}
|
|
} else if (match = valuedConfigRegexp.exec(codeLines[_i4])) {
|
|
if (match[1] in options) {
|
|
options[match[1]] = match[2];
|
|
log("Setting options." + match[1] + " to " + match[2]);
|
|
codeLines.splice(_i4, 1);
|
|
_i4--;
|
|
}
|
|
}
|
|
} // Edge case the errors object to turn it into a string array
|
|
|
|
|
|
if ("errors" in options && typeof options.errors === "string") {
|
|
options.errors = options.errors.split(" ").map(Number);
|
|
log("Setting options.error to ", options.errors);
|
|
}
|
|
|
|
return options;
|
|
}
|
|
/**
|
|
* Runs the checker against a TypeScript/JavaScript code sample returning potentially
|
|
* difference code, and a set of annotations around how it works.
|
|
*
|
|
* @param code The twoslash markup'd code
|
|
* @param extension For example: "ts", "tsx", "typescript", "javascript" or "js".
|
|
* @param options Additional options for twoslash
|
|
*/
|
|
|
|
|
|
function twoslasher(code, extension, options) {
|
|
var _options$tsModule, _options$lzstringModu, _options$defaultCompi;
|
|
|
|
if (options === void 0) {
|
|
options = {};
|
|
}
|
|
|
|
var ts = (_options$tsModule = options.tsModule) != null ? _options$tsModule : require("typescript");
|
|
var lzstring = (_options$lzstringModu = options.lzstringModule) != null ? _options$lzstringModu : require("lz-string");
|
|
var originalCode = code;
|
|
var safeExtension = typesToExtension(extension);
|
|
var defaultFileName = "index." + safeExtension;
|
|
log("\n\nLooking at code: \n```" + safeExtension + "\n" + code + "\n```\n");
|
|
|
|
var defaultCompilerOptions = _extends({
|
|
strict: true,
|
|
target: ts.ScriptTarget.ES2016,
|
|
allowJs: true
|
|
}, (_options$defaultCompi = options.defaultCompilerOptions) != null ? _options$defaultCompi : {});
|
|
|
|
validateInput(code);
|
|
code = cleanMarkdownEscaped(code); // NOTE: codeLines is mutated by the below functions:
|
|
|
|
var codeLines = code.split(/\r\n?|\n/g);
|
|
var tags = options.customTags ? filterCustomTags(codeLines, options.customTags) : [];
|
|
|
|
var handbookOptions = _extends({}, filterHandbookOptions(codeLines), options.defaultOptions);
|
|
|
|
var compilerOptions = filterCompilerOptions(codeLines, defaultCompilerOptions, ts); // Handle special casing the lookup for when using jsx preserve which creates .jsx files
|
|
|
|
if (!handbookOptions.showEmittedFile) {
|
|
handbookOptions.showEmittedFile = compilerOptions.jsx && compilerOptions.jsx === ts.JsxEmit.Preserve ? "index.jsx" : "index.js";
|
|
}
|
|
|
|
var getRoot = function getRoot() {
|
|
var pa = "pa";
|
|
|
|
var path = require(pa + "th");
|
|
|
|
var rootPath = options.vfsRoot || process.cwd();
|
|
return rootPath.split(path.sep).join(path.posix.sep);
|
|
}; // In a browser we want to DI everything, in node we can use local infra
|
|
|
|
|
|
var useFS = !!options.fsMap;
|
|
var vfs = useFS && options.fsMap ? options.fsMap : new Map();
|
|
var system = useFS ? createSystem(vfs) : createFSBackedSystem(vfs, getRoot(), ts);
|
|
var fsRoot = useFS ? "/" : getRoot() + "/";
|
|
var env = createVirtualTypeScriptEnvironment(system, [], ts, compilerOptions, options.customTransformers);
|
|
var ls = env.languageService;
|
|
code = codeLines.join("\n");
|
|
var partialQueries = [];
|
|
var queries = [];
|
|
var highlights = [];
|
|
var nameContent = splitTwoslashCodeInfoFiles(code, defaultFileName, fsRoot);
|
|
var sourceFiles = ["js", "jsx", "ts", "tsx"];
|
|
/** All of the referenced files in the markup */
|
|
|
|
var filenames = nameContent.map(function (nc) {
|
|
return nc[0];
|
|
});
|
|
|
|
var _loop3 = function _loop3() {
|
|
var file = _step2.value;
|
|
var filename = file[0],
|
|
codeLines = file[1];
|
|
var filetype = filename.split(".").pop() || ""; // Only run the LSP-y things on source files
|
|
|
|
var allowJSON = compilerOptions.resolveJsonModule && filetype === "json";
|
|
|
|
if (!sourceFiles.includes(filetype) && !allowJSON) {
|
|
return "continue";
|
|
} // Create the file in the vfs
|
|
|
|
|
|
var newFileCode = codeLines.join("\n");
|
|
env.createFile(filename, newFileCode);
|
|
var updates = filterHighlightLines(codeLines);
|
|
highlights = highlights.concat(updates.highlights); // ------ Do the LSP lookup for the queries
|
|
|
|
var lspedQueries = updates.queries.map(function (q, i) {
|
|
var sourceFile = env.getSourceFile(filename);
|
|
var position = ts.getPositionOfLineAndCharacter(sourceFile, q.line, q.offset);
|
|
|
|
switch (q.kind) {
|
|
case "query":
|
|
{
|
|
var quickInfo = ls.getQuickInfoAtPosition(filename, position); // prettier-ignore
|
|
|
|
var text;
|
|
var docs;
|
|
|
|
if (quickInfo && quickInfo.displayParts) {
|
|
text = quickInfo.displayParts.map(function (dp) {
|
|
return dp.text;
|
|
}).join("");
|
|
docs = quickInfo.documentation ? quickInfo.documentation.map(function (d) {
|
|
return d.text;
|
|
}).join("<br/>") : undefined;
|
|
} else {
|
|
throw new TwoslashError("Invalid QuickInfo query", "The request on line " + q.line + " in " + filename + " for quickinfo via ^? returned no from the compiler.", "This is likely that the x positioning is off.");
|
|
}
|
|
|
|
var queryResult = {
|
|
kind: "query",
|
|
text: text,
|
|
docs: docs,
|
|
line: q.line - i,
|
|
offset: q.offset,
|
|
file: filename
|
|
};
|
|
return queryResult;
|
|
}
|
|
|
|
case "completion":
|
|
{
|
|
var completions = ls.getCompletionsAtPosition(filename, position - 1, {});
|
|
|
|
if (!completions && !handbookOptions.noErrorValidation) {
|
|
throw new TwoslashError("Invalid completion query", "The request on line " + q.line + " in " + filename + " for completions via ^| returned no completions from the compiler.", "This is likely that the positioning is off.");
|
|
}
|
|
|
|
var word = getClosestWord(sourceFile.text, position - 1);
|
|
var prefix = sourceFile.text.slice(word.startPos, position);
|
|
var lastDot = prefix.split(".").pop() || "";
|
|
var _queryResult = {
|
|
kind: "completions",
|
|
completions: (completions == null ? void 0 : completions.entries) || [],
|
|
completionPrefix: lastDot,
|
|
line: q.line - i,
|
|
offset: q.offset,
|
|
file: filename
|
|
};
|
|
return _queryResult;
|
|
}
|
|
}
|
|
});
|
|
partialQueries = partialQueries.concat(lspedQueries); // Sets the file in the compiler as being without the comments
|
|
|
|
var newEditedFileCode = codeLines.join("\n");
|
|
env.updateFile(filename, newEditedFileCode);
|
|
};
|
|
|
|
for (var _iterator2 = _createForOfIteratorHelperLoose(nameContent), _step2; !(_step2 = _iterator2()).done;) {
|
|
var _ret2 = _loop3();
|
|
|
|
if (_ret2 === "continue") continue;
|
|
} // We need to also strip the highlights + queries from the main file which is shown to people
|
|
|
|
|
|
var allCodeLines = code.split(/\r\n?|\n/g);
|
|
filterHighlightLines(allCodeLines);
|
|
code = allCodeLines.join("\n"); // Lets fs changes propagate back up to the fsMap
|
|
|
|
if (handbookOptions.emit) {
|
|
filenames.forEach(function (f) {
|
|
var filetype = f.split(".").pop() || "";
|
|
if (!sourceFiles.includes(filetype)) return;
|
|
var output = ls.getEmitOutput(f);
|
|
output.outputFiles.forEach(function (output) {
|
|
system.writeFile(output.name, output.text);
|
|
});
|
|
});
|
|
} // Code should now be safe to compile, so we're going to split it into different files
|
|
|
|
|
|
var errs = []; // Let because of a filter when cutting
|
|
|
|
var staticQuickInfos = []; // Iterate through the declared files and grab errors and LSP quickinfos
|
|
// const declaredFiles = Object.keys(fileMap)
|
|
|
|
filenames.forEach(function (file) {
|
|
var filetype = file.split(".").pop() || ""; // Only run the LSP-y things on source files
|
|
|
|
if (!sourceFiles.includes(filetype)) {
|
|
return;
|
|
}
|
|
|
|
if (!handbookOptions.noErrors) {
|
|
errs = errs.concat(ls.getSemanticDiagnostics(file), ls.getSyntacticDiagnostics(file));
|
|
}
|
|
|
|
var source = env.sys.readFile(file);
|
|
var sourceFile = env.getSourceFile(file);
|
|
|
|
if (!sourceFile) {
|
|
throw new TwoslashError("Could not find a TypeScript sourcefile for '" + file + "' in the Twoslash vfs", "It's a little hard to provide useful advice on this error. Maybe you imported something which the compiler doesn't think is a source file?", "");
|
|
} // Get all of the interesting quick info popover
|
|
|
|
|
|
if (!handbookOptions.showEmit) {
|
|
var fileContentStartIndexInModifiedFile = code.indexOf(source) == -1 ? 0 : code.indexOf(source);
|
|
var linesAbove = code.slice(0, fileContentStartIndexInModifiedFile).split("\n").length - 1; // Get all interesting identifiers in the file, so we can show hover info for it
|
|
|
|
var identifiers = handbookOptions.noStaticSemanticInfo ? [] : getIdentifierTextSpans(ts, sourceFile);
|
|
|
|
for (var _iterator3 = _createForOfIteratorHelperLoose(identifiers), _step3; !(_step3 = _iterator3()).done;) {
|
|
var identifier = _step3.value;
|
|
var span = identifier.span;
|
|
var quickInfo = ls.getQuickInfoAtPosition(file, span.start);
|
|
|
|
if (quickInfo && quickInfo.displayParts) {
|
|
var text = quickInfo.displayParts.map(function (dp) {
|
|
return dp.text;
|
|
}).join("");
|
|
var targetString = identifier.text;
|
|
var docs = quickInfo.documentation ? quickInfo.documentation.map(function (d) {
|
|
return d.text;
|
|
}).join("\n") : undefined; // Get the position of the
|
|
|
|
var position = span.start + fileContentStartIndexInModifiedFile; // Use TypeScript to pull out line/char from the original code at the position + any previous offset
|
|
|
|
var burnerSourceFile = ts.createSourceFile("_.ts", code, ts.ScriptTarget.ES2015);
|
|
|
|
var _ts$getLineAndCharact = ts.getLineAndCharacterOfPosition(burnerSourceFile, position),
|
|
line = _ts$getLineAndCharact.line,
|
|
character = _ts$getLineAndCharact.character;
|
|
|
|
staticQuickInfos.push({
|
|
text: text,
|
|
docs: docs,
|
|
start: position,
|
|
length: span.length,
|
|
line: line,
|
|
character: character,
|
|
targetString: targetString
|
|
});
|
|
}
|
|
} // Offset the queries for this file because they are based on the line for that one
|
|
// specific file, and not the global twoslash document. This has to be done here because
|
|
// in the above loops, the code for queries/highlights/etc hasn't been stripped yet.
|
|
|
|
|
|
partialQueries.filter(function (q) {
|
|
return q.file === file;
|
|
}).forEach(function (q) {
|
|
var pos = ts.getPositionOfLineAndCharacter(sourceFile, q.line, q.offset) + fileContentStartIndexInModifiedFile;
|
|
|
|
switch (q.kind) {
|
|
case "query":
|
|
{
|
|
queries.push({
|
|
docs: q.docs,
|
|
kind: "query",
|
|
start: pos + fileContentStartIndexInModifiedFile,
|
|
length: q.text.length,
|
|
text: q.text,
|
|
offset: q.offset,
|
|
line: q.line + linesAbove + 1
|
|
});
|
|
break;
|
|
}
|
|
|
|
case "completions":
|
|
{
|
|
queries.push({
|
|
completions: q.completions,
|
|
kind: "completions",
|
|
start: pos + fileContentStartIndexInModifiedFile,
|
|
completionsPrefix: q.completionPrefix,
|
|
length: 1,
|
|
offset: q.offset,
|
|
line: q.line + linesAbove + 1
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
var relevantErrors = errs.filter(function (e) {
|
|
return e.file && filenames.includes(e.file.fileName);
|
|
}); // A validator that error codes are mentioned, so we can know if something has broken in the future
|
|
|
|
if (!handbookOptions.noErrorValidation && relevantErrors.length) {
|
|
validateCodeForErrors(relevantErrors, handbookOptions, extension, originalCode, fsRoot);
|
|
}
|
|
|
|
var errors = []; // We can't pass the ts.DiagnosticResult out directly (it can't be JSON.stringified)
|
|
|
|
for (var _iterator4 = _createForOfIteratorHelperLoose(relevantErrors), _step4; !(_step4 = _iterator4()).done;) {
|
|
var err = _step4.value;
|
|
var codeWhereErrorLives = env.sys.readFile(err.file.fileName);
|
|
var fileContentStartIndexInModifiedFile = code.indexOf(codeWhereErrorLives);
|
|
var renderedMessage = ts.flattenDiagnosticMessageText(err.messageText, "\n");
|
|
var id = "err-" + err.code + "-" + err.start + "-" + err.length;
|
|
|
|
var _ts$getLineAndCharact2 = ts.getLineAndCharacterOfPosition(err.file, err.start),
|
|
line = _ts$getLineAndCharact2.line,
|
|
character = _ts$getLineAndCharact2.character;
|
|
|
|
errors.push({
|
|
category: err.category,
|
|
code: err.code,
|
|
length: err.length,
|
|
start: err.start ? err.start + fileContentStartIndexInModifiedFile : undefined,
|
|
line: line,
|
|
character: character,
|
|
renderedMessage: renderedMessage,
|
|
id: id
|
|
});
|
|
} // Handle emitting files
|
|
|
|
|
|
if (handbookOptions.showEmit) {
|
|
// Get the file which created the file we want to show:
|
|
var emitFilename = handbookOptions.showEmittedFile || defaultFileName;
|
|
var emitSourceFilename = fsRoot + emitFilename.replace(".jsx", "").replace(".js", "").replace(".d.ts", "").replace(".map", "");
|
|
var emitSource = filenames.find(function (f) {
|
|
return f === emitSourceFilename + ".ts" || f === emitSourceFilename + ".tsx";
|
|
});
|
|
|
|
if (!emitSource && !compilerOptions.outFile) {
|
|
var allFiles = filenames.join(", "); // prettier-ignore
|
|
|
|
throw new TwoslashError("Could not find source file to show the emit for", "Cannot find the corresponding **source** file " + emitFilename + " for completions via ^| returned no quickinfo from the compiler.", "Looked for: " + emitSourceFilename + " in the vfs - which contains: " + allFiles);
|
|
} // Allow outfile, in which case you need any file.
|
|
|
|
|
|
if (compilerOptions.outFile) {
|
|
emitSource = filenames[0];
|
|
}
|
|
|
|
var output = ls.getEmitOutput(emitSource);
|
|
var file = output.outputFiles.find(function (o) {
|
|
return o.name === fsRoot + handbookOptions.showEmittedFile || o.name === handbookOptions.showEmittedFile;
|
|
});
|
|
|
|
if (!file) {
|
|
var _allFiles = output.outputFiles.map(function (o) {
|
|
return o.name;
|
|
}).join(", ");
|
|
|
|
throw new TwoslashError("Cannot find the output file in the Twoslash VFS", "Looking for " + handbookOptions.showEmittedFile + " in the Twoslash vfs after compiling", "Looked for\" " + (fsRoot + handbookOptions.showEmittedFile) + " in the vfs - which contains " + _allFiles + ".");
|
|
}
|
|
|
|
code = file.text;
|
|
extension = file.name.split(".").pop(); // Remove highlights and queries, because it won't work across transpiles,
|
|
// though I guess source-mapping could handle the transition
|
|
|
|
highlights = [];
|
|
partialQueries = [];
|
|
staticQuickInfos = [];
|
|
}
|
|
|
|
var zippedCode = lzstring.compressToEncodedURIComponent(originalCode);
|
|
var playgroundURL = "https://www.typescriptlang.org/play/#code/" + zippedCode; // Cutting happens last, and it means editing the lines and character index of all
|
|
// the type annotations which are attached to a location
|
|
|
|
var cutString = "// ---cut---\n";
|
|
|
|
if (code.includes(cutString)) {
|
|
// Get the place it is, then find the end and the start of the next line
|
|
var cutIndex = code.indexOf(cutString) + cutString.length;
|
|
var lineOffset = code.substr(0, cutIndex).split("\n").length - 1; // Kills the code shown
|
|
|
|
code = code.split(cutString).pop(); // For any type of metadata shipped, it will need to be shifted to
|
|
// fit in with the new positions after the cut
|
|
|
|
staticQuickInfos.forEach(function (info) {
|
|
info.start -= cutIndex;
|
|
info.line -= lineOffset;
|
|
});
|
|
staticQuickInfos = staticQuickInfos.filter(function (s) {
|
|
return s.start > -1;
|
|
});
|
|
errors.forEach(function (err) {
|
|
if (err.start) err.start -= cutIndex;
|
|
if (err.line) err.line -= lineOffset;
|
|
});
|
|
errors = errors.filter(function (e) {
|
|
return e.start && e.start > -1;
|
|
});
|
|
highlights.forEach(function (highlight) {
|
|
highlight.start -= cutIndex;
|
|
highlight.line -= lineOffset;
|
|
});
|
|
highlights = highlights.filter(function (e) {
|
|
return e.start > -1;
|
|
});
|
|
queries.forEach(function (q) {
|
|
return q.line -= lineOffset;
|
|
});
|
|
queries = queries.filter(function (q) {
|
|
return q.line > -1;
|
|
});
|
|
tags.forEach(function (q) {
|
|
return q.line -= lineOffset;
|
|
});
|
|
tags = tags.filter(function (q) {
|
|
return q.line > -1;
|
|
});
|
|
}
|
|
|
|
var cutAfterString = "// ---cut-after---\n";
|
|
|
|
if (code.includes(cutAfterString)) {
|
|
// Get the place it is, then find the end and the start of the next line
|
|
var _cutIndex = code.indexOf(cutAfterString) + cutAfterString.length;
|
|
|
|
var _lineOffset = code.substr(0, _cutIndex).split("\n").length - 1; // Kills the code shown, removing any whitespace on the end
|
|
|
|
|
|
code = code.split(cutAfterString).shift().trimEnd(); // Cut any metadata after the cutAfterString
|
|
|
|
staticQuickInfos = staticQuickInfos.filter(function (s) {
|
|
return s.line < _lineOffset;
|
|
});
|
|
errors = errors.filter(function (e) {
|
|
return e.line && e.line < _lineOffset;
|
|
});
|
|
highlights = highlights.filter(function (e) {
|
|
return e.line < _lineOffset;
|
|
});
|
|
queries = queries.filter(function (q) {
|
|
return q.line < _lineOffset;
|
|
});
|
|
tags = tags.filter(function (q) {
|
|
return q.line < _lineOffset;
|
|
});
|
|
}
|
|
|
|
return {
|
|
code: code,
|
|
extension: extension,
|
|
highlights: highlights,
|
|
queries: queries,
|
|
staticQuickInfos: staticQuickInfos,
|
|
errors: errors,
|
|
playgroundURL: playgroundURL,
|
|
tags: tags
|
|
};
|
|
}
|
|
|
|
var splitTwoslashCodeInfoFiles = function splitTwoslashCodeInfoFiles(code, defaultFileName, root) {
|
|
var lines = code.split(/\r\n?|\n/g);
|
|
var nameForFile = code.includes("@filename: " + defaultFileName) ? "global.ts" : defaultFileName;
|
|
var currentFileContent = [];
|
|
var fileMap = [];
|
|
|
|
for (var _iterator5 = _createForOfIteratorHelperLoose(lines), _step5; !(_step5 = _iterator5()).done;) {
|
|
var line = _step5.value;
|
|
|
|
if (line.includes("// @filename: ")) {
|
|
fileMap.push([root + nameForFile, currentFileContent]);
|
|
nameForFile = line.split("// @filename: ")[1].trim();
|
|
currentFileContent = [];
|
|
} else {
|
|
currentFileContent.push(line);
|
|
}
|
|
}
|
|
|
|
fileMap.push([root + nameForFile, currentFileContent]); // Basically, strip these:
|
|
// ["index.ts", []]
|
|
// ["index.ts", [""]]
|
|
|
|
var nameContent = fileMap.filter(function (n) {
|
|
return n[1].length > 0 && (n[1].length > 1 || n[1][0] !== "");
|
|
});
|
|
return nameContent;
|
|
};
|
|
|
|
export { TwoslashError, twoslasher };
|
|
//# sourceMappingURL=twoslash.esm.js.map
|