rudimentary parallel parse

This commit is contained in:
Ron Buckton 2023-09-12 16:42:49 -04:00
parent fd390e78fe
commit 7fa1fd0f36
No known key found for this signature in database
GPG Key ID: 9ADA0DFD36502AB9
68 changed files with 11288 additions and 387 deletions

View File

@ -189,5 +189,77 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
------------------------------------------------------
------------------ esfx ----------------------------
Copyright 2019 Ron Buckton
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
------------------------------------------------------
----------------- .NET Core ----------------------------------
The MIT License (MIT)
Copyright (c) .NET Foundation and Contributors
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------
------------------ xxHash Library ----------------------------
Copyright (c) 2012-2021 Yann Collet
All rights reserved.
BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
------------------------------------------------------
------------- End of ThirdPartyNotices ------------------------------------------- */

View File

@ -90,6 +90,7 @@
"test:eslint-rules": "hereby run-eslint-rules-tests",
"build": "npm run build:compiler && npm run build:tests",
"build:compiler": "hereby local",
"build:tsc": "hereby tsc",
"build:tests": "hereby tests",
"build:tests:notypecheck": "hereby tests --no-typecheck",
"start": "node lib/tsc",

View File

@ -4,7 +4,7 @@ import os from "os";
const ci = ["1", "true"].includes(process.env.CI ?? "");
const parsed = minimist(process.argv.slice(2), {
boolean: ["dirty", "light", "colors", "lkg", "soft", "fix", "failed", "keepFailed", "force", "built", "ci", "bundle", "typecheck", "lint", "coverage"],
boolean: ["dirty", "light", "colors", "lkg", "soft", "fix", "failed", "keepFailed", "force", "built", "ci", "bundle", "typecheck", "lint", "coverage", "structs"],
string: ["browser", "tests", "break", "host", "reporter", "stackTraceLimit", "timeout", "shards", "shardId"],
alias: {
/* eslint-disable quote-props */
@ -43,6 +43,7 @@ const parsed = minimist(process.argv.slice(2), {
typecheck: true,
lint: true,
coverage: false,
structs: false,
}
});
@ -57,6 +58,12 @@ if (!options.bundle && !options.typecheck) {
throw new Error("--no-typecheck cannot be passed when bundling is disabled");
}
// TODO(rbuckton): remove this when esbuild supports `using` and decorators.
if (options.structs) {
options.bundle = false;
options.lkg = true;
}
export default options;
@ -90,5 +97,6 @@ export default options;
* @property {boolean} typecheck
* @property {boolean} lint
* @property {boolean} coverage
* @property {boolean} structs
*/
void 0;

View File

@ -35,6 +35,7 @@ export async function runConsoleTests(runJs, defaultReporter, runInParallel, opt
const shards = +cmdLineOptions.shards || undefined;
const shardId = +cmdLineOptions.shardId || undefined;
const coverage = cmdLineOptions.coverage;
const structs = cmdLineOptions.structs;
if (!cmdLineOptions.dirty) {
if (options.watching) {
console.log(chalk.yellowBright(`[watch] cleaning test directories...`));
@ -119,6 +120,12 @@ export async function runConsoleTests(runJs, defaultReporter, runInParallel, opt
args.push(runJs);
}
if (structs) {
args.unshift("--harmony-struct");
args.unshift("--shared-string-table");
args.unshift("--enable-source-map");
}
/** @type {number | undefined} */
let errorStatus;

View File

@ -331,7 +331,7 @@ function verifyMatchingSymbols(decl) {
* @param {ts.Symbol} moduleSymbol
*/
function emitAsNamespace(name, moduleSymbol) {
assert(moduleSymbol.flags & ts.SymbolFlags.ValueModule, "moduleSymbol is not a module");
assert(moduleSymbol.flags & ts.SymbolFlags.ValueModule, `moduleSymbol '${moduleSymbol.name}' is not a module, was a ${ts.Debug.formatSymbolFlags(moduleSymbol.flags)} instead`);
scopeStack.push(new Map());
const currentScope = scopeStack[scopeStack.length - 1];

View File

@ -9,6 +9,35 @@ export * from "../perfLogger";
export * from "../tracing";
export * from "../types";
export * from "../sys";
export * from "../workerThreads";
export * from "../threading/condition";
export * from "../threading/countdownEvent";
export * from "../threading/lockable";
export * from "../threading/manualResetEvent";
export * from "../threading/mutex";
export * from "../threading/scopedLock";
export * from "../threading/sharedLockable";
export * from "../threading/sharedMutex";
export * from "../threading/threadPool";
export * from "../threading/uniqueLock";
export * from "../sharing/collections/hashData";
export * from "../sharing/collections/sharedLinkedList";
export * from "../sharing/collections/sharedMap";
export * from "../sharing/collections/sharedResizableArray";
export * from "../sharing/collections/sharedSet";
export * from "../sharing/collections/xxhash32";
export * from "../sharing/structs/fakeSharedStruct";
export * from "../sharing/structs/identifiableStruct";
export * from "../sharing/structs/shareable";
export * from "../sharing/structs/sharedStruct";
export * from "../sharing/structs/taggedStruct";
export * from "../sharing/sharedDiagnostics";
export * from "../sharing/sharedNode";
export * from "../sharing/sharedNodeAdapter";
export * from "../sharing/sharedNodeArray";
export * from "../sharing/sharedObjectAllocator";
export * from "../sharing/sharedParserState";
export * from "../sharing/sharedSymbol";
export * from "../path";
export * from "../diagnosticInformationMap.generated";
export * from "../scanner";
@ -68,6 +97,7 @@ export * from "../builder";
export * from "../builderPublic";
export * from "../resolutionCache";
export * from "../watch";
export * from "../worker";
export * from "../watchPublic";
export * from "../tsbuild";
export * from "../tsbuildPublic";

View File

@ -736,6 +736,7 @@ import {
isUMDExportSymbol,
isValidBigIntString,
isValidESSymbolDeclaration,
isValidNumberString,
isValidTypeOnlyAliasUseSite,
isValueSignatureDeclaration,
isVariableDeclaration,
@ -24322,17 +24323,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
sourceEnd.slice(sourceEnd.length - endLen) !== targetEnd.slice(targetEnd.length - endLen);
}
/**
* Tests whether the provided string can be parsed as a number.
* @param s The string to test.
* @param roundTripOnly Indicates the resulting number matches the input when converted back to a string.
*/
function isValidNumberString(s: string, roundTripOnly: boolean): boolean {
if (s === "") return false;
const n = +s;
return isFinite(n) && (!roundTripOnly || "" + n === s);
}
/**
* @param text a valid bigint string excluding a trailing `n`, but including a possible prefix `-`. Use `isValidBigIntString(text, roundTripOnly)` before calling this function.
*/

View File

@ -506,6 +506,18 @@ export const commonOptionsWithBuild: CommandLineOption[] = [
description: Diagnostics.Set_the_language_of_the_messaging_from_TypeScript_This_does_not_affect_emit,
defaultValueDescription: Diagnostics.Platform_specific
},
{
name: "maxCpuCount",
type: "number",
category: Diagnostics.Command_line_Options,
isCommandLineOnly: false,
description: Diagnostics.Sets_the_maximum_number_of_worker_threads_to_use_during_build,
defaultValueDescription: Diagnostics.Platform_specific,
defaultIfValueMissing: () => sys.cpuCount?.(),
extraValidation: value => typeof value === "number" && value <= 0 ?
[Diagnostics.Option_maxCpuCount_must_be_set_to_1_or_greater] :
undefined
}
];
/** @internal */
@ -1882,16 +1894,26 @@ function parseOptionValue(
}
else {
// Check to see if no argument was provided (e.g. "--locale" is the last command-line argument).
if (!args[i] && opt.type !== "boolean") {
if (!args[i] && opt.type !== "boolean" && !(opt.type === "number" && opt.defaultIfValueMissing)) {
errors.push(createCompilerDiagnostic(diagnostics.optionTypeMismatchDiagnostic, opt.name, getCompilerOptionValueTypeString(opt)));
}
if (args[i] !== "null") {
switch (opt.type) {
case "number":
options[opt.name] = validateJsonOptionValue(opt, parseInt(args[i]), errors);
i++;
case "number": {
let value: number | undefined;
if (args[i]) {
value = parseInt(args[i]);
}
if ((isNullOrUndefined(value) || isNaN(value) || !isFinite(value)) && opt.defaultIfValueMissing) {
value = opt.defaultIfValueMissing();
}
else {
i++;
}
options[opt.name] = validateJsonOptionValue(opt, value, errors);
break;
}
case "boolean":
// boolean flag has optional value true, false, others
const optValue = args[i];

View File

@ -5,13 +5,18 @@ import {
Comparison,
Debug,
EqualityComparer,
isTaggedStruct,
isWhiteSpaceLike,
MapLike,
Queue,
SharedNodeArray,
SortedArray,
SortedReadonlyArray,
TextSpan,
} from "./_namespaces/ts";
import { SharedNodeBase } from "./sharing/sharedNode";
import { isShareableNonPrimitive, isSharedArray } from "./sharing/structs/shareable";
import { Tag } from "./sharing/structs/taggedStruct";
/** @internal */
@ -660,21 +665,22 @@ export function mapEntries<K1, V1, K2, V2>(map: ReadonlyMap<K1, V1> | undefined,
}
/** @internal */
export function some<T>(array: readonly T[] | undefined): array is readonly T[];
export function some<T>(array: readonly T[] | SharedNodeArray<Extract<T, SharedNodeBase>> | undefined): array is readonly T[] | SharedNodeArray<Extract<T, SharedNodeBase>>;
/** @internal */
export function some<T>(array: readonly T[] | undefined, predicate: (value: T) => boolean): boolean;
export function some<T>(array: readonly T[] | SharedNodeArray<Extract<T, SharedNodeBase>> | undefined, predicate: (value: T) => boolean): boolean;
/** @internal */
export function some<T>(array: readonly T[] | undefined, predicate?: (value: T) => boolean): boolean {
export function some<T>(array: readonly T[] | SharedNodeArray<Extract<T, SharedNodeBase>> | undefined, predicate?: (value: T) => boolean): boolean {
if (array) {
if (predicate) {
for (const v of array) {
const iterable = isTaggedStruct(array, Tag.NodeArray) ? SharedNodeArray.values(array) : array;
for (const v of iterable) {
if (predicate(v)) {
return true;
}
}
}
else {
return array.length > 0;
return (array instanceof SharedNodeArray ? array.items.length : array.length) > 0;
}
}
return false;
@ -1841,12 +1847,12 @@ export function isArray(value: any): value is readonly unknown[] {
}
/** @internal */
export function toArray<T>(value: T | T[]): T[];
export function toArray<T>(value: T | T[] | SharedArray<Extract<T, Shareable>>): T[];
/** @internal */
export function toArray<T>(value: T | readonly T[]): readonly T[];
export function toArray<T>(value: T | readonly T[] | SharedArray<Extract<T, Shareable>>): readonly T[];
/** @internal */
export function toArray<T>(value: T | T[]): T[] {
return isArray(value) ? value : [value];
export function toArray<T>(value: T | T[] | SharedArray<Extract<T, Shareable>>): T[] {
return isSharedArray(value) ? Array.from(value) : isArray(value) ? value : [value];
}
/**
@ -1857,11 +1863,17 @@ export function toArray<T>(value: T | T[]): T[] {
export function isString(text: unknown): text is string {
return typeof text === "string";
}
/** @internal */
export function isNumber(x: unknown): x is number {
return typeof x === "number";
}
/** @internal */
export function isNull(x: unknown): x is null { // eslint-disable-line no-null/no-null
return x === null; // eslint-disable-line no-null/no-null
}
/** @internal */
export function tryCast<TOut extends TIn, TIn = any>(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut | undefined {
return value !== undefined && test(value) ? value : undefined;
@ -1871,7 +1883,24 @@ export function tryCast<TOut extends TIn, TIn = any>(value: TIn | undefined, tes
export function cast<TOut extends TIn, TIn = any>(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut {
if (value !== undefined && test(value)) return value;
return Debug.fail(`Invalid cast. The supplied value ${value} did not pass the test '${Debug.getFunctionName(test)}'.`);
let valueArg: unknown = value;
if (isShareableNonPrimitive(valueArg)) {
if ("__tag__" in valueArg) {
const tag = valueArg.__tag__ as Tag;
if (tag === Tag.Node) {
const kind = (valueArg as SharedNodeBase).kind;
valueArg = `[object SharedNodeBase(${Debug.formatSyntaxKind(kind)})]`;
}
else {
valueArg = `[object TaggedStruct(${Debug.formatTag(tag)})]`;
}
}
else {
valueArg = "[object SharedStruct]";
}
}
return Debug.fail(`Invalid cast. The supplied value ${valueArg} did not pass the test '${Debug.getFunctionName(test)}'.`);
}
/**
@ -2883,3 +2912,38 @@ export function isNodeLikeSystem(): boolean {
&& !(process as any).browser
&& typeof module === "object";
}
/** @internal */
export class Lazy<T> {
private static circular = (): any => { throw new Error("Lazy instantiation was circular"); };
private _value: T | (() => T);
private _hasValue: boolean;
constructor(valueFactory: () => T) {
this._value = valueFactory;
this._hasValue = false;
}
get hasValue() {
return this._value;
}
get value() {
if (this._hasValue) {
return this._value as T;
}
const valueFactory = this._value as (() => T);
try {
this._value = Lazy.circular;
this._value = valueFactory();
this._hasValue = true;
return this._value;
}
finally {
if (!this._hasValue) {
this._value = valueFactory;
}
}
}
}

View File

@ -93,6 +93,8 @@ import {
VarianceFlags,
zipWith,
} from "./_namespaces/ts";
import { isShareableNonPrimitive } from "./sharing/structs/shareable";
import { Tag } from "./sharing/structs/taggedStruct";
/** @internal */
export enum LogLevel {
@ -106,6 +108,7 @@ export enum LogLevel {
/** @internal */
export interface LoggingHost {
log(level: LogLevel, s: string): void;
format?(...args: readonly unknown[]): string;
}
/** @internal */
@ -123,31 +126,41 @@ export namespace Debug {
return currentLogLevel <= level;
}
function logMessage(level: LogLevel, s: string): void {
function logMessage(level: LogLevel, ...args: unknown[]): void {
if (loggingHost && shouldLog(level)) {
const s = loggingHost.format?.(...args) ?? args.join(" ");
loggingHost.log(level, s);
}
}
export function log(s: string): void {
logMessage(LogLevel.Info, s);
export function format(...args: unknown[]) {
const text = loggingHost?.format?.(...args);
if (text !== undefined) {
return text;
}
args = args.map(arg => isShareableNonPrimitive(arg) ? "[object SharedStruct]" : arg);
return args.join(" ");
}
export function log(...args: unknown[]): void {
logMessage(LogLevel.Info, ...args);
}
export namespace log {
export function error(s: string): void {
logMessage(LogLevel.Error, s);
export function error(...args: unknown[]): void {
logMessage(LogLevel.Error, ...args);
}
export function warn(s: string): void {
logMessage(LogLevel.Warning, s);
export function warn(...args: unknown[]): void {
logMessage(LogLevel.Warning, ...args);
}
export function log(s: string): void {
logMessage(LogLevel.Info, s);
export function log(...args: unknown[]): void {
logMessage(LogLevel.Info, ...args);
}
export function trace(s: string): void {
logMessage(LogLevel.Verbose, s);
export function trace(...args: unknown[]): void {
logMessage(LogLevel.Verbose, ...args);
}
}
@ -192,13 +205,18 @@ export namespace Debug {
return true;
}
export function captureStackTrace<T extends object>(e: T, stackCrawlMark: AnyFunction) {
if ((Error as any).captureStackTrace) {
(Error as any).captureStackTrace(e, stackCrawlMark || fail);
}
return e;
}
export function fail(message?: string, stackCrawlMark?: AnyFunction): never {
// eslint-disable-next-line no-debugger
debugger;
const e = new Error(message ? `Debug Failure. ${message}` : "Debug Failure.");
if ((Error as any).captureStackTrace) {
(Error as any).captureStackTrace(e, stackCrawlMark || fail);
}
captureStackTrace(e, stackCrawlMark ?? fail);
throw e;
}
@ -433,6 +451,10 @@ export namespace Debug {
return sorted;
}
export function formatTag(tag: Tag | undefined): string {
return formatEnum(tag, (ts as any).Tag, /*isFlags*/ false);
}
export function formatSyntaxKind(kind: SyntaxKind | undefined): string {
return formatEnum(kind, (ts as any).SyntaxKind, /*isFlags*/ false);
}
@ -548,7 +570,7 @@ export namespace Debug {
}
}
let nodeArrayProto: NodeArray<Node> | undefined;
const weakNodeArrayPrototype = new WeakMap<object, NodeArray<any>>();
function attachNodeArrayDebugInfoWorker(array: NodeArray<Node>) {
if (!("__tsDebuggerDisplay" in array)) { // eslint-disable-line local/no-in-operator
@ -574,8 +596,11 @@ export namespace Debug {
if (typeof Object.setPrototypeOf === "function") {
// if we're in es2015, attach the method to a shared prototype for `NodeArray`
// so the method doesn't show up in the watch window.
const prototype = Object.getPrototypeOf(array);
let nodeArrayProto = weakNodeArrayPrototype.get(prototype);
if (!nodeArrayProto) {
nodeArrayProto = Object.create(Array.prototype) as NodeArray<Node>;
nodeArrayProto = Object.create(prototype) as NodeArray<Node>;
weakNodeArrayPrototype.set(prototype, nodeArrayProto);
attachNodeArrayDebugInfoWorker(nodeArrayProto);
}
Object.setPrototypeOf(array, nodeArrayProto);

View File

@ -4373,6 +4373,10 @@
"category": "Error",
"code": 5110
},
"Option 'maxCpuCount' must be set to 1 or greater.": {
"category": "Error",
"code": 5111
},
"Generates a sourcemap for each corresponding '.d.ts' file.": {
"category": "Message",
@ -6151,6 +6155,10 @@
"category": "Message",
"code": 6804
},
"Sets the maximum number of worker threads to use during build.": {
"category": "Message",
"code": 6805
},
"one of:": {
"category": "Message",

View File

@ -1,7 +1,8 @@
import {
Node,
NodeArray,
objectAllocator,
SyntaxKind,
SyntaxKind
} from "../_namespaces/ts";
/**
@ -11,6 +12,7 @@ import {
* @internal
*/
export interface BaseNodeFactory {
createBaseNodeArray<T extends Node>(items: readonly T[], hasTrailingComma?: boolean): NodeArray<T>;
createBaseSourceFileNode(kind: SyntaxKind.SourceFile): Node;
createBaseIdentifierNode(kind: SyntaxKind.Identifier): Node;
createBasePrivateIdentifierNode(kind: SyntaxKind.PrivateIdentifier): Node;
@ -24,6 +26,7 @@ export interface BaseNodeFactory {
* @internal
*/
export function createBaseNodeFactory(): BaseNodeFactory {
let NodeArrayContructor: new <T extends Node>(elements: readonly T[], hasTrailingComma?: boolean) => NodeArray<T>;
let NodeConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node;
let TokenConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node;
let IdentifierConstructor: new (kind: SyntaxKind.Identifier, pos: number, end: number) => Node;
@ -35,7 +38,8 @@ export function createBaseNodeFactory(): BaseNodeFactory {
createBaseIdentifierNode,
createBasePrivateIdentifierNode,
createBaseTokenNode,
createBaseNode
createBaseNode,
createBaseNodeArray,
};
function createBaseSourceFileNode(kind: SyntaxKind.SourceFile): Node {
@ -57,4 +61,8 @@ export function createBaseNodeFactory(): BaseNodeFactory {
function createBaseNode(kind: SyntaxKind): Node {
return new (NodeConstructor || (NodeConstructor = objectAllocator.getNodeConstructor()))(kind, /*pos*/ -1, /*end*/ -1);
}
function createBaseNodeArray<T extends Node>(elements: readonly T[], hasTrailingComma?: boolean): NodeArray<T> {
return new (NodeArrayContructor || (NodeArrayContructor = objectAllocator.getNodeArrayConstructor()))(elements, hasTrailingComma);
}
}

View File

@ -475,6 +475,9 @@ import {
WithStatement,
YieldExpression,
} from "../_namespaces/ts";
import { SharedNodeBase } from "../sharing/sharedNode";
import { SharedNodeArray } from "../sharing/sharedNodeArray";
import { isShareableNonPrimitive } from "../sharing/structs/shareable";
let nextAutoGenerateId = 0;
@ -903,7 +906,8 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
createJsxText,
updateJsxText,
createJsxOpeningFragment,
createJsxJsxClosingFragment,
createJsxJsxClosingFragment: createJsxClosingFragment,
createJsxClosingFragment,
updateJsxFragment,
createJsxAttribute,
updateJsxAttribute,
@ -1053,12 +1057,10 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
// This *was* a `NodeArray`, but the `hasTrailingComma` option differs. Recreate the
// array with the same elements, text range, and transform flags but with the updated
// value for `hasTrailingComma`
const array = elements.slice() as MutableNodeArray<T>;
const array = baseFactory.createBaseNodeArray(elements.slice(), hasTrailingComma) as MutableNodeArray<T>;
array.pos = elements.pos;
array.end = elements.end;
array.hasTrailingComma = hasTrailingComma;
array.transformFlags = elements.transformFlags;
Debug.attachNodeArrayDebugInfo(array);
return array;
}
@ -1066,13 +1068,8 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
// repeatedly calling push(), the list may not have the optimal memory layout. We invoke slice() for
// small arrays (1 to 4 elements) to give the VM a chance to allocate an optimal representation.
const length = elements.length;
const array = (length >= 1 && length <= 4 ? elements.slice() : elements) as MutableNodeArray<T>;
array.pos = -1;
array.end = -1;
array.hasTrailingComma = !!hasTrailingComma;
array.transformFlags = TransformFlags.None;
const array = baseFactory.createBaseNodeArray(length >= 1 && length <= 4 ? elements.slice() : elements, !!hasTrailingComma) as MutableNodeArray<T>;
aggregateChildrenFlags(array);
Debug.attachNodeArrayDebugInfo(array);
return array;
}
@ -3768,6 +3765,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
node.transformFlags |=
propagateChildrenFlags(node.modifiers) |
propagateChildFlags(node.declarationList);
if (modifiersToFlags(node.modifiers) & ModifierFlags.Ambient) {
node.transformFlags = TransformFlags.ContainsTypeScript;
}
@ -5591,7 +5589,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
}
// @api
function createJsxJsxClosingFragment() {
function createJsxClosingFragment() {
const node = createBaseNode<JsxClosingFragment>(SyntaxKind.JsxClosingFragment);
node.transformFlags |= TransformFlags.ContainsJsx;
return node;
@ -5931,35 +5929,37 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
propagateChildrenFlags(node.statements) |
propagateChildFlags(node.endOfFileToken);
node.locals = undefined; // initialized by binder (LocalsContainer)
node.nextContainer = undefined; // initialized by binder (LocalsContainer)
node.endFlowNode = undefined;
if (!isShareableNonPrimitive(node)) {
node.locals = undefined; // initialized by binder (LocalsContainer)
node.nextContainer = undefined; // initialized by binder (LocalsContainer)
node.endFlowNode = undefined;
node.nodeCount = 0;
node.identifierCount = 0;
node.symbolCount = 0;
node.parseDiagnostics = undefined!;
node.bindDiagnostics = undefined!;
node.bindSuggestionDiagnostics = undefined;
node.lineMap = undefined!;
node.externalModuleIndicator = undefined;
node.setExternalModuleIndicator = undefined;
node.pragmas = undefined!;
node.checkJsDirective = undefined;
node.referencedFiles = undefined!;
node.typeReferenceDirectives = undefined!;
node.libReferenceDirectives = undefined!;
node.amdDependencies = undefined!;
node.commentDirectives = undefined;
node.identifiers = undefined!;
node.packageJsonLocations = undefined;
node.packageJsonScope = undefined;
node.imports = undefined!;
node.moduleAugmentations = undefined!;
node.ambientModuleNames = undefined!;
node.resolvedModules = undefined;
node.classifiableNames = undefined;
node.impliedNodeFormat = undefined;
}
node.nodeCount = 0;
node.identifierCount = 0;
node.symbolCount = 0;
node.parseDiagnostics = undefined!;
node.bindDiagnostics = undefined!;
node.bindSuggestionDiagnostics = undefined;
node.lineMap = undefined!;
node.externalModuleIndicator = undefined;
node.setExternalModuleIndicator = undefined;
node.pragmas = undefined!;
node.checkJsDirective = undefined;
node.referencedFiles = undefined!;
node.typeReferenceDirectives = undefined!;
node.libReferenceDirectives = undefined!;
node.amdDependencies = undefined!;
node.commentDirectives = undefined;
node.identifiers = undefined!;
node.packageJsonLocations = undefined;
node.packageJsonScope = undefined;
node.imports = undefined!;
node.moduleAugmentations = undefined!;
node.ambientModuleNames = undefined!;
node.resolvedModules = undefined;
node.classifiableNames = undefined;
node.impliedNodeFormat = undefined;
return node;
}
@ -5998,7 +5998,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
const node = baseFactory.createBaseSourceFileNode(SyntaxKind.SourceFile) as Mutable<SourceFile>;
node.flags |= source.flags & ~NodeFlags.Synthesized;
for (const p in source) {
if (hasProperty(node, p) || !hasProperty(source, p)) {
if (hasProperty(node, p) || !hasProperty(source, p) || p === "__tag__" || p === "__hash__" || p === "__shared__" || p === "__sharedCache__") {
continue;
}
if (p === "emitNode") {
@ -7182,10 +7182,11 @@ function propagateChildrenFlags(children: NodeArray<Node> | undefined): Transfor
return children ? children.transformFlags : TransformFlags.None;
}
function aggregateChildrenFlags(children: MutableNodeArray<Node>) {
function aggregateChildrenFlags(children: MutableNodeArray<Node> | SharedNodeArray<SharedNodeBase>) {
let subtreeFlags = TransformFlags.None;
for (const child of children) {
subtreeFlags |= propagateChildFlags(child);
const values = children instanceof SharedNodeArray ? Array.from(children.items) : children;
for (const child of values) {
subtreeFlags |= propagateChildFlags(child as Node);
}
children.transformFlags = subtreeFlags;
}
@ -7280,6 +7281,7 @@ const syntheticFactory: BaseNodeFactory = {
createBasePrivateIdentifierNode: kind => makeSynthetic(baseFactory.createBasePrivateIdentifierNode(kind)),
createBaseTokenNode: kind => makeSynthetic(baseFactory.createBaseTokenNode(kind)),
createBaseNode: kind => makeSynthetic(baseFactory.createBaseNode(kind)),
createBaseNodeArray: baseFactory.createBaseNodeArray,
};
export const factory = createNodeFactory(NodeFactoryFlags.NoIndentationOnFreshPropertyAccess, syntheticFactory);

View File

@ -2,9 +2,11 @@ import {
AccessorDeclaration,
addRange,
addRelatedInfo,
AmdDependency,
append,
ArrayBindingElement,
ArrayBindingPattern,
arrayFrom,
ArrayLiteralExpression,
ArrayTypeNode,
ArrowFunction,
@ -34,6 +36,7 @@ import {
CaseOrDefaultClause,
CatchClause,
CharacterCodes,
CheckJsDirective,
ClassDeclaration,
ClassElement,
ClassExpression,
@ -65,8 +68,11 @@ import {
Diagnostic,
DiagnosticArguments,
DiagnosticMessage,
DiagnosticMessageChain,
DiagnosticRelatedInformation,
Diagnostics,
DiagnosticWithDetachedLocation,
DiagnosticWithLocation,
DoStatement,
DotDotDotToken,
ElementAccessExpression,
@ -88,6 +94,7 @@ import {
ExternalModuleReference,
fileExtensionIs,
fileExtensionIsOneOf,
FileReference,
findIndex,
firstOrUndefined,
forEach,
@ -113,8 +120,10 @@ import {
HasJSDoc,
hasJSDocNodes,
HasModifiers,
hasProperty,
HeritageClause,
Identifier,
identity,
idText,
IfStatement,
ImportClause,
@ -238,7 +247,6 @@ import {
LiteralExpression,
LiteralLikeNode,
LiteralTypeNode,
map,
mapDefined,
MappedTypeNode,
MemberExpression,
@ -330,6 +338,7 @@ import {
setTextRangePos,
setTextRangePosEnd,
setTextRangePosWidth,
SharedMap,
ShorthandPropertyAssignment,
skipTrivia,
some,
@ -398,6 +407,11 @@ import {
YieldExpression,
} from "./_namespaces/ts";
import * as performance from "./_namespaces/ts.performance";
import { SharedDiagnostic, SharedDiagnosticMessageChain, SharedDiagnosticRelatedInformation, SharedDiagnosticWithLocation } from "./sharing/sharedDiagnostics";
import { SharedAmdDependency, SharedCheckJsDirective, SharedCommentDirective, SharedCommentRange, SharedFileReference, SharedNodeBase, SharedPragma, SharedPragmaArguments, SharedPragmaSpan, SharedSourceFile, SharedTextRange } from "./sharing/sharedNode";
import { SharedNodeArray } from "./sharing/sharedNodeArray";
import { isShareableNonPrimitive } from "./sharing/structs/shareable";
import { Tag, TaggedStruct } from "./sharing/structs/taggedStruct";
const enum SignatureFlags {
None = 0,
@ -414,6 +428,7 @@ const enum SpeculationKind {
Reparse
}
let NodeArrayConstructor: new <T extends Node>(elements: readonly T[], hasTrailingComma?: boolean) => NodeArray<T>;
let NodeConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node;
let TokenConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node;
let IdentifierConstructor: new (kind: SyntaxKind.Identifier, pos: number, end: number) => Node;
@ -431,6 +446,7 @@ export const parseBaseNodeFactory: BaseNodeFactory = {
createBasePrivateIdentifierNode: kind => new (PrivateIdentifierConstructor || (PrivateIdentifierConstructor = objectAllocator.getPrivateIdentifierConstructor()))(kind, -1, -1),
createBaseTokenNode: kind => new (TokenConstructor || (TokenConstructor = objectAllocator.getTokenConstructor()))(kind, -1, -1),
createBaseNode: kind => new (NodeConstructor || (NodeConstructor = objectAllocator.getNodeConstructor()))(kind, -1, -1),
createBaseNodeArray: (elements, hasTrailingComma?) => new (NodeArrayConstructor || (NodeArrayConstructor = objectAllocator.getNodeArrayConstructor()))(elements, hasTrailingComma),
};
/** @internal */
@ -1265,16 +1281,17 @@ export function forEachChildRecursively<T>(rootNode: Node, cbNode: (node: Node,
while (queue.length !== 0) {
const current = queue.pop()!;
const parent = parents.pop()!;
if (isArray(current)) {
if (isArray(current) || current instanceof SharedNodeArray) {
const currentArray = current instanceof SharedNodeArray ? arrayFrom(SharedNodeArray.values(current)) as unknown as NodeArray<Node> : current;
if (cbNodes) {
const res = cbNodes(current, parent);
const res = cbNodes(currentArray, parent);
if (res) {
if (res === "skip") continue;
return res;
}
}
for (let i = current.length - 1; i >= 0; --i) {
queue.push(current[i]);
for (let i = currentArray.length - 1; i >= 0; --i) {
queue.push(currentArray[i]);
parents.push(parent);
}
}
@ -1327,6 +1344,9 @@ function setExternalModuleIndicator(sourceFile: SourceFile) {
sourceFile.externalModuleIndicator = isFileProbablyExternalModule(sourceFile);
}
export function createSourceFile(fileName: string, sourceText: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, setParentNodes?: boolean, scriptKind?: ScriptKind): SourceFile;
/** @internal */
export function createSourceFile(fileName: string, sourceText: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, setParentNodes?: boolean, scriptKind?: ScriptKind): SourceFile;
export function createSourceFile(fileName: string, sourceText: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, setParentNodes = false, scriptKind?: ScriptKind): SourceFile {
tracing?.push(tracing.Phase.Parse, "createSourceFile", { path: fileName }, /*separateBeginAndEnd*/ true);
performance.mark("beforeParse");
@ -1424,6 +1444,7 @@ namespace Parser {
var disallowInAndDecoratorContext = NodeFlags.DisallowInContext | NodeFlags.DecoratorContext;
// capture constructors in 'initializeState' to avoid null checks
let NodeArrayConstructor: new <T extends Node>(elements: readonly T[], hasTrailingComma?: boolean) => NodeArray<T>;
var NodeConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node;
var TokenConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node;
var IdentifierConstructor: new (kind: SyntaxKind.Identifier, pos: number, end: number) => Identifier;
@ -1442,7 +1463,8 @@ namespace Parser {
createBaseIdentifierNode: kind => countNode(new IdentifierConstructor(kind, /*pos*/ 0, /*end*/ 0)),
createBasePrivateIdentifierNode: kind => countNode(new PrivateIdentifierConstructor(kind, /*pos*/ 0, /*end*/ 0)),
createBaseTokenNode: kind => countNode(new TokenConstructor(kind, /*pos*/ 0, /*end*/ 0)),
createBaseNode: kind => countNode(new NodeConstructor(kind, /*pos*/ 0, /*end*/ 0))
createBaseNode: kind => countNode(new NodeConstructor(kind, /*pos*/ 0, /*end*/ 0)),
createBaseNodeArray: (elements, hasTrailingComma?) => new NodeArrayConstructor(elements, hasTrailingComma),
};
var factory = createNodeFactory(NodeFactoryFlags.NoParenthesizerRules | NodeFactoryFlags.NoNodeConverters | NodeFactoryFlags.NoOriginalNode, baseNodeFactory);
@ -1702,6 +1724,7 @@ namespace Parser {
}
function initializeState(_fileName: string, _sourceText: string, _languageVersion: ScriptTarget, _syntaxCursor: IncrementalParser.SyntaxCursor | undefined, _scriptKind: ScriptKind) {
NodeArrayConstructor = objectAllocator.getNodeArrayConstructor();
NodeConstructor = objectAllocator.getNodeConstructor();
TokenConstructor = objectAllocator.getTokenConstructor();
IdentifierConstructor = objectAllocator.getIdentifierConstructor();
@ -1765,6 +1788,69 @@ namespace Parser {
topLevel = true;
}
function shareCommentDirective(directive: CommentDirective): SharedCommentDirective {
const sharedRange = new SharedTextRange(directive.range.pos, directive.range.end);
const sharedDirective = new SharedCommentDirective(sharedRange, directive.type);
return sharedDirective;
}
function shareDiagnosticMessageChain(messageChain: DiagnosticMessageChain): SharedDiagnosticMessageChain {
return new SharedDiagnosticMessageChain(
messageChain.messageText,
messageChain.category,
messageChain.code,
shareArray(messageChain.next, shareDiagnosticMessageChain)
);
}
function shareDiagnosticRelatedInformation(related: Diagnostic | DiagnosticRelatedInformation): SharedDiagnostic | SharedDiagnosticRelatedInformation {
if (hasProperty(related, "reportsUnnecessary") ||
hasProperty(related, "reportsDeprecated") ||
hasProperty(related, "source") ||
hasProperty(related, "relatedInformation") ||
hasProperty(related, "skippedOn")) {
return shareDiagnostic(related);
}
Debug.assert(related.file === undefined || isShareableNonPrimitive(related.file));
return new SharedDiagnosticRelatedInformation(
related.category,
related.code,
related.file as unknown as SharedSourceFile | undefined,
related.start,
related.length,
typeof related.messageText === "string" ? related.messageText : shareDiagnosticMessageChain(related.messageText)
);
}
function shareDiagnostic(diagnostic: DiagnosticWithLocation): SharedDiagnosticWithLocation;
function shareDiagnostic(diagnostic: Diagnostic): SharedDiagnostic;
function shareDiagnostic(diagnostic: Diagnostic): SharedDiagnostic {
Debug.assert(diagnostic.file === undefined || isShareableNonPrimitive(diagnostic.file));
const sharedDiagnostic = new SharedDiagnostic(
diagnostic.category,
diagnostic.code,
diagnostic.file as unknown as SharedSourceFile | undefined,
diagnostic.start,
diagnostic.length,
typeof diagnostic.messageText === "string" ? diagnostic.messageText : shareDiagnosticMessageChain(diagnostic.messageText)
);
sharedDiagnostic.reportsUnnecessary = diagnostic.reportsUnnecessary === undefined ? undefined : !!diagnostic.reportsUnnecessary;
sharedDiagnostic.reportsDeprecated = diagnostic.reportsDeprecated === undefined ? undefined : !!diagnostic.reportsDeprecated;
sharedDiagnostic.source = diagnostic.source;
sharedDiagnostic.relatedInformation = shareArray(diagnostic.relatedInformation, shareDiagnosticRelatedInformation);
sharedDiagnostic.skippedOn = diagnostic.skippedOn;
return sharedDiagnostic;
}
function shareMap<K, V, KS extends Shareable, VS extends Shareable>(map: ReadonlyMap<K, V>, shareKey: (key: K) => KS, shareValue: (value: V) => VS) {
const sharedMap = new SharedMap<KS, VS>(map.size);
for (const [key, value] of map) {
SharedMap.set(sharedMap, shareKey(key), shareValue(value));
}
return sharedMap;
}
function parseSourceFileWorker(languageVersion: ScriptTarget, setParentNodes: boolean, scriptKind: ScriptKind, setExternalModuleIndicator: (file: SourceFile) => void): SourceFile {
const isDeclarationFile = isDeclarationFileName(fileName);
if (isDeclarationFile) {
@ -1787,13 +1873,23 @@ namespace Parser {
processCommentPragmas(sourceFile as {} as PragmaContext, sourceText);
processPragmasIntoFields(sourceFile as {} as PragmaContext, reportPragmaDiagnostic);
sourceFile.commentDirectives = scanner.getCommentDirectives();
sourceFile.nodeCount = nodeCount;
sourceFile.identifierCount = identifierCount;
sourceFile.identifiers = identifiers;
sourceFile.parseDiagnostics = attachFileToDiagnostics(parseDiagnostics, sourceFile);
if (jsDocDiagnostics) {
sourceFile.jsDocDiagnostics = attachFileToDiagnostics(jsDocDiagnostics, sourceFile);
if (isShareableNonPrimitive(sourceFile)) {
sourceFile.identifiers = shareMap(identifiers, identity, identity) as unknown as Map<string, string>;
sourceFile.commentDirectives = shareArray(scanner.getCommentDirectives(), shareCommentDirective) as unknown as CommentDirective[];
sourceFile.parseDiagnostics = shareArray(attachFileToDiagnostics(parseDiagnostics, sourceFile), shareDiagnostic) as unknown as DiagnosticWithLocation[];
if (jsDocDiagnostics) {
sourceFile.jsDocDiagnostics = shareArray(attachFileToDiagnostics(jsDocDiagnostics, sourceFile), shareDiagnostic) as unknown as DiagnosticWithLocation[];
}
}
else {
sourceFile.identifiers = identifiers;
sourceFile.commentDirectives = scanner.getCommentDirectives();
sourceFile.parseDiagnostics = attachFileToDiagnostics(parseDiagnostics, sourceFile);
if (jsDocDiagnostics) {
sourceFile.jsDocDiagnostics = attachFileToDiagnostics(jsDocDiagnostics, sourceFile);
}
}
if (setParentNodes) {
@ -1815,7 +1911,14 @@ namespace Parser {
Debug.assert(!node.jsDoc); // Should only be called once per node
const jsDoc = mapDefined(getJSDocCommentRanges(node, sourceText), comment => JSDocParser.parseJSDocComment(node, comment.pos, comment.end - comment.pos));
if (jsDoc.length) node.jsDoc = jsDoc;
if (jsDoc.length) {
if (node instanceof SharedNodeBase) {
node.jsDoc = shareArray(jsDoc, n => n as unknown as SharedNodeBase) as unknown as JSDoc[];
}
else {
node.jsDoc = jsDoc;
}
}
if (hasDeprecatedTag) {
hasDeprecatedTag = false;
(node as Mutable<T>).flags |= NodeFlags.Deprecated;
@ -1965,7 +2068,7 @@ namespace Parser {
function setFields(sourceFile: SourceFile) {
sourceFile.text = sourceText;
sourceFile.bindDiagnostics = [];
sourceFile.bindDiagnostics = isShareableNonPrimitive(sourceFile) ? undefined! : [];
sourceFile.bindSuggestionDiagnostics = undefined;
sourceFile.languageVersion = languageVersion;
sourceFile.fileName = fileName;
@ -1974,7 +2077,9 @@ namespace Parser {
sourceFile.scriptKind = scriptKind;
setExternalModuleIndicator(sourceFile);
sourceFile.setExternalModuleIndicator = setExternalModuleIndicator;
if (!isShareableNonPrimitive(sourceFile)) {
sourceFile.setExternalModuleIndicator = setExternalModuleIndicator;
}
}
}
@ -7366,14 +7471,17 @@ namespace Parser {
const pos = getNodePos();
const hasJSDoc = hasPrecedingJSDocComment();
const modifiers = parseModifiers(/*allowDecorators*/ true);
const isAmbient = some(modifiers, isDeclareModifier);
const modifiersArray =
modifiers instanceof SharedNodeArray ? arrayFrom(SharedNodeArray.values(modifiers) as IterableIterator<Modifier>) :
modifiers;
const isAmbient = some(modifiersArray, isDeclareModifier);
if (isAmbient) {
const node = tryReuseAmbientDeclaration(pos);
if (node) {
return node;
}
for (const m of modifiers!) {
for (const m of modifiersArray!) {
(m as Mutable<Node>).flags |= NodeFlags.Ambient;
}
return doInsideOfContext(NodeFlags.Ambient, () => parseDeclarationWorker(pos, hasJSDoc, modifiers));
@ -9797,7 +9905,7 @@ namespace IncrementalParser {
const result = Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, syntaxCursor, /*setParentNodes*/ true, sourceFile.scriptKind, sourceFile.setExternalModuleIndicator);
result.commentDirectives = getNewCommentDirectives(
sourceFile.commentDirectives,
result.commentDirectives,
result.commentDirectives?.slice(),
changeRange.span.start,
textSpanEnd(changeRange.span),
delta,
@ -9810,7 +9918,7 @@ namespace IncrementalParser {
}
function getNewCommentDirectives(
oldDirectives: CommentDirective[] | undefined,
oldDirectives: readonly CommentDirective[] | undefined,
newDirectives: CommentDirective[] | undefined,
changeStart: number,
changeRangeOldEnd: number,
@ -10374,41 +10482,131 @@ export function processCommentPragmas(context: PragmaContext, sourceText: string
extractPragmas(pragmas, range, comment);
}
context.pragmas = new Map() as PragmaMap;
const map = new Map() as PragmaMap;
for (const pragma of pragmas) {
if (context.pragmas.has(pragma.name)) {
const currentValue = context.pragmas.get(pragma.name);
if (map.has(pragma.name)) {
const currentValue = map.get(pragma.name);
if (currentValue instanceof Array) {
currentValue.push(pragma.args);
}
else {
context.pragmas.set(pragma.name, [currentValue, pragma.args]);
map.set(pragma.name, [currentValue, pragma.args]);
}
continue;
}
context.pragmas.set(pragma.name, pragma.args);
map.set(pragma.name, pragma.args);
}
if (isShareableNonPrimitive(context)) {
const sharedMap = new SharedMap<string, SharedArray<SharedPragma> | SharedPragma>();
for (const [key, value] of map) {
if (isArray(value)) {
SharedMap.set(sharedMap, key, shareArray(value, sharePragma));
}
else {
SharedMap.set(sharedMap, key, sharePragma(value));
}
}
context.pragmas = sharedMap as unknown as PragmaMap;
}
else {
context.pragmas = map as PragmaMap;
}
}
function sharePragmaValue<T extends string | { pos: number, end: number, value: string } | undefined>(span: T): T extends string ? string : T extends object ? SharedPragmaSpan : undefined;
function sharePragmaValue<T extends string | { pos: number, end: number, value: string } | undefined>(span: T): SharedPragmaSpan | string | undefined {
if (typeof span === "string" || span === undefined) {
return span;
}
return new SharedPragmaSpan(span.pos, span.end, span.value);
}
function sharePragmaArguments(args: PragmaPseudoMapEntry["args"]["arguments"]) {
const sharedArgs = new SharedPragmaArguments();
if ("types" in args) sharedArgs.types = sharePragmaValue(args.types);
if ("lib" in args) sharedArgs.lib = sharePragmaValue(args.lib);
if ("path" in args) sharedArgs.path = sharePragmaValue(args.path);
if ("no-default-lib" in args) sharedArgs["no-default-lib"] = sharePragmaValue(args["no-default-lib"]);
if ("resolution-mode" in args) sharedArgs["resolution-mode"] = sharePragmaValue(args["resolution-mode"]);
if ("name" in args) sharedArgs.name = sharePragmaValue(args.name);
if ("factory" in args) sharedArgs.factory = sharePragmaValue(args.factory);
return sharedArgs;
}
function shareCommentRange(range: CommentRange) {
const sharedRange = new SharedCommentRange();
sharedRange.kind = range.kind;
sharedRange.pos = range.pos;
sharedRange.end = range.end;
sharedRange.hasTrailingNewLine = range.hasTrailingNewLine;
return sharedRange;
}
function sharePragma(pragma: PragmaPseudoMapEntry["args"]) {
return new SharedPragma(
sharePragmaArguments(pragma.arguments),
shareCommentRange(pragma.range)
);
}
function shareArray<T, U extends TaggedStruct<Tag>>(array: T[], shareElement: (value: T) => U): SharedArray<U>;
function shareArray<T, U extends TaggedStruct<Tag>>(array: T[] | undefined, shareElement: (value: T) => U): SharedArray<U> | undefined;
function shareArray<T, U extends TaggedStruct<Tag>>(array: T[] | undefined, shareElement: (value: T) => U): SharedArray<U> | undefined {
if (array) {
const sharedArray = new SharedArray<U>(array.length);
for (let i = 0; i < array.length; i++) {
sharedArray[i] = shareElement(array[i]);
}
return sharedArray;
}
}
/** @internal */
export type PragmaDiagnosticReporter = (pos: number, length: number, message: DiagnosticMessage) => void;
function shareFileReference(ref: FileReference) {
return new SharedFileReference(ref.pos, ref.end, ref.fileName, ref.resolutionMode);
}
function shareAmdDependency(dep: AmdDependency) {
return new SharedAmdDependency(dep.path, dep.name);
}
function createFileReference(pos: number, end: number, fileName: string, resolutionMode?: ResolutionMode): FileReference {
return { pos, end, fileName, ...(resolutionMode !== undefined ? { resolutionMode } : {}) };
}
function createAmdDependency(path: string, name?: string): AmdDependency {
return { path, name };
}
function createCheckJsDirective(pos: number, end: number, enabled: boolean) {
return { pos, end, enabled };
}
/** @internal */
export function processPragmasIntoFields(context: PragmaContext, reportDiagnostic: PragmaDiagnosticReporter): void {
context.checkJsDirective = undefined;
context.referencedFiles = [];
context.typeReferenceDirectives = [];
context.libReferenceDirectives = [];
context.amdDependencies = [];
context.hasNoDefaultLib = false;
context.pragmas!.forEach((entryOrList, key) => { // TODO: GH#18217
const referencedFiles: FileReference[] = [];
const typeReferenceDirectives: FileReference[] = [];
const libReferenceDirectives: FileReference[] = [];
const amdDependencies: AmdDependency[] = [];
let checkJsDirective: CheckJsDirective | undefined;
let pragmas;
if (isShareableNonPrimitive(context)) {
const contextPragmas = context.pragmas as unknown as SharedMap<string, SharedArray<SharedPragma> | SharedPragma>;
pragmas = SharedMap.entries(contextPragmas);
}
else {
pragmas = context.pragmas!.entries();
}
for (const [key, entryOrList] of pragmas) {
// TODO: The below should be strongly type-guarded and not need casts/explicit annotations, since entryOrList is related to
// key and key is constrained to a union; but it's not (see GH#21483 for at least partial fix) :(
switch (key) {
case "reference": {
const referencedFiles = context.referencedFiles;
const typeReferenceDirectives = context.typeReferenceDirectives;
const libReferenceDirectives = context.libReferenceDirectives;
forEach(toArray(entryOrList) as PragmaPseudoMap["reference"][], arg => {
const { types, lib, path, ["resolution-mode"]: res } = arg.arguments;
if (arg.arguments["no-default-lib"]) {
@ -10416,13 +10614,13 @@ export function processPragmasIntoFields(context: PragmaContext, reportDiagnosti
}
else if (types) {
const parsed = parseResolutionMode(res, types.pos, types.end, reportDiagnostic);
typeReferenceDirectives.push({ pos: types.pos, end: types.end, fileName: types.value, ...(parsed ? { resolutionMode: parsed } : {}) });
typeReferenceDirectives.push(createFileReference(types.pos, types.end, types.value, parsed));
}
else if (lib) {
libReferenceDirectives.push({ pos: lib.pos, end: lib.end, fileName: lib.value });
libReferenceDirectives.push(createFileReference(lib.pos, lib.end, lib.value));
}
else if (path) {
referencedFiles.push({ pos: path.pos, end: path.end, fileName: path.value });
referencedFiles.push(createFileReference(path.pos, path.end, path.value));
}
else {
reportDiagnostic(arg.range.pos, arg.range.end - arg.range.pos, Diagnostics.Invalid_reference_directive_syntax);
@ -10431,9 +10629,9 @@ export function processPragmasIntoFields(context: PragmaContext, reportDiagnosti
break;
}
case "amd-dependency": {
context.amdDependencies = map(
toArray(entryOrList) as PragmaPseudoMap["amd-dependency"][],
x => ({ name: x.arguments.name, path: x.arguments.path }));
for (const x of toArray(entryOrList) as PragmaPseudoMap["amd-dependency"][]) {
amdDependencies.push(createAmdDependency(x.arguments.path, x.arguments.name));
}
break;
}
case "amd-module": {
@ -10455,12 +10653,8 @@ export function processPragmasIntoFields(context: PragmaContext, reportDiagnosti
case "ts-check": {
// _last_ of either nocheck or check in a file is the "winner"
forEach(toArray(entryOrList), entry => {
if (!context.checkJsDirective || entry.range.pos > context.checkJsDirective.pos) {
context.checkJsDirective = {
enabled: key === "ts-check",
end: entry.range.end,
pos: entry.range.pos
};
if (!checkJsDirective || entry.range.pos > checkJsDirective.pos) {
checkJsDirective = createCheckJsDirective(entry.range.pos, entry.range.end, key === "ts-check");
}
});
break;
@ -10472,7 +10666,24 @@ export function processPragmasIntoFields(context: PragmaContext, reportDiagnosti
return; // Accessed directly
default: Debug.fail("Unhandled pragma kind"); // Can this be made into an assertNever in the future?
}
});
}
if (isShareableNonPrimitive(context)) {
if (checkJsDirective) {
context.checkJsDirective = new SharedCheckJsDirective(checkJsDirective.pos, checkJsDirective.end, checkJsDirective.enabled);
}
context.referencedFiles = shareArray(referencedFiles, shareFileReference) as unknown as FileReference[];
context.typeReferenceDirectives = shareArray(typeReferenceDirectives, shareFileReference) as unknown as FileReference[];
context.libReferenceDirectives = shareArray(libReferenceDirectives, shareFileReference) as unknown as FileReference[];
context.amdDependencies = shareArray(amdDependencies, shareAmdDependency) as unknown as AmdDependency[];
}
else {
context.checkJsDirective = checkJsDirective;
context.referencedFiles = referencedFiles;
context.typeReferenceDirectives = typeReferenceDirectives;
context.libReferenceDirectives = libReferenceDirectives;
context.amdDependencies = amdDependencies;
}
}
const namedArgRegExCache = new Map<string, RegExp>();

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,356 @@
import { hasProperty } from "../../core";
import { Debug } from "../../debug";
import { sys } from "../../sys";
import { IdentifiableStruct } from "../structs/identifiableStruct";
import { Shared, SharedStructBase } from "../structs/sharedStruct";
import { xxh32string } from "./xxhash32";
const seed = sys.stringSeed ?? Math.floor(Math.random() * 0xffffffff) >>> 0;
function hash(value: Shareable) {
if (value === undefined || value === null) { // eslint-disable-line no-null/no-null -- necessary comparison
return 0;
}
switch (typeof value) {
case "number":
return value >> 0;
case "boolean":
return value ? 1 : 0;
case "string":
return xxh32string(value, seed);
case "object":
if (hasProperty(value, "__hash__")) {
return (value as IdentifiableStruct).__hash__ >> 0;
}
return 0;
default:
Debug.assertNever(value);
}
}
// Portions of the following are derived from esfx and .NET Core. See ThirdPartyNoticeText.txt in the root of this
// repository for their respective license notices.
const MAX_INT32 = (2 ** 31) - 1;
const maxPrimeArrayLength = 2146435069;
const hashPrime = 101;
// Table of prime numbers to use as hash table sizes.
// A typical resize algorithm would pick the smallest prime number in this array
// that is larger than twice the previous capacity.
// Suppose our Hashtable currently has capacity x and enough elements are added
// such that a resize needs to occur. Resizing first computes 2x then finds the
// first prime in the table greater than 2x, i.e. if primes are ordered
// p_1, p_2, ..., p_i, ..., it finds p_n such that p_n-1 < 2x < p_n.
// Doubling is important for preserving the asymptotic complexity of the
// hashtable operations such as add. Having a prime guarantees that double
// hashing does not lead to infinite loops. IE, your hash function will be
// h1(key) + i*h2(key), 0 <= i < size. h2 and the size must be relatively prime.
// We prefer the low computation costs of higher prime numbers over the increased
// memory allocation of a fixed prime number i.e. when right sizing a HashSet.
const primes: readonly number[] = [
3, 7, 11, 17,
23, 29, 37, 47,
59, 71, 89, 107,
131, 163, 197, 239,
293, 353, 431, 521,
631, 761, 919, 1103,
1327, 1597, 1931, 2333,
2801, 3371, 4049, 4861,
5839, 7013, 8419, 10103,
12143, 14591, 17519, 21023,
25229, 30293, 36353, 43627,
52361, 62851, 75431, 90523,
108631, 130363, 156437, 187751,
225307, 270371, 324449, 389357,
467237, 560689, 672827, 807403,
968897, 1162687, 1395263, 1674319,
2009191, 2411033, 2893249, 3471899,
4166287, 4999559, 5999471, 7199369
];
function isPrime(candidate: number) {
if (candidate & 1) {
const limit = Math.sqrt(candidate) | 0;
for (let divisor = 3; divisor <= limit; divisor += 2) {
if (!(candidate % divisor)) return false;
}
return true;
}
return candidate === 2;
}
function getPrime(min: number) {
if (min < 0) throw new RangeError();
for (const prime of primes) {
if (prime >= min) return prime;
}
for (let i = min | 1; i < MAX_INT32; i += 2) {
if (isPrime(i) && (i - 1) % hashPrime) {
return i;
}
}
return min;
}
function expandPrime(oldSize: number) {
const newSize = 2 * oldSize;
// Allow the hashtables to grow to maximum possible size (~2G elements) before encountering capacity overflow.
// Note that this check works even when _items.Length overflowed thanks to the (uint) cast
if (newSize > maxPrimeArrayLength && maxPrimeArrayLength > oldSize) {
return maxPrimeArrayLength;
}
return getPrime(newSize);
}
/** @internal */
@Shared()
export class HashEntry<K extends Shareable, V extends Shareable> extends SharedStructBase {
@Shared() next = 0;
@Shared() prevEntry: HashEntry<K, V> | undefined;
@Shared() nextEntry: HashEntry<K, V> | undefined;
@Shared() skipNextEntry = false;
@Shared() hashCode = 0;
@Shared() key!: K;
@Shared() value!: V;
}
/** @internal */
@Shared()
export class HashData<K extends Shareable, V extends Shareable> extends SharedStructBase {
@Shared() buckets: SharedArray<number>;
@Shared() entries: SharedArray<HashEntry<K, V>>;
@Shared() freeSize = 0;
@Shared() freeList = -1;
@Shared() size = 0;
@Shared() head: HashEntry<K, V>;
@Shared() tail: HashEntry<K, V>;
constructor(capacity: number) {
super();
capacity = getPrime(capacity);
this.buckets = HashData.fill(new SharedArray(capacity), 0);
this.entries = new SharedArray(capacity);
this.head = new HashEntry<K, V>();
this.tail = this.head;
}
private static fill<T extends Shareable>(array: SharedArray<T>, value: T) {
for (let i = 0; i < array.length; i++) {
array[i] = value;
}
return array;
}
private static resizeSharedArray<T extends Shareable>(array: SharedArray<T>, newSize: number) {
if (array.length === newSize) return array;
const newArray = new SharedArray<T>(newSize);
const minSize = Math.min(array.length, newSize);
for (let i = 0; i < minSize; i++) {
newArray[i] = array[i];
}
return newArray;
}
private static resizeHashData<K extends Shareable, V extends Shareable>(hashData: HashData<K, V>, newSize: number) {
const size = hashData.size;
const buckets = HashData.fill(new SharedArray<number>(newSize), 0);
const entries = HashData.resizeSharedArray(hashData.entries, newSize);
for (let i = 0; i < size; i++) {
const entry = entries[i];
if (entry && entry.hashCode >= 0) {
const bucket = entry.hashCode % newSize;
// Value in buckets is 1-based
entry.next = buckets[bucket] - 1;
// Value in buckets is 1-based
buckets[bucket] = i + 1;
}
}
hashData.buckets = buckets;
hashData.entries = entries;
}
static findEntryIndex<K extends Shareable, V extends Shareable>(hashData: HashData<K, V>, key: K) {
const hashCode = hash(key) & MAX_INT32;
// Value in _buckets is 1-based
let i = hashData.buckets[hashCode % hashData.buckets.length] - 1;
const length = hashData.entries.length;
while ((i >>> 0) < length) {
const entry = hashData.entries[i];
if (entry.hashCode === hashCode && entry.key === key) {
break;
}
i = entry.next;
}
return i;
}
static findEntryValue<K extends Shareable, V extends Shareable>(hashData: HashData<K, V>, key: K) {
const index = HashData.findEntryIndex(hashData, key);
return index >= 0 ? hashData.entries[index].value : undefined;
}
static insertEntry<K extends Shareable, V extends Shareable>(hashData: HashData<K, V>, key: K, value: V) {
const hashCode = hash(key) & MAX_INT32;
let bucket = hashCode % hashData.buckets.length;
// Value in _buckets is 1-based
let i = hashData.buckets[bucket] - 1;
while ((i >>> 0) < hashData.entries.length) {
const entry = hashData.entries[i];
if (entry.hashCode === hashCode && entry.key === key) {
entry.value = value;
return;
}
i = entry.next;
}
let updateFreeList = false;
let index: number;
if (hashData.freeSize > 0) {
index = hashData.freeList;
updateFreeList = true;
hashData.freeSize--;
}
else {
const size = hashData.size;
if (size === hashData.entries.length) {
HashData.resizeHashData(hashData, expandPrime(hashData.size));
bucket = hashCode % hashData.buckets.length;
}
index = size;
hashData.size = size + 1;
}
const entry = hashData.entries[index] ??= new HashEntry<K, V>();
if (updateFreeList) hashData.freeList = entry.next;
entry.hashCode = hashCode;
// Value in _buckets is 1-based
entry.next = hashData.buckets[bucket] - 1;
entry.key = key;
entry.value = value;
entry.skipNextEntry = false;
const tail = hashData.tail;
tail.nextEntry = entry;
entry.prevEntry = tail;
hashData.tail = entry;
// Value in _buckets is 1-based
hashData.buckets[bucket] = index + 1;
}
static deleteEntry<K extends Shareable, V extends Shareable>(hashData: HashData<K, V>, key: K) {
const hashCode = hash(key) & MAX_INT32;
const bucket = hashCode % hashData.buckets.length;
let last = -1;
let entry: HashEntry<K, V> | undefined;
// Value in _buckets is 1-based
for (let i = hashData.buckets[bucket] - 1; i >= 0; i = entry.next) {
entry = hashData.entries[i];
if (entry.hashCode === hashCode && entry.key === key) {
if (last < 0) {
// Value in _buckets is 1-based
hashData.buckets[bucket] = entry.next + 1;
}
else {
hashData.entries[last]!.next = entry.next;
}
const prevEntry = entry.prevEntry!;
prevEntry.nextEntry = entry.nextEntry;
if (prevEntry.nextEntry) {
prevEntry.nextEntry.prevEntry = prevEntry;
}
if (hashData.tail === entry) {
hashData.tail = prevEntry;
}
entry.hashCode = -1;
entry.next = hashData.freeList;
entry.key = undefined!;
entry.value = undefined!;
entry.prevEntry = undefined;
entry.nextEntry = prevEntry;
entry.skipNextEntry = true;
hashData.freeList = i;
hashData.freeSize++;
return true;
}
last = i;
}
return false;
}
static clearEntries<K extends Shareable, V extends Shareable>(hashData: HashData<K, V>) {
const size = hashData.size;
if (size > 0) {
for (let i = 0; i < hashData.buckets.length; i++) hashData.buckets[i] = 0;
for (let i = 0; i < hashData.entries.length; i++) hashData.entries[i] = undefined!;
let currentEntry = hashData.head.nextEntry;
while (currentEntry) {
const nextEntry = currentEntry.nextEntry;
currentEntry.prevEntry = undefined;
currentEntry.nextEntry = hashData.head;
currentEntry.skipNextEntry = true;
currentEntry = nextEntry;
}
hashData.head.nextEntry = undefined;
hashData.tail = hashData.head;
hashData.size = 0;
hashData.freeList = -1;
hashData.freeSize = 0;
}
}
static ensureCapacity<K extends Shareable, V extends Shareable>(hashData: HashData<K, V>, capacity: number) {
if (capacity < 0) throw new RangeError();
const existingCapacity = hashData.entries.length;
if (existingCapacity >= capacity) return existingCapacity;
const newCapacity = getPrime(capacity);
HashData.resizeHashData(hashData, newCapacity);
return newCapacity;
}
static trimExcessEntries<K extends Shareable, V extends Shareable>(hashData: HashData<K, V>, capacity = hashData.size - hashData.freeSize) {
if (capacity < hashData.size) throw new RangeError(); // TODO
const newCapacity = getPrime(capacity);
const existingEntries = hashData.entries;
if (newCapacity >= existingEntries.length) return;
const oldSize = hashData.size;
hashData.freeList = -1;
hashData.buckets = new SharedArray(newCapacity);
hashData.entries = new SharedArray(newCapacity);
let newSize = 0;
for (let i = 0; i < oldSize; i++) {
const hashCode = existingEntries[i].hashCode;
if (hashCode >= 0) {
const bucket = hashCode % newCapacity;
hashData.entries[newSize] = existingEntries[i];
// Value in _buckets is 1-based
hashData.entries[newSize].next = hashData.buckets[bucket] - 1;
// Value in _buckets is 1-based
hashData.buckets[bucket] = newSize + 1;
newSize++;
}
}
hashData.size = newSize;
hashData.freeSize = 0;
}
static * iterateEntries<K extends Shareable, V extends Shareable, R>(head: HashEntry<K, V>, selector: (entry: HashEntry<K, V>) => R) {
let currentEntry: HashEntry<K, V> | undefined = head;
while (currentEntry) {
const skipNextEntry = currentEntry.skipNextEntry;
currentEntry = currentEntry.nextEntry;
if (skipNextEntry) continue;
if (currentEntry) yield selector(currentEntry);
}
}
static selectEntryKey<K extends Shareable, V extends Shareable>(entry: HashEntry<K, V>) {
return entry.key;
}
static selectEntryValue<K extends Shareable, V extends Shareable>(entry: HashEntry<K, V>) {
return entry.value;
}
static selectEntryEntry<K extends Shareable, V extends Shareable>(entry: HashEntry<K, V>) {
return [entry.key, entry.value] as [K, V];
}
}

View File

@ -0,0 +1,218 @@
import { Debug } from "../../debug";
import { Shared, SharedStructBase } from "../structs/sharedStruct";
/**
* A shareable data structure that represents a doubly-linked list.
* @internal
*/
@Shared()
export class SharedLinkedList<T extends Shareable> extends SharedStructBase {
@Shared() head: SharedLinkedListNode<T> | undefined;
@Shared() size: number;
constructor(iterable?: Iterable<T>) {
super();
this.head = undefined;
this.size = 0;
if (iterable) {
for (const value of iterable) {
SharedLinkedList.push(this, value);
}
}
}
static size(list: SharedLinkedList<any>) {
return list.size;
}
static firstNode<T extends Shareable>(list: SharedLinkedList<T>) {
return list.head;
}
static lastNode<T extends Shareable>(list: SharedLinkedList<T>) {
return list.head?.prev;
}
static previousNode<T extends Shareable>(node: SharedLinkedListNode<T>) {
if (node.list && node !== node.list.head) {
return node.prev;
}
}
static nextNode<T extends Shareable>(node: SharedLinkedListNode<T>) {
if (node.list && node !== node.list.head?.prev) {
return node.next;
}
}
static insertBeforeNode<T extends Shareable>(list: SharedLinkedList<T>, contextNode: SharedLinkedListNode<T> | undefined, newNode: SharedLinkedListNode<T>) {
Debug.assert(!contextNode || contextNode.list === list);
Debug.assert(!newNode.list);
newNode.list = list;
if (list.head === undefined) {
newNode.next = newNode;
newNode.prev = newNode;
list.head = newNode;
}
else {
if (contextNode === undefined) {
contextNode = list.head;
list.head = newNode;
}
else if (contextNode === list.head) {
list.head = newNode;
}
newNode.next = contextNode;
newNode.prev = contextNode.prev;
contextNode.prev.next = newNode;
contextNode.prev = newNode;
}
list.size++;
return newNode;
}
static insertAfterNode<T extends Shareable>(list: SharedLinkedList<T>, contextNode: SharedLinkedListNode<T> | undefined, newNode: SharedLinkedListNode<T>) {
Debug.assert(!contextNode || contextNode.list === list);
Debug.assert(!newNode.list);
newNode.list = list;
if (list.head === undefined) {
newNode.next = newNode;
newNode.prev = newNode;
list.head = newNode;
}
else {
if (contextNode === undefined) {
contextNode = list.head.prev!;
}
newNode.prev = contextNode;
newNode.next = contextNode.next;
contextNode.next.prev = newNode;
contextNode.next = newNode;
}
list.size++;
return newNode;
}
static pushNode<T extends Shareable>(list: SharedLinkedList<T>, node: SharedLinkedListNode<T>) {
Debug.assert(!node.list);
return this.insertAfterNode(list, /*contextNode*/ undefined, node);
}
static push<T extends Shareable>(list: SharedLinkedList<T>, value: T) {
return this.pushNode(list, new SharedLinkedListNode(value));
}
static popNode<T extends Shareable>(list: SharedLinkedList<T>): SharedLinkedListNode<T> | undefined {
const node = list.head?.prev;
if (node && this.deleteNode(list, node)) {
return node;
}
}
static pop<T extends Shareable>(list: SharedLinkedList<T>): T | undefined {
return this.popNode(list)?.value;
}
static unshiftNode<T extends Shareable>(list: SharedLinkedList<T>, node: SharedLinkedListNode<T>) {
Debug.assert(!node.list);
return this.insertBeforeNode(list, /*contextNode*/ undefined, node);
}
static unshift<T extends Shareable>(list: SharedLinkedList<T>, value: T) {
return this.unshiftNode(list, new SharedLinkedListNode(value));
}
static shiftNode<T extends Shareable>(list: SharedLinkedList<T>): SharedLinkedListNode<T> | undefined {
const node = list.head;
if (node && this.deleteNode(list, node)) {
return node;
}
}
static shift<T extends Shareable>(list: SharedLinkedList<T>): T | undefined {
return this.shiftNode(list)?.value;
}
static deleteNode<T extends Shareable>(list: SharedLinkedList<T>, node: SharedLinkedListNode<T>) {
if (node.list !== list) {
return undefined;
}
node.list = undefined;
if (node.next === node) {
Debug.assert(list.head === node);
list.head = undefined;
}
else {
node.next.prev = node.prev;
node.prev.next = node.next;
if (list.head === node) {
list.head = node.next;
}
}
node.next = node;
node.prev = node;
list.size--;
return node;
}
static clear<T extends Shareable>(list: SharedLinkedList<T>) {
while (list.head?.prev) {
this.deleteNode(list, list.head.prev);
}
}
static * nodes<T extends Shareable>(list: SharedLinkedList<T>) {
let node: SharedLinkedListNode<T>;
let next = list.head;
if (next) {
do {
node = next;
next = node.next;
yield node;
}
while (next !== list.head);
}
}
static * values<T extends Shareable>(list: SharedLinkedList<T>) {
let node: SharedLinkedListNode<T>;
let next = list.head;
if (next) {
do {
node = next;
next = node.next;
yield node.value;
}
while (next !== list.head);
}
}
}
/** @internal */
@Shared()
export class SharedLinkedListNode<T extends Shareable> extends SharedStructBase {
@Shared() list: SharedLinkedList<T> | undefined;
@Shared() prev: SharedLinkedListNode<T>;
@Shared() next: SharedLinkedListNode<T>;
@Shared() value: T;
constructor(value: T) {
super();
this.list = undefined;
this.prev = this;
this.next = this;
this.value = value;
}
static previous<T extends Shareable>(node: SharedLinkedListNode<T>) {
if (node.list && node !== node.list.head) {
return node.prev;
}
}
static next<T extends Shareable>(node: SharedLinkedListNode<T>) {
if (node.list && node !== node.list.head?.prev) {
return node.next;
}
}
}

View File

@ -0,0 +1,59 @@
import { Identifiable } from "../structs/identifiableStruct";
import { Shared, SharedStructBase } from "../structs/sharedStruct";
import { Tag, Tagged } from "../structs/taggedStruct";
import { HashData } from "./hashData";
@Shared()
export class SharedMap<K extends Shareable, V extends Shareable> extends Identifiable(Tagged(SharedStructBase, Tag.Map)) {
@Shared() private _hashData: HashData<K, V>;
constructor(capacity = 0) {
super();
this._hashData = new HashData(capacity);
}
static size<K extends Shareable, V extends Shareable>(self: SharedMap<K, V>) {
return self._hashData.size - self._hashData.freeSize;
}
static has<K extends Shareable, V extends Shareable>(self: SharedMap<K, V>, key: K) {
return HashData.findEntryIndex(self._hashData, key) >= 0;
}
static get<K extends Shareable, V extends Shareable>(self: SharedMap<K, V>, key: K) {
return HashData.findEntryValue(self._hashData, key);
}
static set<K extends Shareable, V extends Shareable>(self: SharedMap<K, V>, key: K, value: V) {
HashData.insertEntry(self._hashData, key, value);
return self;
}
static delete<K extends Shareable, V extends Shareable>(self: SharedMap<K, V>, key: K) {
return HashData.deleteEntry(self._hashData, key);
}
static clear<K extends Shareable, V extends Shareable>(self: SharedMap<K, V>) {
HashData.clearEntries(self._hashData);
}
static ensureCapacity<K extends Shareable, V extends Shareable>(self: SharedMap<K, V>, capacity: number) {
return HashData.ensureCapacity(self._hashData, capacity);
}
static trimExcess<K extends Shareable, V extends Shareable>(self: SharedMap<K, V>, capacity?: number) {
HashData.trimExcessEntries(self._hashData, capacity);
}
static keys<K extends Shareable, V extends Shareable>(self: SharedMap<K, V>) {
return HashData.iterateEntries(self._hashData.head, HashData.selectEntryKey);
}
static values<K extends Shareable, V extends Shareable>(self: SharedMap<K, V>) {
return HashData.iterateEntries(self._hashData.head, HashData.selectEntryValue);
}
static entries<K extends Shareable, V extends Shareable>(self: SharedMap<K, V>) {
return HashData.iterateEntries(self._hashData.head, HashData.selectEntryEntry);
}
}

View File

@ -0,0 +1,47 @@
import { Identifiable } from "../structs/identifiableStruct";
import { Shared, SharedStructBase } from "../structs/sharedStruct";
import { Tag, Tagged } from "../structs/taggedStruct";
// TODO: consider constructing ropes of shared arrays rather than reallocating and copying.
/**
* The default `SharedArray` implementation depends on a fixed-length array. A ResizableSharedArray is an abstraction
* over `SharedArray` that allows us to emulate resizing.
* @internal
*/
@Shared()
export class SharedResizableArray<T extends Shareable> extends Identifiable(Tagged(SharedStructBase, Tag.ResizableArray)) {
@Shared() items: SharedArray<T>;
constructor(initialSize = 0) {
super();
this.items = new SharedArray(initialSize);
}
static size<T extends Shareable>(self: SharedResizableArray<T>) {
return self.items.length;
}
static get<T extends Shareable>(self: SharedResizableArray<T>, index: number): T {
return self.items[index];
}
static set<T extends Shareable>(self: SharedResizableArray<T>, index: number, value: T): SharedResizableArray<T> {
if (index >= self.items.length) {
this.resize(self, index + 1);
}
self.items[index] = value;
return self;
}
static resize<T extends Shareable>(self: SharedResizableArray<T>, newLength: number) {
if (self.items.length !== newLength) {
const newArray = new SharedArray<T>(newLength);
const minSize = Math.min(self.items.length, newLength);
for (let i = 0; i < minSize; i++) {
newArray[i] = self.items[i];
}
}
return self;
}
}

View File

@ -0,0 +1,55 @@
import { Identifiable } from "../structs/identifiableStruct";
import { Shared, SharedStructBase } from "../structs/sharedStruct";
import { Tag, Tagged } from "../structs/taggedStruct";
import { HashData } from "./hashData";
@Shared()
export class SharedSet<T extends Shareable> extends Identifiable(Tagged(SharedStructBase, Tag.Set)) {
@Shared() private _hashData: HashData<T, T>;
constructor(capacity = 0) {
super();
this._hashData = new HashData(capacity);
}
static size<T extends Shareable>(self: SharedSet<T>) {
return self._hashData.size - self._hashData.freeSize;
}
static has<T extends Shareable>(self: SharedSet<T>, value: T) {
return HashData.findEntryIndex(self._hashData, value) >= 0;
}
static add<T extends Shareable>(self: SharedSet<T>, value: T) {
HashData.insertEntry(self._hashData, value, value);
return self;
}
static delete<T extends Shareable>(self: SharedSet<T>, value: T) {
return HashData.deleteEntry(self._hashData, value);
}
static clear<T extends Shareable>(self: SharedSet<T>) {
HashData.clearEntries(self._hashData);
}
static ensureCapacity<T extends Shareable>(self: SharedSet<T>, capacity: number) {
return HashData.ensureCapacity(self._hashData, capacity);
}
static trimExcess<T extends Shareable>(self: SharedSet<T>, capacity?: number) {
HashData.trimExcessEntries(self._hashData, capacity);
}
static keys<T extends Shareable>(self: SharedSet<T>) {
return HashData.iterateEntries(self._hashData.head, HashData.selectEntryKey);
}
static values<T extends Shareable>(self: SharedSet<T>) {
return HashData.iterateEntries(self._hashData.head, HashData.selectEntryValue);
}
static entries<T extends Shareable>(self: SharedSet<T>) {
return HashData.iterateEntries(self._hashData.head, HashData.selectEntryEntry);
}
}

View File

@ -0,0 +1,86 @@
import { utf8encodeInto } from "../../utilities";
const PRIME32_1 = 0x9e3779b1;
const PRIME32_2 = 0x85ebca77;
const PRIME32_3 = 0xc2b2ae3d;
const PRIME32_4 = 0x27d4eb2f;
const PRIME32_5 = 0x165667b1;
/**
* Fast non-cryptographic hashing algorithm with good avalanche properties.
* https://xxhash.com/
* @internal
*/
export function xxh32(buffer: ArrayBuffer, inputPtr: number, inputLength: number, seed: number): number {
if (inputPtr % 4) throw new TypeError("Pointer not aligned");
const u32buffer = new Uint32Array(buffer);
const end = inputPtr + inputLength;
let acc: number;
let limit: number;
let v1: number;
let v2: number;
let v3: number;
let v4: number;
// translate ptr to u32 offset
inputPtr >>= 2;
if (inputLength >= 16) {
limit = (end - 16) >> 2;
v1 = (((seed + PRIME32_1) | 0) + PRIME32_2) | 0;
v2 = (seed + PRIME32_2) | 0;
v3 = (seed + 0) | 0;
v4 = (seed + PRIME32_1) | 0;
do {
v1 = (v1 + (u32buffer[inputPtr++] * PRIME32_2) | 0) | 0;
v1 = (((v1 << 13) | (v1 >>> 19)) * PRIME32_1) | 0;
v2 = (v2 + (u32buffer[inputPtr++] * PRIME32_2) | 0) | 0;
v2 = (((v2 << 13) | (v2 >>> 19)) * PRIME32_1) | 0;
v3 = (v3 + (u32buffer[inputPtr++] * PRIME32_2) | 0) | 0;
v3 = (((v3 << 13) | (v3 >>> 19)) * PRIME32_1) | 0;
v4 = (v4 + (u32buffer[inputPtr++] * PRIME32_2) | 0) | 0;
v4 = (((v4 << 13) | (v4 >>> 19)) * PRIME32_1) | 0;
}
while (inputPtr <= limit);
acc = (v1 << 1 | v1 >>> 31) + (v2 << 7 | v2 >>> 25) | (v3 << 12 | v3 >>> 20) | (v4 << 18 | v4 >>> 14);
}
else {
acc = (seed + PRIME32_5) | 0;
}
acc = (acc + inputLength) | 0;
limit = (end - 4) >> 2;
while (inputPtr <= limit) {
acc = (acc + (u32buffer[inputPtr++] * PRIME32_3) | 0) | 0;
acc = ((acc << 17 | acc >>> 15) * PRIME32_4) | 0;
}
// translate ptr to byte offset
inputPtr = inputPtr << 2;
if (inputPtr < end) {
const u8buffer = new Uint8Array(u32buffer.buffer);
do {
acc = (acc + (u8buffer[inputPtr++] * PRIME32_5) | 0) | 0;
acc = ((acc << 11 | acc >>> 21) * PRIME32_1) | 0;
}
while (inputPtr < end);
}
acc = ((acc ^ (acc >>> 15)) * PRIME32_2) | 0;
acc = ((acc ^ (acc >>> 13)) * PRIME32_3) | 0;
acc = acc ^ (acc >>> 16);
return acc >>> 0;
}
let memory: Uint8Array | undefined;
function ensureCapacity(size: number) {
if (!memory || memory.byteLength < size) {
memory = new Uint8Array(size + (65536 - size % 65536));
}
return memory;
}
/**
* @internal
*/
export function xxh32string(x: string, seed: number) {
const memory = ensureCapacity(x.length * 3);
const written = utf8encodeInto(x, memory);
return xxh32(memory.buffer, 0, written, seed);
}

View File

@ -0,0 +1,132 @@
import { CompilerOptions, DiagnosticCategory } from "../types";
import { SharedSourceFile } from "./sharedNode";
import { Shared, SharedStructBase } from "./structs/sharedStruct";
import { Tag, Tagged } from "./structs/taggedStruct";
/** @internal */
@Shared()
export class SharedDiagnosticMessage extends SharedStructBase {
@Shared() key: string;
@Shared() category: DiagnosticCategory;
@Shared() code: number;
@Shared() message: string;
@Shared() reportsUnnecessary?: boolean;
@Shared() reportsDeprecated?: boolean;
@Shared() elidedInCompatabilityPyramid?: boolean | undefined;
constructor(
key: string,
category: DiagnosticCategory,
code: number,
message: string,
reportsUnnecessary?: {},
reportsDeprecated?: {},
elidedInCompatabilityPyramid?: boolean | undefined
) {
super();
this.key = key;
this.category = category;
this.code = code;
this.message = message;
this.reportsUnnecessary = !!reportsUnnecessary;
this.reportsDeprecated = !!reportsDeprecated;
this.elidedInCompatabilityPyramid = elidedInCompatabilityPyramid;
}
}
/** @internal */
@Shared()
export class SharedDiagnosticMessageChain extends Tagged(SharedStructBase, Tag.DiagnosticMessageChain) {
@Shared() messageText: string;
@Shared() category: DiagnosticCategory;
@Shared() code: number;
@Shared() next?: SharedArray<SharedDiagnosticMessageChain>;
// does this need to be shared somehow?
// repopulateInfo?: () => RepopulateDiagnosticChainInfo;
constructor(messageText: string, category: DiagnosticCategory, code: number, next?: SharedArray<SharedDiagnosticMessageChain>) {
super();
this.messageText = messageText;
this.category = category;
this.code = code;
this.next = next;
}
}
/** @internal */
@Shared()
export class SharedDiagnosticRelatedInformation extends Tagged(SharedStructBase, Tag.DiagnosticRelatedInformation) {
@Shared() category: DiagnosticCategory;
@Shared() code: number;
@Shared() file: SharedSourceFile | undefined;
@Shared() start: number | undefined;
@Shared() length: number | undefined;
@Shared() messageText: string | SharedDiagnosticMessageChain;
constructor(
category: DiagnosticCategory,
code: number,
file: SharedSourceFile | undefined,
start: number | undefined,
length: number | undefined,
messageText: string | SharedDiagnosticMessageChain
) {
super();
this.category = category;
this.code = code;
this.file = file;
this.start = start;
this.length = length;
this.messageText = messageText;
}
}
/** @internal */
@Shared()
export class SharedDiagnostic extends Tagged(SharedDiagnosticRelatedInformation, Tag.Diagnostic) {
@Shared() reportsUnnecessary?: boolean;
@Shared() reportsDeprecated?: boolean;
@Shared() source?: string;
@Shared() relatedInformation?: SharedArray<SharedDiagnosticRelatedInformation | SharedDiagnostic>;
@Shared() skippedOn?: keyof CompilerOptions;
}
/** @internal */
@Shared()
export class SharedDiagnosticAndArguments extends SharedStructBase {
@Shared() message: SharedDiagnosticMessage;
@Shared() args: SharedArray<string | number>;
constructor(message: SharedDiagnosticMessage, args: SharedArray<string | number>) {
super();
this.message = message;
this.args = args;
}
}
/** @internal */
export interface SharedDiagnosticWithLocation extends SharedDiagnostic {
file: SharedSourceFile;
start: number;
length: number;
}
/** @internal */
@Shared()
export class SharedDiagnosticWithDetachedLocation extends SharedDiagnostic {
@Shared() fileName: string;
declare file: undefined;
constructor(
category: DiagnosticCategory,
code: number,
file: SharedSourceFile | undefined,
start: number | undefined,
length: number | undefined,
messageText: string | SharedDiagnosticMessageChain,
fileName: string
) {
super(category, code, file, start, length, messageText);
this.fileName = fileName;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,29 @@
import { TransformFlags } from "../types";
import type { SharedNodeBase } from "./sharedNode";
import { Identifiable } from "./structs/identifiableStruct";
import { isShareableNonPrimitive } from "./structs/shareable";
import { Shared, SharedStructBase } from "./structs/sharedStruct";
import { isTaggedStruct, Tag, Tagged } from "./structs/taggedStruct";
/** @internal */
@Shared()
export class SharedNodeArray<T extends SharedNodeBase> extends Identifiable(Tagged(SharedStructBase, Tag.NodeArray)) {
@Shared() items!: SharedArray<T>;
@Shared() pos = -1;
@Shared() end = -1;
@Shared() hasTrailingComma = false;
@Shared() transformFlags = TransformFlags.None;
@Shared() isMissingList = false;
static * values<T extends SharedNodeBase>(self: SharedNodeArray<T>): IterableIterator<T> {
for (let i = 0; i < self.items.length; i++) {
yield self.items[i];
}
}
static [Symbol.hasInstance](value: unknown): value is SharedNodeArray<SharedNodeBase> {
return isShareableNonPrimitive(value) &&
isTaggedStruct(value) &&
value.__tag__ === Tag.NodeArray;
}
}

View File

@ -0,0 +1,37 @@
import { Identifier, Node, NodeArray, PrivateIdentifier, SourceFile, SyntaxKind, Token } from "../types";
import { ObjectAllocator, objectAllocator } from "../utilities";
import { getSharedConstructorForKind, SharedNodeBase } from "./sharedNode";
import { SharedNodeArray } from "./sharedNodeArray";
function NodeArray(items: readonly any[], hasTrailingComma?: boolean) {
const array = new SharedNodeArray();
array.items = new SharedArray(items.length);
for (let i = 0; i < items.length; i++) {
array.items[i] = items[i];
}
array.hasTrailingComma = !!hasTrailingComma;
return array;
}
function Node(kind: SyntaxKind, pos: number, end: number) {
const SharedNode = getSharedConstructorForKind(kind);
const node = new SharedNode() as SharedNodeBase;
node.kind = kind;
node.pos = pos;
node.end = end;
return node;
}
/** @internal */
export function getSharedObjectAllocator(): ObjectAllocator {
return {
...objectAllocator,
getNodeArrayConstructor: () => NodeArray as unknown as new <T extends Node>(items: readonly T[], hasTrailingComma?: boolean | undefined) => NodeArray<T>,
getNodeConstructor: () => Node as unknown as new (kind: SyntaxKind, pos: number, end: number) => Node,
getTokenConstructor: () => Node as unknown as new <Kind extends SyntaxKind>(kind: Kind, pos: number, end: number) => Token<Kind>,
getIdentifierConstructor: () => Node as unknown as new (kind: SyntaxKind.Identifier, pos: number, end: number) => Identifier,
getPrivateIdentifierConstructor: () => Node as unknown as new (kind: SyntaxKind.PrivateIdentifier, pos: number, end: number) => PrivateIdentifier,
getSourceFileConstructor: () => Node as unknown as new (kind: SyntaxKind.SourceFile, pos: number, end: number) => SourceFile,
// getSymbolConstructor: () => SymbolConstructor,
};
}

View File

@ -0,0 +1,51 @@
import { Condition } from "../threading/condition";
import { Mutex } from "../threading/mutex";
import { SharedMutex } from "../threading/sharedMutex";
import { ResolutionMode, ScriptTarget } from "../types";
import { SharedMap } from "./collections/sharedMap";
import { SharedSourceFile } from "./sharedNode";
import { Shared, SharedStructBase } from "./structs/sharedStruct";
/** @internal */
@Shared()
export class SharedParserState extends SharedStructBase {
@Shared() sharedMutex = new SharedMutex();
@Shared() files = new SharedMap<string, SharedSourceFileEntry>();
}
/** @internal */
@Shared()
export class SharedSourceFileEntry extends SharedStructBase {
@Shared() parserState: SharedParserState;
@Shared() setParentNodes: boolean;
@Shared() setFileVersion: boolean;
@Shared() fileName: string;
@Shared() languageVersion: ScriptTarget;
@Shared() impliedNodeFormat: ResolutionMode | undefined;
@Shared() shouldCreateNewSourceFile: boolean | undefined;
@Shared() done = false;
@Shared() error = false;
@Shared() file: SharedSourceFile | undefined;
@Shared() fileMutex = new Mutex();
@Shared() fileCondition = new Condition();
constructor(
parserState: SharedParserState,
setFileVersion: boolean,
setParentNodes: boolean,
fileName: string,
languageVersion: ScriptTarget,
impliedNodeFormat: ResolutionMode | undefined,
shouldCreateNewSourceFile: boolean | undefined,
) {
super();
this.parserState = parserState;
this.setFileVersion = setFileVersion;
this.setParentNodes = setParentNodes;
this.fileName = fileName;
this.languageVersion = languageVersion;
this.impliedNodeFormat = impliedNodeFormat;
this.shouldCreateNewSourceFile = shouldCreateNewSourceFile;
}
}

View File

@ -0,0 +1,36 @@
import { __String, SymbolFlags } from "../types";
import { SharedResizableArray } from "./collections/sharedResizableArray";
import { SharedMap } from "./collections/sharedMap";
import { Identifiable } from "./structs/identifiableStruct";
import { SharedDeclaration } from "./sharedNode";
import { Shared, SharedStructBase } from "./structs/sharedStruct";
import { Tag, Tagged } from "./structs/taggedStruct";
declare global {
interface OtherShareablePrimitive {
__String: __String;
}
}
/** @internal */
export interface SharedSymbolTable extends SharedMap<__String, SharedSymbol> {
}
/** @internal */
@Shared()
export class SharedSymbol extends Identifiable(Tagged(SharedStructBase, Tag.Symbol)) {
@Shared() flags!: SymbolFlags;
@Shared() escapedName!: __String;
@Shared() declarations: SharedResizableArray<SharedDeclaration> | undefined;
@Shared() valueDeclaration: SharedDeclaration | undefined;
@Shared() members: SharedSymbolTable | undefined;
@Shared() exports: SharedSymbolTable | undefined;
@Shared() globalExports: SharedSymbolTable | undefined;
@Shared() parent: SharedSymbol | undefined;
@Shared() exportSymbol: SharedSymbol | undefined;
@Shared() constEnumOnlyModule: boolean | undefined;
@Shared() isReferenced: SymbolFlags | undefined;
@Shared() isReplaceableByMethod: boolean | undefined;
@Shared() isAssigned: boolean | undefined;
@Shared() assignmentDeclarationMembers: SharedMap<number, SharedDeclaration> | undefined;
}

View File

@ -0,0 +1,145 @@
// Brand used to identify a fake shared struct, so that we can emulate the restriction that shared struct fields
import { isNull } from "../../core";
// can only contain shareable primitives and other shared structs.
const allFakeSharedStructInstances = new WeakSet<object>();
const allFakeSharedArrayInstances = new WeakSet<object>();
// Emulates the behavior of the Shared Structs origin trial, for forwards compatibility.
const proxyHandler: ProxyHandler<object> = {
set(target, p, value, receiver) {
switch (typeof value) {
case "undefined":
case "string":
case "number":
case "boolean":
break;
case "object":
if (isNull(value) || allFakeSharedStructInstances.has(value) || allFakeSharedArrayInstances.has(value)) { // eslint-disable-line no-null/no-null
break;
}
// falls through
default:
throw new TypeError("value not shareable");
}
return Reflect.set(target, p, value, receiver);
},
defineProperty(target, property, attributes) {
const { value } = attributes;
switch (typeof value) {
case "undefined":
case "string":
case "number":
case "boolean":
break;
case "object":
if (isNull(value) || allFakeSharedStructInstances.has(value) || allFakeSharedArrayInstances.has(value)) { // eslint-disable-line no-null/no-null
break;
}
// falls through
default:
throw new TypeError("value not shareable");
}
return Reflect.defineProperty(target, property, attributes);
},
};
/**
* Creates a new fake SharedStructType constructor that emulates fixed-shape object behavior, though the result can't
* actually be shared across threads. Fake shared structs are useful in single-threaded scenarios when SharedStructType
* isn't available and allows us to write single threaded code that is forwards-compatible with eventual multi-threaded
* code.
* @internal
*/
export const FakeSharedStructType = class FakeSharedStructType {
constructor(fields: readonly string[]) {
const thisFakeSharedStructInstances = new WeakSet<object>();
class FakeSharedStruct { // eslint-disable-line @typescript-eslint/no-unsafe-declaration-merging
constructor() {
// Fields must be explicitly defined and are not configurable.
for (const field of fields) {
Object.defineProperty(this, field, {
enumerable: false,
configurable: false,
writable: true,
value: undefined
});
}
Object.setPrototypeOf(this, null); // eslint-disable-line no-null/no-null
Object.preventExtensions(this);
const fakeSharedStruct = new Proxy(this, proxyHandler);
allFakeSharedStructInstances.add(fakeSharedStruct);
thisFakeSharedStructInstances.add(fakeSharedStruct);
return fakeSharedStruct as this;
}
static [Symbol.hasInstance](value: unknown) {
try {
return typeof value === "object" && !isNull(value) && thisFakeSharedStructInstances.has(value);
}
catch {
return false;
}
}
}
// augment the interface with the brand from `SharedStruct`.
interface FakeSharedStruct extends SharedStruct { // eslint-disable-line @typescript-eslint/no-unsafe-declaration-merging
}
Object.setPrototypeOf(FakeSharedStruct.prototype, null); // eslint-disable-line no-null/no-null
return FakeSharedStruct;
}
static isSharedStruct(value: unknown): value is SharedStruct {
try {
return typeof value === "object" && !isNull(value) && allFakeSharedStructInstances.has(value);
}
catch {
return false;
}
}
} as SharedStructTypeConstructor;
/**
* Creates a new fake SharedArray constructor that emulates fixed-shape object behavior, though the result can't
* actually be shared across threads. Fake shared structs are useful in single-threaded scenarios when SharedStructType
* isn't available and allows us to write single threaded code that is forwards-compatible with eventual multi-threaded
* code.
* @internal
*/
export const FakeSharedArray = class FakeSharedArray {
constructor(length: number) {
for (let i = 0; i < length; i++) {
Object.defineProperty(this, i, {
enumerable: false,
configurable: false,
writable: true,
value: undefined
});
}
Object.defineProperty(this, "length", {
enumerable: false,
configurable: false,
writable: false,
value: length
});
Object.setPrototypeOf(this, null); // eslint-disable-line no-null/no-null
Object.preventExtensions(this);
const fakeSharedArray = new Proxy(this, proxyHandler);
allFakeSharedArrayInstances.add(fakeSharedArray);
return fakeSharedArray as this;
}
static [Symbol.hasInstance](value: unknown) {
try {
return typeof value === "object" && !isNull(value) && allFakeSharedArrayInstances.has(value);
}
catch {
return false;
}
}
} as SharedArrayConstructor;

View File

@ -0,0 +1,52 @@
import { workerThreads } from "../../workerThreads";
import { Shared } from "./sharedStruct";
const identifiableConstructors = new WeakSet();
let typeCount = 0;
/** @internal */
export interface IdentifiableStruct extends SharedStruct {
readonly __hash__: number;
}
/**
* Defines an "identity hash" on a shared struct, allowing it to be used as a key in a `SharedMap` or `SharedSet`.
* @internal
*/
export function Identifiable<C extends abstract new (...args: any) => any>(base: C): C & (abstract new (...args: any) => IdentifiableStruct) {
if (isIdentifiableConstructor(base)) {
// constructor is already marked as identifiable. Nothing to do here.
return base;
}
const threadId = workerThreads?.threadId ?? 0;
const typeId = typeCount++;
let instanceCount = 0;
@Shared({ abstract: true })
abstract class IdentifiableStruct extends base {
@Shared() readonly __hash__: number;
constructor(...args: any) {
super(...args);
const instanceId = instanceCount++;
let hc = threadId;
hc = ((hc << 7) | (hc >>> 25)) ^ typeId;
hc = ((hc << 7) | (hc >>> 25)) ^ instanceId;
this.__hash__ = hc;
}
}
identifiableConstructors.add(IdentifiableStruct);
return IdentifiableStruct;
}
function isIdentifiableConstructor(value: object | null) {
while (value) {
if (identifiableConstructors.has(value)) {
return true;
}
value = Object.getPrototypeOf(value);
}
return false;
}

View File

@ -0,0 +1,54 @@
import { isNull } from "../../core";
import { FakeSharedArray, FakeSharedStructType } from "./fakeSharedStruct";
/**
* Returns whether the provided value is a shareable primitive value.
* @internal
*/
export function isShareablePrimitive(value: unknown): value is ShareablePrimitive {
switch (typeof value) {
case "undefined":
case "string":
case "number":
case "boolean":
case "symbol":
return true;
case "object":
return value === null; // eslint-disable-line no-null/no-null -- necessary for comparison
default:
return false;
}
}
// You cannot use `instanceof` with shared structs and there is no `isArray`-like test currently. The only way we have
// to determine if a value is a shareable non-primitive (i.e., a shared struct, `SharedArray`, `Atomics.Mutex`, or
// `Atomics.Condition`) is to try and assign it to a property of a shared struct and catch the exception. This will
// hopefully be addressed in a future version of the origin trial.
const supportsSharedStructs = typeof SharedStructType === "function";
const sharedStructTypeConstructor = supportsSharedStructs ? SharedStructType : FakeSharedStructType;
const sharedArrayConstructor = supportsSharedStructs ? SharedArray : FakeSharedArray;
/**
* Returns whether the provided value is a shareable non-primitive value.
* @internal
*/
export function isShareableNonPrimitive(value: unknown): value is ShareableNonPrimitive {
if (typeof value !== "object" || isNull(value)) return false;
return sharedStructTypeConstructor.isSharedStruct(value) ||
value instanceof sharedArrayConstructor ||
supportsSharedStructs && (value instanceof Atomics.Mutex || value instanceof Atomics.Condition);
}
/**
* Returns whether the provided value is a shareable value.
* @internal
*/
export function isShareable(value: unknown): value is Shareable {
return isShareablePrimitive(value) || isShareableNonPrimitive(value);
}
/** @internal */
export function isSharedArray(value: unknown): value is SharedArray<Shareable> {
return value instanceof SharedArray;
}

View File

@ -0,0 +1,333 @@
import "../../symbolMetadataShim";
import "./sharedStructsGlobals";
import { Debug } from "../../debug";
import { FakeSharedStructType } from "./fakeSharedStruct";
import { AbstractConstructor } from "../../types";
// Holds the fields tracked with `@Shared()`
const weakSharedFields = new WeakMap<object, string[]>();
// Holds the `SharedStructType` instances produced for classes tracked with `@Shared()`
const weakSharedStructType = new WeakMap<object, SharedStructTypeFactory>();
// WeakRef and FinalizationRegistry may not exist, so we'll fall back to a non-weak shim when unavailable
interface WeakRef<T extends WeakKey> {
deref(): T | undefined;
}
interface FinalizationRegistry<T> {
register(target: WeakKey, heldValue: T, unregisterToken?: WeakKey): void;
unregister(unregisterToken: WeakKey): void;
}
declare var WeakRef: new <T extends WeakKey>(value: T) => WeakRef<T>;
declare var FinalizationRegistry: new <T>(cleanupCallback: (heldValue: T) => void) => FinalizationRegistry<T>;
class FakeWeakRef<T extends WeakKey> implements WeakRef<T> {
private _target: T; // NOTE: Not actually weak
constructor(target: T) {
this._target = target;
}
deref(): T | undefined {
return this._target;
}
}
class FakeFinalizationRegistry<T> implements FinalizationRegistry<T> {
constructor(cleanupCallback: (heldValue: T) => void) {
void cleanupCallback;
}
register(target: WeakKey, heldValue: T, unregisterToken?: WeakKey): void {
void target;
void heldValue;
void unregisterToken;
}
unregister(unregisterToken: WeakKey): void {
void unregisterToken;
}
}
function createWeakRef<T extends WeakKey>(value: T) {
return typeof WeakRef === "function" ? new WeakRef(value) : new FakeWeakRef(value);
}
function createFinalizationRegistry<T>(cleanupCallback: (heldValue: T) => void): FinalizationRegistry<T> {
return typeof FinalizationRegistry === "function" ? new FinalizationRegistry(cleanupCallback) : new FakeFinalizationRegistry(cleanupCallback);
}
function createSharedStructType(fields: readonly string[]) {
// If shared structs are available, create a shared struct type to use when creating a new instance of the
// target. Otherwise, create a fake shared struct that emulates the required semantics to ensure we are forwards
// compatible.
return typeof SharedStructType === "function" ? new SharedStructType(fields) : new FakeSharedStructType(fields);
}
class SharedStructTypeFactory {
private _fields: readonly string[];
private _structType: WeakRef<new () => SharedStruct> | undefined;
private _arrayTypes: Map<number, WeakRef<new () => SharedStruct>> | undefined;
private _registry: FinalizationRegistry<number>;
constructor(fields: readonly string[]) {
this._fields = fields;
this._registry = createFinalizationRegistry(length => { this._arrayTypes?.delete(length); });
}
structType(): new () => SharedStruct {
let structType = this._structType?.deref();
if (!structType) {
structType = createSharedStructType(this._fields);
this._structType = createWeakRef(structType);
}
return structType;
}
arrayType(length: number): new () => SharedStruct {
let arrayType = this._arrayTypes?.get(length)?.deref();
if (!arrayType) {
const fields = new Set(this._fields);
fields.add("length");
for (let i = 0; i < length; i++) {
fields.add(`${i}`);
}
arrayType = createSharedStructType([...fields]);
this._arrayTypes ??= new Map();
this._arrayTypes.set(length, createWeakRef(arrayType));
this._registry.register(arrayType, length);
}
return arrayType;
}
}
interface SharedClassDecorator {
<T extends AbstractConstructor>(target: T, context: ClassDecoratorContext<T>): void;
<T extends AbstractConstructor>(target: T): void;
}
interface SharedClassOptions {
abstract?: boolean;
}
interface SharedFieldDecorator {
<This, T extends Shareable>(target: undefined, context: ClassFieldDecoratorContext<This, T> & { private: false, static: false, name: string }): void;
<This, T extends Shareable>(target: This, propertyKey: string, descriptor?: TypedPropertyDescriptor<T>): void;
}
function getOrCreateMetadata(target: AbstractConstructor) {
if (Object.prototype.hasOwnProperty.call(target, Symbol.metadata)) {
return target[Symbol.metadata];
}
const metadata = Object.create(target[Symbol.metadata] ?? null);
Object.defineProperty(target, Symbol.metadata, {
enumerable: false,
configurable: true,
writable: true,
value: metadata
});
return metadata;
}
/**
* A decorator used to mark a `class` or a non-static public class field as "shared". This is intended to be used to
* emulate syntax for shared structs and provide a mechanism to associate types with shared struct fields.
* @internal
*/
export function Shared(): SharedClassDecorator & SharedFieldDecorator;
/**
* A decorator used to mark a `class` as "shared". This is intended to be used to emulate syntax for shared structs.
* @param options.abstract Indicates the `class` is `abstract` and cannot be constructed directly.
* @internal
*/
export function Shared(options: SharedClassOptions): SharedClassDecorator;
export function Shared(options?: SharedClassOptions) {
function SharedClassDecorator<T extends AbstractConstructor>(target: T, context: ClassDecoratorContext<T>) {
if (weakSharedStructType.has(target)) {
Debug.fail("@Shared() cannot be applied to the same class more than once.");
}
// Delete the default `constructor` property from the prototype, as shared structs cannot currently have
// properties on the prototype.
Reflect.deleteProperty(target.prototype, "constructor");
// Validate the prototype chain. Even though `@Shared` class supertypes will have a null prototype, there may
// have been non-`@Shared` abstract classes in between.
let prototype: object | null = target.prototype;
while (prototype) {
if (Reflect.ownKeys(target.prototype).length > 0) {
Debug.fail(`Shared struct type '${context.name}' cannot have methods on its prototype.`);
}
prototype = Object.getPrototypeOf(prototype);
// Break early when we find a `@Shared` supertype since it should have already been validated.
if (prototype && weakSharedStructType.has(prototype)) {
break;
}
}
// Give the constructor a null-ish prototype. We can't set it directly because the `prototype` property of a
// class constructor is non-configurable, non-writable. Instead, we set it's `[[Prototype]]` to `null`.
Object.setPrototypeOf(target.prototype, null); // eslint-disable-line no-null/no-null
// If the class is `abstract`, do not associate a shared struct type with it.
if (options?.abstract) {
return;
}
// If decorator metadata is not available we cannot emulate shared struct. In this case we are in an environment
// where `../symbolMetadataShim` could not execute and likely doesn't support `SharedStructType` anyways. In
// that case, we fall back to a regular object with no sharing support.
if (!context.metadata) {
weakSharedStructType.set(target.prototype, Object as any);
return;
}
// Collect all of the `@Shared` fields in declaration order, with supertype fields coming first in the list.
let fields: string[] = [];
let metadata: DecoratorMetadata | null = context.metadata;
while (metadata) {
const sharedFields = weakSharedFields.get(metadata);
if (sharedFields) {
fields = [...sharedFields, ...fields];
}
metadata = Object.getPrototypeOf(metadata);
}
// Remove duplicate field names
fields = [...new Set(fields)];
weakSharedStructType.set(target.prototype, new SharedStructTypeFactory(fields));
return;
}
function SharedFieldDecorator<This, T>(_target: undefined, context: ClassFieldDecoratorContext<This, T>) {
Debug.assert(!context.private, "@Shared is not supported on private fields.");
Debug.assert(!context.static, "@Shared is not supported on static fields.");
Debug.assert(typeof context.name === "string", "@Shared cannot be used on symbol-named fields.");
// If `context.metadata` does not exists then we cannot record shared field information about the class and
// should do nothing. The `@Shared` decorator on the class will perform appropriate fallback behavior.
if (!context.metadata) {
return;
}
// Get or create an array of `@Shared` field names to be used to construct a SharedStructType for the class.
let sharedFields = weakSharedFields.get(context.metadata);
if (!sharedFields) weakSharedFields.set(context.metadata, sharedFields = []);
Debug.assert(!sharedFields.includes(context.name), "@Shared cannot be specified more than once on the same declaration.");
sharedFields.push(context.name);
}
function decorator(...args:
| [target: AbstractConstructor]
| [target: AbstractConstructor, context: ClassDecoratorContext]
| [target: undefined, context: ClassFieldDecoratorContext]
| [target: object, propertyKey: string, descriptor?: PropertyDescriptor]
) {
if (args.length === 1) {
const [target] = args;
return SharedClassDecorator(target, {
name: target.name,
get metadata() { return getOrCreateMetadata(target); }
} as ClassDecoratorContext);
}
else if (typeof args[1] === "string") {
const [target, propertyKey, _descriptor] = args;
Debug.assert(target);
return SharedFieldDecorator(
/*target*/ undefined,
{
kind: "field",
name: propertyKey,
public: true,
static: false,
private: false,
get metadata() { return getOrCreateMetadata(target.constructor as AbstractConstructor); }
} as unknown as ClassFieldDecoratorContext
);
}
else {
const [target, context] = args;
switch (context.kind) {
case "class": return SharedClassDecorator(target as AbstractConstructor, context);
case "field": return SharedFieldDecorator(target as undefined, context);
default: Debug.fail(`@Shared is not supported on ${(context as DecoratorContext).kind} declarations.`);
}
}
}
return decorator;
}
class NullObject extends null { // eslint-disable-line no-null/no-null
constructor() {
const self = Object.create(new.target.prototype);
Object.setPrototypeOf(self, null); // eslint-disable-line no-null/no-null
return self;
}
}
Object.setPrototypeOf(NullObject.prototype, null); // eslint-disable-line no-null/no-null
/** @internal */
@Shared({ abstract: true })
// The compiler doesn't like us using `extends null` in `SharedStructBase` due to the interface extension to
// opt into the `SharedStruct` brand. If we use `extends null` below we end up with this error:
//
// Class static side 'typeof SharedStructBase' incorrectly extends base class static side 'null'.ts(2417)
//
export abstract class SharedStructBase extends NullObject { // eslint-disable-line @typescript-eslint/no-unsafe-declaration-merging
constructor() {
// Find the first `SharedStructTypeFactory` instance associated with this prototype chain
let prototype: object | null = new.target.prototype;
while (prototype) {
const StructType = weakSharedStructType.get(prototype)?.structType();
if (StructType) {
// This will either be an instance of `SharedStructType` (if shared structs and `context.metadata` are
// available), an instance of `FakeSharedStructType` (if shared structs are not available, but
// `context.metadata` is), or `Object` (if neither shared structs nor `context.metadata` are available).
return new StructType();
}
prototype = Object.getPrototypeOf(prototype);
}
Debug.fail("Class not marked @Shareable()");
super();
}
}
// Reopen `SharedStructBase` to give it the `SharedStruct` brand.
/** @internal */
export interface SharedStructBase extends SharedStruct { // eslint-disable-line @typescript-eslint/no-unsafe-declaration-merging
}
// /** @internal */
// @Shared({ abstract: true })
// export abstract class SharedArrayBase<T extends Shareable> extends SharedStructBase {
// @Shared() readonly length!: number;
// constructor(length: number) {
// // Find the first `SharedStructTypeFactory` instance associated with this prototype chain
// let prototype: object | null = new.target.prototype;
// while (prototype) {
// const StructType = weakSharedStructType.get(prototype)?.arrayType(length);
// if (StructType) {
// // This will either be an instance of `SharedStructType` (if shared structs and `context.metadata` are
// // available), an instance of `FakeSharedStructType` (if shared structs are not available, but
// // `context.metadata` is), or `Object` (if neither shared structs nor `context.metadata` are available).
// const inst = new StructType() as Mutable<SharedArrayBase<T>>;
// inst.length = length;
// return inst;
// }
// prototype = Object.getPrototypeOf(prototype);
// }
// Debug.fail("Class not marked @Shareable()");
// super();
// }
// [index: number]: T;
// }
// // Reopen `SharedArrayBase` to give it the `SharedArray` brand.
// /** @internal */
// export interface SharedArrayBase<T extends Shareable> extends SharedArray<T> { // eslint-disable-line @typescript-eslint/no-unsafe-declaration-merging
// }

View File

@ -0,0 +1,124 @@
export { };
// NOTE: These types relate to the origin trial implementation of the shared structs proposal and may not be indicative
// of the final proposal. To use these types you must pass `--shared-string-table --harmony-struct` to NodeJS.
// the following brands are used to distinguish Shared Structs-related objects from other objects since they are not
// interchangeable with `object`:
declare const kGlobalSharedStructBrand: unique symbol;
declare const kGlobalSharedArrayBrand: unique symbol;
declare const kGlobalMutexBrand: unique symbol;
declare const kGlobalConditionBrand: unique symbol;
declare global {
type ShareablePrimitive =
| string
| number
| boolean
| null
| undefined
| OtherShareablePrimitive[keyof OtherShareablePrimitive]
;
type ShareableNonPrimitive =
| SharedStruct
| SharedArray<Shareable>
| Atomics.Mutex
| Atomics.Condition
| OtherShareableNonPrimitive[keyof OtherShareableNonPrimitive]
;
type Shareable =
| ShareablePrimitive
| ShareableNonPrimitive
;
interface OtherShareablePrimitive {}
interface OtherShareableNonPrimitive {}
interface SharedStruct {
[kGlobalSharedStructBrand]: any;
}
type SharedStructType<T extends SharedStruct & { readonly [P in keyof T]: Shareable }> = new () => T;
interface SharedStructTypeConstructor {
new <T extends SharedStruct & { readonly [P in keyof T]: Shareable }>(fields: readonly (keyof T & string)[]): SharedStructType<T>;
new (fields: readonly string[]): SharedStructType<SharedStruct>;
isSharedStruct(value: unknown): value is SharedStruct;
}
const SharedStructType: SharedStructTypeConstructor;
interface SharedArray<T extends Shareable> {
[kGlobalSharedArrayBrand]: any;
[index: number]: T;
readonly length: number;
}
interface ReadonlySharedArray<T extends Shareable> extends SharedArray<T> {
readonly [index: number]: T;
}
type SharedArrayConstructor = new <T extends Shareable>(length: number) => SharedArray<T>;
const SharedArray: SharedArrayConstructor;
namespace Atomics {
/**
* An opaque object that represents a shareable mutex.
*/
interface Mutex {
[kGlobalMutexBrand]: any;
}
interface MutexConstructor {
new (): Mutex;
/**
* Locks the mutex during the synchronous execution of the provided callback.
* @param mutex The mutex object.
* @param cb The callback to execute.
* @returns The result of executing the callback.
*/
lock<T>(mutex: Mutex, cb: () => T): T;
/**
* Tries to lock the mutex during the synchronous execution of the provided callback
* @param mutex The mutex object.
* @param cb The callback to execute.
* @returns `true` if the lock was taken and the callback was executed; otherwise, `false`.
*/
tryLock(mutex: Mutex, cb: () => void): boolean;
}
/**
* An opaque object that represents a shareable condition variable.
*/
interface Condition {
[kGlobalConditionBrand]: any;
}
interface ConditionConstructor {
new (): Condition;
/**
* Waits for a period of time for notification signal on a Condition.
* @param condition The condition to wait for.
* @param mutex The mutex to use to wait on.
* @param timeout The period of time to wait.
* @returns `true` if the condition was notified prior to the timeout elapsing; otherwise, `false`.
*/
wait(condition: Condition, mutex: Mutex, timeout?: number): boolean;
/**
* Notify one or more waiters on a condition.
* @param condition The condition to notify.
* @param count The number of waiters to notify (default: all current waiters).
* @returns The number of waiters that were notified.
*/
notify(condition: Condition, count?: number): number;
}
}
interface Atomics {
readonly Mutex: Atomics.MutexConstructor;
readonly Condition: Atomics.ConditionConstructor;
}
}

View File

@ -0,0 +1,68 @@
import { hasProperty } from "../../core";
import { isShareableNonPrimitive } from "./shareable";
import { Shared } from "./sharedStruct";
/** @internal */
export const enum Tag {
Mutex,
SharedMutex,
Condition,
ManualResetEvent,
CountdownEvent,
Map,
Set,
ResizableArray,
NodeArray,
Node,
Symbol,
FileReference,
AmdDependency,
CheckJsDirective,
CommentDirective,
TextRange,
Diagnostic,
DiagnosticMessageChain,
DiagnosticRelatedInformation,
Pragma,
PragmaArguments,
PragmaSpan,
}
/** @internal */
export interface TaggedStruct<TTag extends Tag> extends SharedStruct {
readonly __tag__: TTag;
}
/** @internal */
export type TaggedStructConstructor<TTag extends Tag> = (abstract new (...args: any) => TaggedStruct<TTag>) & {
readonly __tag__: TTag
};
/** @internal */
export type Tagged<F extends abstract new (...args: any) => SharedStruct, TTag extends Tag> =
(F extends abstract new (...args: infer A) => (infer R extends TaggedStruct<infer _>) ? (abstract new (...args: A) => Omit<R, "__tag__">) : F) & TaggedStructConstructor<TTag>
/**
* Since shared structs do not support instanceof, we often need a way to distingush one shared struct from another to
* create the correct proxy class in our membrane. To accomplish this, we can use the Tagged mixin to inject a
* `__tag__` field that we can use instead of `instanceof`.
* @internal
*/
export function Tagged<F extends abstract new (...args: any) => SharedStruct, TTag extends Tag>(base: F, tag: TTag): Tagged<F, TTag> {
@Shared({ abstract: true })
abstract class TaggedStruct extends base {
static readonly __tag__ = tag;
@Shared() readonly __tag__ = tag;
}
return TaggedStruct as Tagged<F, TTag>;
}
/** @internal */
export function isTaggedStructObject<TTag extends Tag>(value: ShareableNonPrimitive, tag?: TTag): value is TaggedStruct<TTag> {
return hasProperty(value, "__tag__") && (tag === undefined || (value as TaggedStruct<Tag>).__tag__ === tag);
}
/** @internal */
export function isTaggedStruct<TTag extends Tag>(value: unknown, tag?: TTag): value is TaggedStruct<TTag> {
return isShareableNonPrimitive(value) && isTaggedStructObject(value, tag);
}

View File

@ -0,0 +1,12 @@
/// <reference lib="esnext.disposable" />
export {};
// Shim `Symbol.dispose` so that we can can use `using`.
if (!Symbol.dispose) {
try {
Object.defineProperty(Symbol, "dispose", { configurable: true, writable: true, value: Symbol.for("Symbol.dispose") });
}
catch {
// do nothing
}
}

View File

@ -0,0 +1,12 @@
/// <reference lib="esnext.decorators" />
export {};
// Inject `Symbol.metadata`, if it is missing, so that we can leverage it from the `@Shared()` decorator.
if (!Symbol.metadata) {
try {
Object.defineProperty(Symbol, "metadata", { configurable: true, writable: true, value: Symbol.for("Symbol.metadata") });
}
catch {
// do nothing
}
}

View File

@ -1420,7 +1420,9 @@ export interface System {
realpath?(path: string): string;
/** @internal */ getEnvironmentVariable(name: string): string;
/** @internal */ tryEnableSourceMapsForHost?(): void;
/** @internal */ tryEnableSharedStructs?(): void;
/** @internal */ debugMode?: boolean;
/** @internal */ cpuCount?(): number;
setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any;
clearTimeout?(timeoutId: any): void;
clearScreen?(): void;
@ -1433,6 +1435,8 @@ export interface System {
// For testing
/** @internal */ now?(): Date;
/** @internal */ storeFilesChangingSignatureDuringEmit?: boolean;
/** @internal */ stringSeed?: number;
}
export interface FileWatcher {
@ -1454,7 +1458,8 @@ export let sys: System = (() => {
const nativePattern = /^native |^\([^)]+\)$|^(internal[\\/]|[a-zA-Z0-9_\s]+(\.js)?$)/;
const _fs: typeof import("fs") = require("fs");
const _path: typeof import("path") = require("path");
const _os = require("os");
const _os: typeof import("os") = require("os");
const _cp: typeof import("child_process") = require("child_process");
// crypto can be absent on reduced node installations
let _crypto: typeof import("crypto") | undefined;
try {
@ -1506,9 +1511,16 @@ export let sys: System = (() => {
inodeWatching: isLinuxOrMacOs,
sysLog,
});
const stringSeedFromEnv = +(process.env.TS_STRING_SEED || NaN);
const stringSeed = isFinite(stringSeedFromEnv) && Math.floor(stringSeedFromEnv) === stringSeedFromEnv ?
stringSeedFromEnv >>> 0 :
Math.floor(Math.random() * 0xffffffff) >>> 0;
const nodeSystem: System = {
args: process.argv.slice(2),
newLine: _os.EOL,
stringSeed,
useCaseSensitiveFileNames,
write(s: string): void {
process.stdout.write(s);
@ -1587,6 +1599,29 @@ export let sys: System = (() => {
// Could not enable source maps.
}
},
tryEnableSharedStructs() {
// If `SharedStructType` is available, or if we've already restarted with options, do nothing.
if (typeof SharedStructType === "function" || process.env.TS_RESTARTED_WITH_OPTIONS === "1") {
return;
}
const execArgv = ["--shared-string-table", "--harmony-struct"];
// If a call to `node --version` with the provided args results in a non-zero exit status, then
// we cannot restart with the provided options.
if (_cp.spawnSync(process.execPath, [...process.execArgv, ...execArgv, "--version"]).status) {
return;
}
// Start the process with the provided arguments, exiting with that process's exit code.
process.exit(_cp.spawnSync(
process.execPath,
[...process.execArgv, ...execArgv, ...process.argv.slice(1)],
{ stdio: "inherit", env: { ...process.env, TS_RESTARTED_WITH_OPTIONS: "1" } },
).status ?? 0);
},
cpuCount() {
return _os.cpus().length;
},
setTimeout,
clearTimeout,
clearScreen: () => {
@ -1611,7 +1646,6 @@ export let sys: System = (() => {
}
}
};
return nodeSystem;
/**
* `throwIfNoEntry` was added so recently that it's not in the node types.
@ -1983,6 +2017,8 @@ export let sys: System = (() => {
hash.update(data);
return hash.digest("hex");
}
return nodeSystem;
}
let sys: System | undefined;

View File

@ -0,0 +1,110 @@
import { Debug } from "../debug";
import { Mutex } from "./mutex";
import { Shared, SharedStructBase } from "../sharing/structs/sharedStruct";
import { isTaggedStruct, Tag, Tagged } from "../sharing/structs/taggedStruct";
import { UniqueLock } from "./uniqueLock";
/** @internal */
@Shared()
export class Condition extends Tagged(SharedStructBase, Tag.Condition) {
@Shared() private _condition = new Atomics.Condition();
/**
* Waits until a Condition is signaled via a call to {@link notify|`Condition.notify`}.
* @param self The condition to wait for.
* @param lock A {@link UniqueLock} taken for a {@link Mutex}.
* @param stopWaiting An optional callback that can be used to continue waiting if a necessary condition is not met.
*/
static wait(self: Condition, lock: UniqueLock<Mutex>, stopWaiting?: () => boolean): void {
Condition.waitUntil(self, lock, Infinity, stopWaiting!);
}
/**
* Waits until a Condition is signaled via a call to {@link notify|`Condition.notify`}, or until a timeout period
* has elapsed.
* @param self The condition to wait for.
* @param lock A {@link UniqueLock} taken for a {@link Mutex}.
* @param timeout The number of milliseconds to wait before returning.
* @returns `"no-timeout"` if the condition was notified before the timeout elapsed, or `"timeout"` to indicate the
* timeout elapsed before the condition was notified.
*/
static waitFor(self: Condition, lock: UniqueLock<Mutex>, timeout: number): "no-timeout" | "timeout";
/**
* Waits until a Condition is signaled via a call to {@link notify|`Condition.notify`}, or until a timeout period
* has elapsed.
* @param self The condition to wait for.
* @param lock A {@link UniqueLock} taken for a {@link Mutex}.
* @param timeout The number of milliseconds to wait before returning.
* @param stopWaiting An optional callback that can be used to continue waiting if a necessary condition is not met.
* @returns `true` if the condition was notified and the `stopWaiting` callback returned `true`. Otherwise,
* `false` to indicate that the timeout elapsed before the condition was ready.
*/
static waitFor(self: Condition, lock: UniqueLock<Mutex>, timeout: number, stopWaiting: () => boolean): boolean;
static waitFor(self: Condition, lock: UniqueLock<Mutex>, timeout: number, stopWaiting?: () => boolean): "no-timeout" | "timeout" | boolean {
return Condition.waitUntil(self, lock, Date.now() + timeout, stopWaiting!);
}
/**
* Waits until a Condition is signaled via a call to {@link notify|`Condition.notify`}, or until a specific time has been reached.
* @param self The condition to wait for.
* @param lock A {@link UniqueLock} taken for a {@link Mutex}.
* @param absoluteTimeout The number of milliseconds since the UNIX epoch.
* @returns `"no-timeout"` if the condition was notified before the timeout elapsed, or `"timeout"` to indicate the
* timeout elapsed before the condition was notified.
*/
static waitUntil(self: Condition, lock: UniqueLock<Mutex>, absoluteTimeout: number): "no-timeout" | "timeout";
/**
* Waits until a Condition is signaled via a call to {@link notify|`Condition.notify`}, or until a specific time has been reached.
* @param self The condition to wait for.
* @param lock A {@link UniqueLock} taken for a {@link Mutex}.
* @param absoluteTimeout The number of milliseconds since the UNIX epoch.
* @param stopWaiting An optional callback that can be used to continue waiting if a necessary condition is not met.
* @returns `true` if the condition was notified and the `stopWaiting` callback returned `true`. Otherwise,
* `false` to indicate that the timeout elapsed before the condition was ready.
*/
static waitUntil(self: Condition, lock: UniqueLock<Mutex>, absoluteTimeout: number, stopWaiting: () => boolean): boolean;
static waitUntil(self: Condition, lock: UniqueLock<Mutex>, absoluteTimeout: number, stopWaiting?: () => boolean): "no-timeout" | "timeout" | boolean {
const mutex = lock.mutex;
Debug.assert(mutex);
Debug.assert(mutex["_locked"]); // eslint-disable-line dot-notation -- declared `private`
const nativeMutex = mutex["_mutex"]; // eslint-disable-line dot-notation -- declared `private`
const nativeCondition = mutex["_condition"]; // eslint-disable-line dot-notation -- declared `private`
return Atomics.Mutex.lock(nativeMutex, () => {
while (!stopWaiting?.()) {
mutex["_locked"] = false; // eslint-disable-line dot-notation -- declared `private`
Atomics.Condition.notify(nativeCondition);
try {
const remainingTimeout = isFinite(absoluteTimeout) ? Date.now() - absoluteTimeout : undefined;
const result = Atomics.Condition.wait(self._condition, nativeMutex, remainingTimeout) ? "no-timeout" : "timeout";
if (result === "timeout") {
return stopWaiting ? stopWaiting() : result;
}
if (!stopWaiting) {
return "no-timeout";
}
}
finally {
while (mutex["_locked"]) { // eslint-disable-line dot-notation -- declared `private`
Atomics.Condition.wait(nativeCondition, nativeMutex);
}
mutex["_locked"] = true; // eslint-disable-line dot-notation -- declared `private`
}
}
return true;
});
}
/**
* Notify one or more waiters on a condition.
* @param condition The condition to notify.
* @param count The number of waiters to notify (default: all current waiters).
* @returns The number of waiters that were notified.
*/
static notify(self: Condition, count?: number) {
return Atomics.Condition.notify(self._condition, count);
}
static [Symbol.hasInstance](value: unknown): value is Condition {
return isTaggedStruct(value, Tag.Condition);
}
}

View File

@ -0,0 +1,118 @@
import { Condition, Debug, isTaggedStruct, Mutex, Shared, SharedStructBase, Tag, Tagged, UniqueLock } from "../_namespaces/ts";
const MAX_INT32 = 2 ** 31 - 1;
/** @internal */
@Shared()
export class CountdownEvent extends Tagged(SharedStructBase, Tag.CountdownEvent) {
@Shared() private _mutex: Mutex | undefined = new Mutex();
@Shared() private _condition: Condition | undefined = new Condition();
@Shared() private _signaled = false;
@Shared() private _initialCount: number;
@Shared() private _remainingCount: number = 0;
constructor(initialCount = 0) {
super();
initialCount |= 0;
Debug.assert(initialCount >= 0, "initialCount out of range");
this._initialCount = initialCount;
this._remainingCount = initialCount;
this._signaled = initialCount === 0;
}
static initialCount(self: CountdownEvent) {
return self._initialCount;
}
static remainingCount(self: CountdownEvent) {
return self._remainingCount;
}
static isSet(self: CountdownEvent) {
return self._remainingCount === 0;
}
static add(self: CountdownEvent, count?: number) {
Debug.assert(CountdownEvent.tryAdd(self, count), "event is already signaled");
}
static tryAdd(self: CountdownEvent, count = 1) {
count |= 0;
Debug.assert(count >= 1, "count out of range");
Debug.assert(self._mutex, "object disposed");
using _ = new UniqueLock(self._mutex);
if (self._remainingCount === 0) return false;
Debug.assert(self._remainingCount <= MAX_INT32 - count, "count out of range");
self._remainingCount += count;
return true;
}
static reset(self: CountdownEvent, count?: number) {
if (count !== undefined) {
count |= 0;
Debug.assert(count >= 0, "count out of range");
}
Debug.assert(self._mutex && self._condition, "object disposed");
using _ = new UniqueLock(self._mutex);
count ??= self._initialCount;
self._remainingCount = count;
self._initialCount = count;
if (count === 0) {
if (!self._signaled) {
self._signaled = true;
Condition.notify(self._condition);
}
}
else {
self._signaled = false;
}
}
static signal(self: CountdownEvent, count = 1) {
count |= 0;
Debug.assert(count >= 1, "count out of range");
Debug.assert(self._mutex && self._condition, "object disposed");
using _ = new UniqueLock(self._mutex);
Debug.assert(self._remainingCount >= count, "count out of range");
self._remainingCount = self._remainingCount - count;
if (self._remainingCount === 0) {
if (!self._signaled) {
self._signaled = true;
Condition.notify(self._condition);
}
return true;
}
return false;
}
static wait(self: CountdownEvent, timeout?: number) {
if (timeout !== undefined) {
timeout = Math.max(0, timeout | 0);
}
Debug.assert(self._mutex && self._condition, "object disposed");
if (CountdownEvent.isSet(self)) {
return true;
}
using lck = new UniqueLock(self._mutex);
if (timeout !== undefined) {
return Condition.waitFor(self._condition, lck, timeout) !== "timeout";
}
else {
Condition.wait(self._condition, lck);
return true;
}
}
static close(self: CountdownEvent): void {
self._mutex = undefined;
self._condition = undefined;
}
static [Symbol.hasInstance](value: unknown): value is CountdownEvent {
return isTaggedStruct(value, Tag.CountdownEvent);
}
}

View File

@ -0,0 +1,10 @@
/** @internal */
export interface BasicLockable {
lock(): void;
unlock(): void;
}
/** @internal */
export interface Lockable extends BasicLockable {
tryLock(): boolean;
}

View File

@ -0,0 +1,41 @@
import { Shared, SharedStructBase } from "../sharing/structs/sharedStruct";
import { Tag, Tagged } from "../sharing/structs/taggedStruct";
/** @internal */
@Shared()
export class ManualResetEvent extends Tagged(SharedStructBase, Tag.ManualResetEvent) {
@Shared() private mutex = new Atomics.Mutex();
@Shared() private condition = new Atomics.Condition();
@Shared() private signaled = false;
static isSet(self: ManualResetEvent) {
return self.signaled;
}
static set(self: ManualResetEvent) {
let result = false;
Atomics.Mutex.tryLock(self.mutex, () => {
if (!self.signaled) {
self.signaled = true;
Atomics.Condition.notify(self.condition);
result = true;
}
});
return result;
}
static reset(self: ManualResetEvent) {
let result = false;
Atomics.Mutex.tryLock(self.mutex, () => {
if (self.signaled) {
self.signaled = false;
result = true;
}
});
return result;
}
static wait(self: ManualResetEvent, timeout?: number) {
return Atomics.Mutex.lock(self.mutex, () => Atomics.Condition.wait(self.condition, self.mutex, timeout));
}
}

View File

@ -0,0 +1,154 @@
import { hasProperty } from "../core";
import { Debug } from "../debug";
import { Identifiable } from "../sharing/structs/identifiableStruct";
import { Shared, SharedStructBase } from "../sharing/structs/sharedStruct";
import { Tag, Tagged, TaggedStruct } from "../sharing/structs/taggedStruct";
import { Lockable } from "./lockable";
let tryLock: (self: Mutex, cacheKey?: object) => boolean;
let lock: (self: Mutex, cacheKey?: object) => void;
let unlock: (self: Mutex, cacheKey?: object) => void;
interface CallbackCache {
tryLock?: () => void;
lock?: () => void;
unlock?: () => void;
}
/**
* This wraps the experimental `Atomics.Mutex` such that locks can be taken via the RAII-compatible `UniqueLock` and
* `ScopedLock` primitives.
* @internal
*/
@Shared()
export class Mutex extends Identifiable(Tagged(SharedStructBase, Tag.Mutex)) {
@Shared() private _mutex = new Atomics.Mutex();
@Shared() private _condition = new Atomics.Condition();
@Shared() private _locked = false;
static {
const callbackCache = new WeakMap<object, CallbackCache>();
// we reuse the same lockTaken variable for each call to tryLock in a thread because its not
// reentrant and is reset each time the callback is invoked.
let lockTaken = false;
function getCache(key: object | undefined) {
if (key) {
let cache = callbackCache.get(key);
if (!cache) callbackCache.set(key, cache = {});
return cache;
}
}
function makeTryLockCallback(self: Mutex, cache: CallbackCache | undefined) {
const fn = () => {
if (!self._locked) {
self._locked = true;
lockTaken = true;
}
};
return cache ? cache.tryLock = fn : fn;
}
function makeLockCallback(self: Mutex, cache: CallbackCache | undefined) {
const fn = () => {
while (self._locked) {
Atomics.Condition.wait(self._condition, self._mutex);
}
self._locked = true;
};
return cache ? cache.lock = fn : fn;
}
function makeUnlockCallback(self: Mutex, cache: CallbackCache | undefined) {
const fn = () => {
self._locked = false;
};
return cache ? cache.unlock = fn : fn;
}
tryLock = (self, cacheKey) => {
lockTaken = false;
const cache = getCache(cacheKey);
Atomics.Mutex.tryLock(self._mutex, cache?.tryLock ?? makeTryLockCallback(self, cache));
return lockTaken;
};
lock = (self, cacheKey) => {
const cache = getCache(cacheKey);
Atomics.Mutex.lock(self._mutex, cache?.lock ?? makeLockCallback(self, cache));
};
unlock = (self, cacheKey) => {
const cache = getCache(cacheKey);
Atomics.Mutex.lock(self._mutex, cache?.unlock ?? makeUnlockCallback(self, cache));
Atomics.Condition.notify(self._condition);
};
}
static tryLock(self: Mutex, cb: () => void): boolean {
if (!tryLock(self, /*cacheKey*/ undefined)) {
return false;
}
try {
cb();
return true;
}
finally {
unlock(self, /*cacheKey*/ undefined);
}
}
/**
* Locks the mutex and invokes `cb` in the context of the lock. The mutex will always be unlocked when the method
* returns, regardless as to whether `cb` throws an exception.
*/
static lock<T>(self: Mutex, cb: () => T): T {
lock(self, /*cacheKey*/ undefined);
try {
return cb();
}
finally {
unlock(self, /*cacheKey*/ undefined);
}
}
static asLockable(self: Mutex): Lockable {
return new LockableMutex(self);
}
static [Symbol.hasInstance](value: unknown): value is Mutex {
return typeof value === "object" && value !== null && // eslint-disable-line no-null/no-null -- necessary for comparison
hasProperty(value, "__tag__") &&
(value as TaggedStruct<Tag>).__tag__ === Tag.Mutex;
}
}
class LockableMutex implements Lockable {
private _mutex: Mutex;
private _ownsLock = false;
constructor(mutex: Mutex) {
this._mutex = mutex;
}
tryLock(): boolean {
Debug.assert(!this._ownsLock);
this._ownsLock = tryLock(this._mutex, this);
return this._ownsLock;
}
lock(): void {
Debug.assert(!this._ownsLock);
lock(this._mutex, this);
this._ownsLock = true;
}
unlock(): void {
Debug.assert(this._ownsLock);
unlock(this._mutex, this);
this._ownsLock = false;
}
}

View File

@ -0,0 +1,108 @@
import { Debug } from "../debug";
import "../symbolDisposeShim";
import { Lockable } from "./lockable";
import { Mutex } from "./mutex";
import { SharedMutex } from "./sharedMutex";
/** @internal */
export class ScopedLock {
private _mutexes: readonly Lockable[] | undefined;
constructor(mutexes: Iterable<Lockable | Mutex | SharedMutex>) {
const array: Lockable[] = [];
for (const mutex of mutexes) {
array.push(
mutex instanceof Mutex ? Mutex.asLockable(mutex) :
mutex instanceof SharedMutex ? SharedMutex.asLockable(mutex) :
mutex);
}
let remaining = array.length;
let index = 0;
let error;
let hasError = false;
while (!hasError) {
try {
if (remaining === 0) {
// return all locks taken, in the order they were taken. index should be back at the first lock taken in
// this attempt.
this._mutexes = [...array.slice(index), ...array.slice(0, index)];
return;
}
const lockable = array[index];
if (remaining === array.length) {
// always wait for the first lock
lockable.lock();
}
else {
if (lockable.tryLock()) {
// this lock was taken. move to the next lock
index = (index + 1) % array.length;
remaining--;
}
else {
// if we fail to take a lock, unlock each lock taken so far so that we start over at the current
// index.
let i = (index + array.length - 1) % array.length;
while (remaining < array.length) {
// always unlock all locks taken, even if one unlock fails for some reason.
try {
array[i].unlock();
}
catch (e) {
error = error ? createSuppressedError(e, error) : e;
hasError = true;
}
i = (index + array.length - 1) % array.length;
remaining++;
}
}
}
}
catch (e) {
error = error ? createSuppressedError(e, error) : e;
hasError = true;
}
}
// if we reach this point, an error occurred. unlock all locks taken in reverse order and throw the error.
let i = (index + array.length - 1) % array.length;
while (remaining < array.length) {
// always unlock all locks taken, even if one unlock fails for some reason.
try {
array[i].unlock();
}
catch (e) {
error = createSuppressedError(e, error);
}
i = (index + array.length - 1) % array.length;
remaining++;
}
throw error;
}
[Symbol.dispose]() {
const mutexes = this._mutexes;
if (mutexes) {
this._mutexes = undefined;
for (let i = mutexes.length - 1; i >= 0; i--) {
mutexes[i].unlock();
}
}
}
}
function createSuppressedError(error: unknown, suppressed: unknown) {
if (typeof SuppressedError === "function") {
return Debug.captureStackTrace(new SuppressedError(error, suppressed), createSuppressedError);
}
const e = new Error("An error suppression occurred.") as SuppressedError;
e.error = error;
e.suppressed = suppressed;
e.name = "SuppressedError";
return Debug.captureStackTrace(e, createSuppressedError);
}

View File

@ -0,0 +1,108 @@
import "../symbolDisposeShim";
import { Debug } from "../debug";
import { SharedLockable } from "./sharedLockable";
import { SharedMutex } from "./sharedMutex";
/** @internal */
export class SharedLock<T extends SharedLockable | SharedMutex> {
private _mutex: T | undefined;
private _lockable: SharedLockable | undefined;
private _ownsLock = false;
constructor();
constructor(mutex: T, t?: "defer-lock" | "try-to-lock" | "adopt-lock");
constructor(mutex?: T, t?: "defer-lock" | "try-to-lock" | "adopt-lock") {
this._mutex = mutex;
this._lockable =
mutex instanceof SharedMutex ? SharedMutex.asSharedLockable(mutex) :
mutex;
if (this._lockable) {
switch (t) {
case "defer-lock":
break;
case "try-to-lock":
this._ownsLock = this._lockable.tryLockShared();
break;
case "adopt-lock":
this._ownsLock = true;
break;
case undefined:
this._lockable.lockShared();
this._ownsLock = true;
break;
}
}
}
get mutex() {
return this._mutex;
}
get ownsLock() {
return this._ownsLock;
}
tryLock(): boolean {
Debug.assert(this._lockable);
Debug.assert(!this._ownsLock);
this._ownsLock = this._lockable.tryLockShared();
return this._ownsLock;
}
lock(): void {
Debug.assert(this._lockable);
Debug.assert(!this._ownsLock);
this._lockable.lockShared();
this._ownsLock = true;
}
unlock() {
Debug.assert(this._lockable);
Debug.assert(this._ownsLock);
this._ownsLock = false;
this._lockable.unlockShared();
}
release() {
this._mutex = undefined;
this._lockable = undefined;
this._ownsLock = false;
}
swap(other: SharedLock<T>) {
const mutex = other._mutex;
other._mutex = this._mutex;
const lockable = other._lockable;
other._lockable = this._lockable;
const ownsLock = other._ownsLock;
other._ownsLock = this._ownsLock;
this._mutex = mutex;
this._lockable = lockable;
this._ownsLock = ownsLock;
}
move() {
const other = new SharedLock<T>();
other._mutex = this._mutex;
other._lockable = this._lockable;
other._ownsLock = this._ownsLock;
this._mutex = undefined;
this._lockable = undefined;
this._ownsLock = false;
return other;
}
[Symbol.dispose]() {
const lockable = this._lockable;
if (lockable) {
const ownsLock = this._ownsLock;
this._mutex = undefined;
this._lockable = undefined;
this._ownsLock = false;
if (ownsLock) {
lockable.unlockShared();
}
}
}
}

View File

@ -0,0 +1,6 @@
/** @internal */
export interface SharedLockable {
tryLockShared(): boolean;
lockShared(): void;
unlockShared(): void;
}

View File

@ -0,0 +1,277 @@
import "../symbolDisposeShim";
import { hasProperty } from "../core";
import { Debug } from "../debug";
import { Identifiable } from "../sharing/structs/identifiableStruct";
import { Shared, SharedStructBase } from "../sharing/structs/sharedStruct";
import { Tag, Tagged, TaggedStruct } from "../sharing/structs/taggedStruct";
import { Lockable } from "./lockable";
import { SharedLockable } from "./sharedLockable";
const EXCLUSIVE = 1 << 31 >>> 0;
const SHARED = ~EXCLUSIVE >>> 0;
let tryLock: (self: SharedMutex, cacheKey: object | undefined) => boolean;
let lock: (self: SharedMutex, cacheKey: object | undefined) => void;
let unlock: (self: SharedMutex, cacheKey: object | undefined) => void;
let tryLockShared: (self: SharedMutex, cacheKey: object | undefined) => boolean;
let lockShared: (self: SharedMutex, cacheKey: object | undefined) => void;
let unlockShared: (self: SharedMutex, cacheKey: object | undefined) => void;
interface CallbackCache {
tryLock?: () => void;
lock?: () => void;
unlock?: () => void;
tryLockShared?: () => void;
lockShared?: () => void;
unlockShared?: () => void;
}
/** @internal */
@Shared()
export class SharedMutex extends Identifiable(Tagged(SharedStructBase, Tag.SharedMutex)) {
@Shared() private _mutex = new Atomics.Mutex();
@Shared() private _gate1 = new Atomics.Condition();
@Shared() private _gate2 = new Atomics.Condition();
@Shared() private _state = 0;
static {
const callbackCache = new WeakMap<object, CallbackCache>();
// we reuse the same lockTaken variable for each call to tryLock/tryShardLock in a thread because its not
// reentrant and is reset each time the callback is invoked.
let lockTaken = false;
function getCache(key: object | undefined) {
if (key) {
let cache = callbackCache.get(key);
if (!cache) callbackCache.set(key, cache = {});
return cache;
}
}
function makeTryLockCallback(self: SharedMutex, cache: CallbackCache | undefined) {
const fn = () => {
if (self._state === 0) {
self._state |= EXCLUSIVE;
lockTaken = true;
}
};
return cache ? cache.tryLock = fn : fn;
}
function makeLockCallback(self: SharedMutex, cache: CallbackCache | undefined) {
const fn = () => {
// take exclusive lock
while (self._state & EXCLUSIVE) {
Atomics.Condition.wait(self._gate1, self._mutex);
}
self._state |= EXCLUSIVE;
// wait for readers to drain
while (self._state & SHARED) {
Atomics.Condition.wait(self._gate2, self._mutex);
}
};
return cache ? cache.lock = fn : fn;
}
function makeUnlockCallback(self: SharedMutex, cache: CallbackCache | undefined) {
const fn = () => {
// release exclusive lock
self._state = 0;
};
return cache ? cache.unlock = fn : fn;
}
function makeTryLockSharedCallback(self: SharedMutex, cache: CallbackCache | undefined) {
const fn = () => {
if (self._state === 0) {
self._state |= EXCLUSIVE;
lockTaken = true;
}
};
return cache ? cache.tryLockShared = fn : fn;
}
function makeLockSharedCallback(self: SharedMutex, cache: CallbackCache | undefined) {
const fn = () => {
while ((self._state & EXCLUSIVE) || ((self._state & SHARED) >>> 0) === SHARED) {
Atomics.Condition.wait(self._gate1, self._mutex);
}
const readerCount = ((self._state & SHARED) >>> 0) + 1;
self._state = (readerCount | (self._state & EXCLUSIVE)) >>> 0;
};
return cache ? cache.lockShared = fn : fn;
}
function makeUnlockSharedCallback(self: SharedMutex, cache: CallbackCache | undefined) {
const fn = () => {
const readerCount = ((self._state & SHARED) >>> 0) - 1;
self._state = (readerCount | (self._state & EXCLUSIVE)) >>> 0;
if (self._state & EXCLUSIVE) {
if (readerCount === 0) {
Atomics.Condition.notify(self._gate2, 1);
}
}
else {
if (readerCount === SHARED - 1) {
Atomics.Condition.notify(self._gate1, 1);
}
}
};
return cache ? cache.unlockShared = fn : fn;
}
tryLock = (self, cacheKey) => {
lockTaken = false;
const cache = getCache(cacheKey);
Atomics.Mutex.tryLock(self._mutex, cache?.tryLock ?? makeTryLockCallback(self, cache));
return lockTaken;
};
lock = (self, cacheKey) => {
const cache = getCache(cacheKey);
Atomics.Mutex.lock(self._mutex, cache?.lock ?? makeLockCallback(self, cache));
};
unlock = (self, cacheKey) => {
const cache = getCache(cacheKey);
Atomics.Mutex.lock(self._mutex, cache?.unlock ?? makeUnlockCallback(self, cache));
Atomics.Condition.notify(self._gate1);
};
tryLockShared = (self, cacheKey) => {
lockTaken = false;
const cache = getCache(cacheKey);
Atomics.Mutex.tryLock(self._mutex, cache?.tryLockShared ?? makeTryLockSharedCallback(self, cache));
return lockTaken;
};
lockShared = (self, cacheKey) => {
const cache = getCache(cacheKey);
Atomics.Mutex.lock(self._mutex, cache?.lockShared ?? makeLockSharedCallback(self, cache));
};
unlockShared = (self, cacheKey) => {
const cache = getCache(cacheKey);
Atomics.Mutex.lock(self._mutex, cache?.unlockShared ?? makeUnlockCallback(self, cache));
};
}
static tryLock(self: SharedMutex, cb: () => void): boolean {
if (!tryLock(self, /*cacheKey*/ undefined)) {
return false;
}
try {
cb();
return true;
}
finally {
unlock(self, /*cacheKey*/ undefined);
}
}
static lock<T>(self: SharedMutex, cb: () => T): T {
lock(self, /*cacheKey*/ undefined);
try {
return cb();
}
finally {
unlock(self, /*cacheKey*/ undefined);
}
}
static tryLockShared(self: SharedMutex, cb: () => void): boolean {
if (!tryLockShared(self, /*cacheKey*/ undefined)) {
return false;
}
try {
cb();
return true;
}
finally {
unlockShared(self, /*cacheKey*/ undefined);
}
}
static lockShared<T>(self: SharedMutex, cb: () => T): T {
lockShared(self, /*cacheKey*/ undefined);
try {
return cb();
}
finally {
unlockShared(self, /*cacheKey*/ undefined);
}
}
static asLockable(self: SharedMutex): Lockable {
return new LockableSharedMutex(self);
}
static asSharedLockable(self: SharedMutex): SharedLockable {
return new SharedLockableSharedMutex(self);
}
static [Symbol.hasInstance](value: unknown): value is SharedMutex {
return typeof value === "object" && value !== null && // eslint-disable-line no-null/no-null -- necessary for comparision
hasProperty(value, "__tag__") &&
(value as TaggedStruct<Tag>).__tag__ === Tag.SharedMutex;
}
}
class LockableSharedMutex implements Lockable {
private _mutex: SharedMutex;
private _ownsLock = false;
constructor(mutex: SharedMutex) {
this._mutex = mutex;
}
tryLock(): boolean {
Debug.assert(!this._ownsLock);
this._ownsLock = tryLock(this._mutex, this);
return this._ownsLock;
}
lock(): void {
Debug.assert(!this._ownsLock);
lock(this._mutex, this);
this._ownsLock = true;
}
unlock(): void {
Debug.assert(this._ownsLock);
unlock(this._mutex, this);
this._ownsLock = false;
}
}
class SharedLockableSharedMutex implements SharedLockable {
private _mutex: SharedMutex;
private _ownsLock = false;
constructor(mutex: SharedMutex) {
this._mutex = mutex;
}
tryLockShared(): boolean {
Debug.assert(!this._ownsLock);
this._ownsLock = tryLockShared(this._mutex, this);
return this._ownsLock;
}
lockShared(): void {
Debug.assert(!this._ownsLock);
lockShared(this._mutex, this);
this._ownsLock = true;
}
unlockShared(): void {
Debug.assert(this._ownsLock);
unlockShared(this._mutex, this);
this._ownsLock = false;
}
}

View File

@ -0,0 +1,231 @@
import { WorkerThreadsHost, Worker, workerThreads } from "../workerThreads";
import { SharedLinkedList } from "../sharing/collections/sharedLinkedList";
import { Shared, SharedStructBase } from "../sharing/structs/sharedStruct";
import { Mutex } from "./mutex";
import { Condition } from "./condition";
import { UniqueLock } from "./uniqueLock";
import { Debug } from "../debug";
import { isNodeLikeSystem } from "../core";
import { CountdownEvent } from "./countdownEvent";
@Shared()
class ThreadPoolWorkItem extends SharedStructBase {
@Shared() readonly name: string;
@Shared() readonly arg: Shareable;
constructor(name: string, arg?: Shareable) {
super();
this.name = name;
this.arg = arg;
}
}
/** @internal */
@Shared()
export class ThreadPoolState extends SharedStructBase {
@Shared() mutex = new Mutex();
@Shared() condition = new Condition();
@Shared() countdown = new CountdownEvent(1);
@Shared() queue = new SharedLinkedList<ThreadPoolWorkItem>();
@Shared() active = 0;
@Shared() done = false;
@Shared() error: string | undefined;
}
/**
* Creates a thread pool of one or more worker threads. A {@link ThreadPool} can only be created on the main thread.
* @internal
*/
export class ThreadPool {
readonly poolSize: number;
private readonly _host: WorkerThreadsHost;
private _workers: Worker[] = [];
private _state = new ThreadPoolState();
private _listening = false;
private _onUncaughtException = () => {
if (!this._state.done) {
this.abort();
}
};
constructor(poolSize: number, host = workerThreads ?? Debug.fail("Worker threads not available.")) {
Debug.assert(poolSize >= 1);
Debug.assert(host.isMainThread(), "A new thread pool can only be created on the main thread.");
this.poolSize = poolSize;
this._host = host;
}
/**
* Starts all threads in the thread pool.
*/
start(): void {
if (this._workers.length < this.poolSize) {
this._startListening();
}
for (let i = this._workers.length; i < this.poolSize; i++) {
this._workers.push(this._host.createWorker({
name: "ThreadPool Thread",
workerData: { type: "ThreadPoolThread", state: this._state }
}));
}
}
/**
* Disable the addition of new work items in the thread pool and wait for threads to terminate gracefully.
*/
stop(timeout?: number) {
this._shutdown();
return CountdownEvent.wait(this._state.countdown, timeout);
}
/**
* Immediately stop all threads in the thread pool and wait for them to terminate.
*/
async abort() {
this._shutdown();
const workers = this._workers.splice(0, this._workers.length);
await Promise.all(workers.map(worker => worker.terminate()));
}
/**
* Queues a work item to execute in a worker thread.
* @param name The name of the work item to execute.
* @param arg An argument passed to the work item that can be used to communicate and coordinate with the background
* thread.
*/
queueWorkItem(name: string, arg?: Shareable) {
{
using _ = new UniqueLock(this._state.mutex);
SharedLinkedList.push(this._state.queue, new ThreadPoolWorkItem(name, arg));
}
Condition.notify(this._state.condition, 1);
}
private _shutdown() {
this._stopListening();
Debug.log.trace("Shutting down thread pool");
if (!this._state.done) {
using _ = new UniqueLock(this._state.mutex);
this._state.done = true;
Condition.notify(this._state.condition);
CountdownEvent.signal(this._state.countdown, 1);
}
}
private _startListening() {
if (isNodeLikeSystem()) {
if (this._listening) {
return;
}
this._listening = true;
process.on("uncaughtExceptionMonitor", this._onUncaughtException);
}
}
private _stopListening() {
if (isNodeLikeSystem()) {
if (!this._listening) {
return;
}
process.off("uncaughtExceptionMonitor", this._onUncaughtException);
}
}
}
/**
* Represents a thread in a {@link ThreadPool} and can be used to process work items. A {@link ThreadPoolThread} should
* only be used in a worker thread.
* @internal
*/
export class ThreadPoolThread {
private _state: ThreadPoolState;
private _processWorkItem: (name: string, arg: Shareable) => void;
constructor(state: ThreadPoolState, processWorkItem: (name: string, arg: Shareable) => void) {
this._state = state;
this._processWorkItem = processWorkItem;
}
/**
* Runs the thread pool thread until the {@link ThreadPool} signals the thread should shut down.
*/
run() {
let running = true;
let started = false;
try {
const processWorkItem = this._processWorkItem;
if (!CountdownEvent.tryAdd(this._state.countdown, 1)) {
Debug.log.trace("thread pool is already shut down");
return;
}
while (running) {
let workItem: ThreadPoolWorkItem | undefined;
{
using lck = new UniqueLock(this._state.mutex);
// decrement the active thread counter before we start waiting for work
if (started) {
this._state.active--;
}
// wait until we have work to do
Condition.wait(this._state.condition, lck, () => this._state.queue.size > 0 || this._state.done);
// stop the thread if the thread pool is closed
if (this._state.done) {
if (started) {
this._state.active--;
}
running = false;
break;
}
// increment the active thread counter before we start processing a work item
this._state.active++;
started = true;
workItem = SharedLinkedList.shift(this._state.queue);
}
// process the workitem
if (workItem) {
try {
processWorkItem(workItem.name, workItem.arg);
}
catch (e) {
Debug.log.trace(e);
running = false;
using _ = new UniqueLock(this._state.mutex);
this._state.active--;
break;
}
}
}
}
catch (e) {
Debug.log.trace(e);
}
// Debug.log.trace(`shutting down.`);
CountdownEvent.signal(this._state.countdown);
}
/**
* Queue additional work to be performed in another thread.
*/
queueWorkItem(name: string, arg?: Shareable) {
Debug.assert(!this._state.done);
{
using _ = new UniqueLock(this._state.mutex);
Debug.assert(!this._state.done);
SharedLinkedList.push(this._state.queue, new ThreadPoolWorkItem(name, arg));
}
Condition.notify(this._state.condition, 1);
}
}

View File

@ -0,0 +1,118 @@
import "../symbolDisposeShim";
import { hasProperty } from "../core";
import { Debug } from "../debug";
import { BasicLockable, Lockable } from "./lockable";
import { Mutex } from "./mutex";
import { SharedMutex } from "./sharedMutex";
function isLockable(x: BasicLockable): x is Lockable {
return x instanceof UniqueLock ? !!x.mutex && isLockable(x.mutex) : hasProperty(x, "tryLock") && typeof (x as Lockable).tryLock === "function";
}
/** @internal */
export class UniqueLock<T extends BasicLockable | Mutex | SharedMutex> {
private _mutex: T | undefined;
private _lockable: BasicLockable | undefined;
private _ownsLock = false;
constructor(mutex?: T);
constructor(mutex: Extract<T, Lockable | Mutex | SharedMutex>, t?: "defer-lock" | "try-to-lock" | "adopt-lock");
constructor(mutex: T, t?: "defer-lock" | "adopt-lock");
constructor(mutex?: T, t?: "defer-lock" | "try-to-lock" | "adopt-lock") {
this._mutex = mutex;
this._lockable =
mutex instanceof Mutex ? Mutex.asLockable(mutex) :
mutex instanceof SharedMutex ? SharedMutex.asLockable(mutex) :
mutex;
if (this._lockable) {
switch (t) {
case "defer-lock":
break;
case "try-to-lock":
Debug.assert(isLockable(this._lockable));
this._ownsLock = this._lockable.tryLock();
break;
case "adopt-lock":
this._ownsLock = true;
break;
case undefined:
this._lockable.lock();
this._ownsLock = true;
break;
}
}
}
get mutex() {
return this._mutex;
}
get ownsLock() {
return this._ownsLock;
}
tryLock(this: UniqueLock<Extract<T, Lockable | Mutex | SharedMutex>>): boolean {
Debug.assert(this._lockable);
Debug.assert(!this._ownsLock);
Debug.assert(isLockable(this._lockable));
this._ownsLock = this._lockable.tryLock();
return this._ownsLock;
}
lock(): void {
Debug.assert(this._lockable);
Debug.assert(!this._ownsLock);
this._lockable.lock();
this._ownsLock = true;
}
unlock() {
Debug.assert(this._lockable);
Debug.assert(this._ownsLock);
this._ownsLock = false;
this._lockable.unlock();
}
release() {
this._mutex = undefined;
this._lockable = undefined;
this._ownsLock = false;
}
swap(other: UniqueLock<T>) {
const mutex = other._mutex;
const lockable = other._lockable;
const ownsLock = other._ownsLock;
other._mutex = this._mutex;
other._lockable = this._lockable;
other._ownsLock = this._ownsLock;
this._mutex = mutex;
this._lockable = lockable;
this._ownsLock = ownsLock;
}
move() {
const other = new UniqueLock<T>();
other._mutex = this._mutex;
other._lockable = this._lockable;
other._ownsLock = this._ownsLock;
this._mutex = undefined;
this._lockable = undefined;
this._ownsLock = false;
return other;
}
[Symbol.dispose]() {
const lockable = this._lockable;
if (lockable) {
const ownsLock = this._ownsLock;
this._mutex = undefined;
this._lockable = undefined;
this._ownsLock = false;
if (ownsLock) {
lockable.unlock();
}
}
}
}

View File

@ -52,7 +52,6 @@ import {
ForegroundColorEscapeSequences,
formatColorAndReset,
getAllProjectOutputs,
getBuildInfo as ts_getBuildInfo,
getBuildInfoFileVersionMap,
getConfigFileParsingDiagnostics,
getDirectoryPath,
@ -61,7 +60,6 @@ import {
getFilesInErrorForSummary,
getFirstProjectOutput,
getLocaleTimeString,
getModifiedTime as ts_getModifiedTime,
getNormalizedAbsolutePath,
getParsedCommandLineOfConfigFile,
getPendingEmitKind,
@ -111,6 +109,9 @@ import {
Status,
sys,
System,
ThreadPool,
getBuildInfo as ts_getBuildInfo,
getModifiedTime as ts_getModifiedTime,
toPath as ts_toPath,
TypeReferenceDirectiveResolutionCache,
unorderedRemoveItem,
@ -126,6 +127,7 @@ import {
WatchStatusReporter,
WatchType,
WildcardDirectoryWatcher,
WorkerThreadsHost,
writeFile,
WriteFileCallback,
} from "./_namespaces/ts";
@ -155,6 +157,7 @@ export interface BuildOptions {
emitDeclarationOnly?: boolean;
sourceMap?: boolean;
inlineSourceMap?: boolean;
maxCpuCount?: number;
traceResolution?: boolean;
/** @internal */ diagnostics?: boolean;
@ -302,8 +305,8 @@ export function createBuilderStatusReporter(system: System, pretty?: boolean): D
};
}
function createSolutionBuilderHostBase<T extends BuilderProgram>(system: System, createProgram: CreateProgram<T> | undefined, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter) {
const host = createProgramHost(system, createProgram) as SolutionBuilderHostBase<T>;
function createSolutionBuilderHostBase<T extends BuilderProgram>(system: System, createProgram: CreateProgram<T> | undefined, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, workerThreads?: WorkerThreadsHost, threadPool?: ThreadPool) {
const host = createProgramHost(system, createProgram, workerThreads, threadPool) as SolutionBuilderHostBase<T>;
host.getModifiedTime = system.getModifiedTime ? path => system.getModifiedTime!(path) : returnUndefined;
host.setModifiedTime = system.setModifiedTime ? (path, date) => system.setModifiedTime!(path, date) : noop;
host.deleteFile = system.deleteFile ? path => system.deleteFile!(path) : noop;
@ -313,14 +316,20 @@ function createSolutionBuilderHostBase<T extends BuilderProgram>(system: System,
return host;
}
export function createSolutionBuilderHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system = sys, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportErrorSummary?: ReportEmitErrorSummary) {
const host = createSolutionBuilderHostBase(system, createProgram, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderHost<T>;
export function createSolutionBuilderHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system?: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportErrorSummary?: ReportEmitErrorSummary): SolutionBuilderHost<T>;
/** @internal */
export function createSolutionBuilderHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system?: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportErrorSummary?: ReportEmitErrorSummary, workerThreads?: WorkerThreadsHost, threadPool?: ThreadPool): SolutionBuilderHost<T>;
export function createSolutionBuilderHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system = sys, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportErrorSummary?: ReportEmitErrorSummary, workerThreads?: WorkerThreadsHost, threadPool?: ThreadPool) {
const host = createSolutionBuilderHostBase(system, createProgram, reportDiagnostic, reportSolutionBuilderStatus, workerThreads, threadPool) as SolutionBuilderHost<T>;
host.reportErrorSummary = reportErrorSummary;
return host;
}
export function createSolutionBuilderWithWatchHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system = sys, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter) {
const host = createSolutionBuilderHostBase(system, createProgram, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderWithWatchHost<T>;
export function createSolutionBuilderWithWatchHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system?: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): SolutionBuilderWithWatchHost<T>;
/** @internal */
export function createSolutionBuilderWithWatchHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system?: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter, workerThreads?: WorkerThreadsHost, threadPool?: ThreadPool): SolutionBuilderWithWatchHost<T>;
export function createSolutionBuilderWithWatchHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system = sys, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter, workerThreads?: WorkerThreadsHost, threadPool?: ThreadPool) {
const host = createSolutionBuilderHostBase(system, createProgram, reportDiagnostic, reportSolutionBuilderStatus, workerThreads, threadPool) as SolutionBuilderWithWatchHost<T>;
const watchHost = createWatchHost(system, reportWatchStatus);
copyProperties(host, watchHost);
return host;

View File

@ -1,7 +1,8 @@
{
"extends": "../tsconfig-base",
"compilerOptions": {
"types": ["node"]
"types": ["node"],
"experimentalDecorators": true
},
"references": [

View File

@ -17,7 +17,11 @@ import {
ProgramBuildInfo,
SymlinkCache,
ThisContainer,
ThreadPool,
WorkerThreadsHost,
} from "./_namespaces/ts";
import { SharedNodeBase } from "./sharing/sharedNode";
import { SharedParserState } from "./sharing/sharedParserState";
// branded string type used to store absolute, normalized and canonicalized paths
// arbitrary file name can be converted to Path via toPath function
@ -927,6 +931,7 @@ export interface Node extends ReadonlyTextRange {
// `locals` and `nextContainer` have been moved to `LocalsContainer`
// `flowNode` has been moved to `FlowContainer`
// see: https://github.com/microsoft/TypeScript/pull/51682
/** @internal */ __shared__?: SharedNodeBase;
}
export interface JSDocContainer extends Node {
@ -935,7 +940,7 @@ export interface JSDocContainer extends Node {
}
/** @internal */
export interface JSDocArray extends Array<JSDoc> {
export interface JSDocArray extends ReadonlyArray<JSDoc> {
jsDocCache?: readonly JSDocTag[]; // Cache for getJSDocTags
}
@ -4364,7 +4369,7 @@ export interface SourceFile extends Declaration, LocalsContainer {
/** @internal */ lineMap: readonly number[];
/** @internal */ classifiableNames?: ReadonlySet<__String>;
// Comments containing @ts-* directives, in order.
/** @internal */ commentDirectives?: CommentDirective[];
/** @internal */ commentDirectives?: readonly CommentDirective[];
// Stores a mapping 'external module reference text' -> 'resolved file name' | undefined
// It is used to resolve module names in the checker.
// Content of this field should never be used directly - use getResolvedModuleFileName/setResolvedModuleFileName functions instead
@ -4385,6 +4390,8 @@ export interface SourceFile extends Declaration, LocalsContainer {
/** @internal */ exportedModulesFromDeclarationEmit?: ExportedModulesFromDeclarationEmit;
/** @internal */ endFlowNode?: FlowNode;
/** @internal */ __sharedCache__?: ReadonlyMap<ShareableNonPrimitive, object>;
}
/** @internal */
@ -4959,6 +4966,9 @@ export interface TypeCheckerHost extends ModuleSpecifierResolutionHost {
typesPackageExists(packageName: string): boolean;
packageBundlesTypes(packageName: string): boolean;
workerThreads: WorkerThreadsHost | undefined;
threadPool: ThreadPool | undefined;
}
export interface TypeChecker {
@ -7147,6 +7157,7 @@ export interface CompilerOptions {
/** @internal */listFilesOnly?: boolean;
locale?: string;
mapRoot?: string;
maxCpuCount?: number;
maxNodeModuleJsDepth?: number;
module?: ModuleKind;
moduleResolution?: ModuleResolutionKind;
@ -7426,6 +7437,7 @@ export interface CommandLineOptionOfStringType extends CommandLineOptionBase {
export interface CommandLineOptionOfNumberType extends CommandLineOptionBase {
type: "number";
defaultValueDescription: number | undefined | DiagnosticMessage;
defaultIfValueMissing?: () => number | undefined;
}
/** @internal */
@ -7760,6 +7772,7 @@ export type HasInvalidatedLibResolutions = (libFileName: string) => boolean;
export type HasChangedAutomaticTypeDirectiveNames = () => boolean;
export interface CompilerHost extends ModuleResolutionHost {
/** @internal */ requestSourceFile?(parserState: SharedParserState, fileName: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, shouldCreateNewSourceFile?: boolean, setFileVersion?: boolean): void;
getSourceFile(fileName: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined;
getSourceFileByPath?(fileName: string, path: Path, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined;
getCancellationToken?(): CancellationToken;
@ -7836,6 +7849,8 @@ export interface CompilerHost extends ModuleResolutionHost {
// For testing:
/** @internal */ storeFilesChangingSignatureDuringEmit?: boolean;
/** @internal */ getBuildInfo?(fileName: string, configFilePath: string | undefined): BuildInfo | undefined;
/** @internal */ workerThreads?: WorkerThreadsHost | undefined;
/** @internal */ threadPool?: ThreadPool | undefined;
}
/** true if --out otherwise source file name *
@ -8764,7 +8779,8 @@ export interface NodeFactory {
createJsxText(text: string, containsOnlyTriviaWhiteSpaces?: boolean): JsxText;
updateJsxText(node: JsxText, text: string, containsOnlyTriviaWhiteSpaces?: boolean): JsxText;
createJsxOpeningFragment(): JsxOpeningFragment;
createJsxJsxClosingFragment(): JsxClosingFragment;
/** @deprecated Use `createJsxClosingFragment` */ createJsxJsxClosingFragment(): JsxClosingFragment;
createJsxClosingFragment(): JsxClosingFragment;
updateJsxFragment(node: JsxFragment, openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment): JsxFragment;
createJsxAttribute(name: JsxAttributeName, initializer: JsxAttributeValue | undefined): JsxAttribute;
updateJsxAttribute(node: JsxAttribute, name: JsxAttributeName, initializer: JsxAttributeValue | undefined): JsxAttribute;
@ -10007,3 +10023,9 @@ export interface Queue<T> {
dequeue(): T;
isEmpty(): boolean;
}
/** @internal */
export type Constructor<T = any, A extends any[] = any[]> = new (...args: A) => T;
/** @internal */
export type AbstractConstructor<T = any, A extends any[] = any[]> = abstract new (...args: A) => T;

View File

@ -34,6 +34,7 @@ import {
BindingElement,
BindingElementOfBareOrAccessedRequire,
Block,
BuildOptions,
BundleFileSection,
BundleFileSectionKind,
BundleFileTextLike,
@ -400,6 +401,7 @@ import {
ModuleResolutionKind,
moduleResolutionOptionDeclarations,
MultiMap,
MutableNodeArray,
NamedDeclaration,
NamedExports,
NamedImports,
@ -501,7 +503,7 @@ import {
SuperExpression,
SuperProperty,
SwitchStatement,
Symbol,
type Symbol,
SymbolFlags,
SymbolTable,
SyntaxKind,
@ -562,6 +564,8 @@ import {
WriteFileCallbackData,
YieldExpression,
} from "./_namespaces/ts";
import { SharedModifierLike } from "./sharing/sharedNode";
import { SharedNodeArray } from "./sharing/sharedNodeArray";
/** @internal */
export const resolvingEmptyArray: never[] = [];
@ -1131,7 +1135,7 @@ export function isPinnedComment(text: string, start: number) {
}
/** @internal */
export function createCommentDirectivesMap(sourceFile: SourceFile, commentDirectives: CommentDirective[]): CommentDirectivesMap {
export function createCommentDirectivesMap(sourceFile: SourceFile, commentDirectives: readonly CommentDirective[]): CommentDirectivesMap {
const directivesByLine = new Map(
commentDirectives.map(commentDirective => ([
`${getLineAndCharacterOfPosition(sourceFile, commentDirective.range.end).line}`,
@ -6945,7 +6949,11 @@ export function getSyntacticModifierFlagsNoCache(node: Node): ModifierFlags {
}
/** @internal */
export function modifiersToFlags(modifiers: readonly ModifierLike[] | undefined) {
export function modifiersToFlags(modifiers: Iterable<ModifierLike | SharedModifierLike> | SharedNodeArray<SharedModifierLike> | undefined) {
if (modifiers instanceof SharedNodeArray) {
modifiers = SharedNodeArray.values(modifiers);
}
let flags = ModifierFlags.None;
if (modifiers) {
for (const modifier of modifiers) {
@ -7197,41 +7205,91 @@ function isExportDefaultSymbol(symbol: Symbol): boolean {
export function tryExtractTSExtension(fileName: string): string | undefined {
return find(supportedTSExtensionsForExtractExtension, extension => fileExtensionIs(fileName, extension));
}
/**
* Replace each instance of non-ascii characters by one, two, three, or four escape sequences
* representing the UTF-8 encoding of the character, and return the expanded char code list.
*/
function getExpandedCharCodes(input: string): number[] {
const output: number[] = [];
const length = input.length;
function writeByte(output: number[] | Uint8Array | undefined, index: number, value: number) {
if (output) {
output[index] = value;
}
}
/**
* NOTE: Use `utf8encodeInto` instead. This function is a fallback implementation and is only exposed for testing
* purposes.
* @internal
*/
export function utf8encodeIntoCompat(input: string, output: number[] | Uint8Array | undefined) {
const length = input.length;
let written = 0;
for (let i = 0; i < length; i++) {
const charCode = input.charCodeAt(i);
let charCode = input.charCodeAt(i);
// decode UCS2 surrogate pairs
if (/*is leading surrogate*/ (charCode & 0xd800) === 0xd800 &&
/*is not last character*/ i < length - 1) {
const charCode2 = input.charCodeAt(i + 1);
if (/*is trailing surrogate*/ (charCode2 & 0xdc00) === 0xdc00) {
charCode = ((charCode & 0x3ff) << 10) + (charCode2 & 0x3ff) + 0x10000;
i++;
}
}
// handle utf8
if (charCode < 0x80) {
output.push(charCode);
writeByte(output, written++, charCode);
}
else if (charCode < 0x800) {
output.push((charCode >> 6) | 0B11000000);
output.push((charCode & 0B00111111) | 0B10000000);
writeByte(output, written++, (charCode >> 6) | 0B11000000);
writeByte(output, written++, (charCode & 0B00111111) | 0B10000000);
}
else if (charCode < 0x10000) {
output.push((charCode >> 12) | 0B11100000);
output.push(((charCode >> 6) & 0B00111111) | 0B10000000);
output.push((charCode & 0B00111111) | 0B10000000);
writeByte(output, written++, (charCode >> 12) | 0B11100000);
writeByte(output, written++, ((charCode >> 6) & 0B00111111) | 0B10000000);
writeByte(output, written++, (charCode & 0B00111111) | 0B10000000);
}
else if (charCode < 0x20000) {
output.push((charCode >> 18) | 0B11110000);
output.push(((charCode >> 12) & 0B00111111) | 0B10000000);
output.push(((charCode >> 6) & 0B00111111) | 0B10000000);
output.push((charCode & 0B00111111) | 0B10000000);
writeByte(output, written++, (charCode >> 18) | 0B11110000);
writeByte(output, written++, ((charCode >> 12) & 0B00111111) | 0B10000000);
writeByte(output, written++, ((charCode >> 6) & 0B00111111) | 0B10000000);
writeByte(output, written++, (charCode & 0B00111111) | 0B10000000);
}
else {
Debug.assert(false, "Unexpected code point");
}
}
return written;
}
/**
* Encodes a string into UTF-8 bytes.
* If `output` is a `Uint8Array`, it must have enough capacity to hold the output.
* `output` may be `undefined`, in which case only the total number of bytes necessary to encode the input will be
* calculated.
* @internal
*/
export const utf8encodeInto = (() => {
if (typeof TextEncoder === "function") {
const encoder = new TextEncoder();
return (input: string, output: number[] | Uint8Array | undefined) => {
if (output === undefined || isArray(output)) {
return utf8encodeIntoCompat(input, output);
}
const { written = 0 } = encoder.encodeInto(input, output);
return written;
};
}
return utf8encodeIntoCompat;
})();
/**
* Replace each instance of non-ascii characters by one, two, three, or four escape sequences
* representing the UTF-8 encoding of the character, and return the expanded char code list.
*/
function getExpandedCharCodes(input: string): number[] {
const output: number[] = [];
utf8encodeInto(input, output);
return output;
}
@ -7275,39 +7333,71 @@ export function convertToBase64(input: string): string {
return result;
}
function getStringFromExpandedCharCodes(codes: number[]): string {
/**
* NOTE: Use `utf8decode` instead. This function is a fallback implementation and is only exposed for testing
* purposes.
* @internal
*/
export function utf8decodeCompat(input: readonly number[] | Uint8Array) {
let output = "";
let i = 0;
const length = codes.length;
const length = input.length;
while (i < length) {
const charCode = codes[i];
const charCode = input[i];
i++;
if (charCode < 0x80) {
output += String.fromCharCode(charCode);
i++;
continue;
}
else if ((charCode & 0B11000000) === 0B11000000) {
let value = charCode & 0B00111111;
i++;
let nextCode: number = codes[i];
while ((nextCode & 0B11000000) === 0B10000000) {
value = (value << 6) | (nextCode & 0B00111111);
i++;
nextCode = codes[i];
}
// `value` may be greater than 10FFFF (the maximum unicode codepoint) - JS will just make this into an invalid character for us
output += String.fromCharCode(value);
let value: number;
let remaining: number;
if ((charCode & 0B11111000) === 0B11110000) {
value = charCode & 0B00000111;
remaining = 3;
}
else if ((charCode & 0B11110000) === 0B11100000) {
value = charCode & 0B00001111;
remaining = 2;
}
else if ((charCode & 0B11100000) === 0B11000000) {
value = charCode & 0B00011111;
remaining = 1;
}
else {
// We don't want to kill the process when decoding fails (due to a following char byte not
// following a leading char), so we just print the (bad) value
output += String.fromCharCode(charCode);
i++;
value = charCode;
remaining = 0;
}
while (remaining > 0 && i < input.length) {
const nextCode = input[i];
i++;
remaining--;
value = (value << 6) | (nextCode & 0B00111111);
}
// `value` may be greater than 10FFFF (the maximum unicode codepoint) - JS will just make this into an invalid character for us
output += String.fromCodePoint(value);
}
return output;
}
/** @internal */
export const utf8decode = (() => {
if (typeof TextDecoder === "function") {
const decoder = new TextDecoder();
return (input: readonly number[] | Uint8Array) => {
if (isArray(input)) {
return decoder.decode(new Uint8Array(input));
}
return decoder.decode(input);
};
}
return utf8decodeCompat;
})();
/** @internal */
export function base64encode(host: { base64encode?(input: string): string } | undefined, input: string): string {
if (host && host.base64encode) {
@ -7350,7 +7440,7 @@ export function base64decode(host: { base64decode?(input: string): string } | un
}
i += 4;
}
return getStringFromExpandedCharCodes(expandedCharCodes);
return utf8decode(expandedCharCodes);
}
/** @internal */
@ -8006,6 +8096,7 @@ export function getLeftmostExpression(node: Expression, stopAtCallExpressions: b
/** @internal */
export interface ObjectAllocator {
getNodeArrayConstructor(): new <T extends Node>(items: readonly T[], hasTrailingComma?: boolean) => NodeArray<T>;
getNodeConstructor(): new (kind: SyntaxKind, pos: number, end: number) => Node;
getTokenConstructor(): new <TKind extends SyntaxKind>(kind: TKind, pos: number, end: number) => Token<TKind>;
getIdentifierConstructor(): new (kind: SyntaxKind.Identifier, pos: number, end: number) => Identifier;
@ -8017,7 +8108,7 @@ export interface ObjectAllocator {
getSourceMapSourceConstructor(): new (fileName: string, text: string, skipTrivia?: (pos: number) => number) => SourceMapSource;
}
function Symbol(this: Symbol, flags: SymbolFlags, name: __String) {
const SymbolConstructor = function Symbol(this: Symbol, flags: SymbolFlags, name: __String) {
this.flags = flags;
this.escapedName = name;
this.declarations = undefined;
@ -8032,7 +8123,7 @@ function Symbol(this: Symbol, flags: SymbolFlags, name: __String) {
this.isReferenced = undefined;
this.isAssigned = undefined;
(this as any).links = undefined; // used by TransientSymbol
}
};
function Type(this: Type, checker: TypeChecker, flags: TypeFlags) {
this.flags = flags;
@ -8090,14 +8181,61 @@ function SourceMapSource(this: SourceMapSource, fileName: string, text: string,
this.skipTrivia = skipTrivia || (pos => pos);
}
/**
* Emulates the prior behavior of `createNodeArray` to convert an existing array into a `NodeArray<T>` while also fixing
* the prototype. The resulting array becoms the `this` of any derived class.
*/
const ArrayAdopter = function <T>(elements: T[]) { // eslint-disable-line local/only-arrow-functions -- arrow functions aren't constructable.
if (elements !== emptyArray) {
Object.setPrototypeOf(elements, new.target.prototype);
return elements;
}
return Reflect.construct(Array, [0], new.target.prototype);
} as typeof Array & (new <T>(elements: T[]) => T[]);
Object.setPrototypeOf(ArrayAdopter, Array);
Object.setPrototypeOf(ArrayAdopter.prototype, Array.prototype);
declare class NodeArrayImpl<T extends Node> extends Array<T> {
hasTrailingComma: boolean;
transformFlags: TransformFlags;
pos: number;
end: number;
constructor(elements: readonly T[] | NodeArray<T>, hasTrailingComma?: boolean);
}
const NodeArray = class <T extends Node> extends ArrayAdopter<T> implements MutableNodeArray<T> {
declare hasTrailingComma: boolean;
declare transformFlags: TransformFlags;
declare pos: number;
declare end: number;
constructor(elements: readonly T[] | NodeArray<T>, hasTrailingComma?: boolean) {
const length = elements.length;
super(length >= 1 && length <= 4 ? elements.slice() : elements as T[]);
this.pos = -1;
this.end = -1;
this.hasTrailingComma = !!hasTrailingComma;
this.transformFlags = TransformFlags.None;
Debug.attachNodeArrayDebugInfo(this);
}
static {
Object.defineProperty(this, Symbol.species, { configurable: true, value: Array });
}
} as typeof NodeArrayImpl;
/** @internal */
export const objectAllocator: ObjectAllocator = {
getNodeArrayConstructor: () => NodeArray as any,
getNodeConstructor: () => Node as any,
getTokenConstructor: () => Token as any,
getIdentifierConstructor: () => Identifier as any,
getPrivateIdentifierConstructor: () => Node as any,
getSourceFileConstructor: () => Node as any,
getSymbolConstructor: () => Symbol as any,
getSymbolConstructor: () => SymbolConstructor as any,
getTypeConstructor: () => Type as any,
getSignatureConstructor: () => Signature as any,
getSourceMapSourceConstructor: () => SourceMapSource as any,
@ -8416,32 +8554,47 @@ function isFileForcedToBeModuleByFormat(file: SourceFile): true | undefined {
return (file.impliedNodeFormat === ModuleKind.ESNext || (fileExtensionIsOneOf(file.fileName, [Extension.Cjs, Extension.Cts, Extension.Mjs, Extension.Mts]))) && !file.isDeclarationFile ? true : undefined;
}
function forceSetExternalModuleIndicator(file: SourceFile) {
file.externalModuleIndicator = isFileProbablyExternalModule(file) || !file.isDeclarationFile || undefined;
}
function legacySetExternalModuleIndicator(file: SourceFile) {
file.externalModuleIndicator = isFileProbablyExternalModule(file);
}
function autoSetExternalModuleIndicatorNonJsx(file: SourceFile) {
file.externalModuleIndicator = isFileProbablyExternalModule(file) || isFileForcedToBeModuleByFormat(file);
}
function autoSetExternalModuleIndicatorJsx(file: SourceFile) {
file.externalModuleIndicator = isFileProbablyExternalModule(file) || isFileModuleFromUsingJSXTag(file) || isFileForcedToBeModuleByFormat(file);
}
/** @internal */
export function isSetExternalModuleIndicatorCallbackFromOptions(cb: (file: SourceFile) => void) {
return cb === forceSetExternalModuleIndicator ||
cb === legacySetExternalModuleIndicator ||
cb === autoSetExternalModuleIndicatorNonJsx ||
cb === autoSetExternalModuleIndicatorJsx;
}
/** @internal */
export function getSetExternalModuleIndicator(options: CompilerOptions): (file: SourceFile) => void {
// TODO: Should this callback be cached?
switch (getEmitModuleDetectionKind(options)) {
case ModuleDetectionKind.Force:
// All non-declaration files are modules, declaration files still do the usual isFileProbablyExternalModule
return (file: SourceFile) => {
file.externalModuleIndicator = isFileProbablyExternalModule(file) || !file.isDeclarationFile || undefined;
};
return forceSetExternalModuleIndicator;
case ModuleDetectionKind.Legacy:
// Files are modules if they have imports, exports, or import.meta
return (file: SourceFile) => {
file.externalModuleIndicator = isFileProbablyExternalModule(file);
};
return legacySetExternalModuleIndicator;
case ModuleDetectionKind.Auto:
// If module is nodenext or node16, all esm format files are modules
// If jsx is react-jsx or react-jsxdev then jsx tags force module-ness
// otherwise, the presence of import or export statments (or import.meta) implies module-ness
const checks: ((file: SourceFile) => Node | true | undefined)[] = [isFileProbablyExternalModule];
if (options.jsx === JsxEmit.ReactJSX || options.jsx === JsxEmit.ReactJSXDev) {
checks.push(isFileModuleFromUsingJSXTag);
return autoSetExternalModuleIndicatorJsx;
}
checks.push(isFileForcedToBeModuleByFormat);
const combined = or(...checks);
const callback = (file: SourceFile) => void (file.externalModuleIndicator = combined(file));
return callback;
return autoSetExternalModuleIndicatorNonJsx;
}
}
@ -8704,6 +8857,11 @@ export function getJSXRuntimeImport(base: string | undefined, options: CompilerO
return base ? `${base}/${options.jsx === JsxEmit.ReactJSXDev ? "jsx-dev-runtime" : "jsx-runtime"}` : undefined;
}
/** @internal */
export function getMaxCpuCount(options: CompilerOptions | BuildOptions): number {
return options.maxCpuCount ?? 1;
}
/** @internal */
export function hasZeroOrOneAsteriskCharacter(str: string): boolean {
let seenAsterisk = false;
@ -9843,7 +10001,13 @@ export function setParent<T extends Node>(child: T | undefined, parent: T["paren
/** @internal */
export function setParent<T extends Node>(child: T | undefined, parent: T["parent"] | undefined): T | undefined {
if (child && parent) {
(child as Mutable<T>).parent = parent;
try {
(child as Mutable<T>).parent = parent;
}
catch (e) {
e.message = Debug.format(e.message, child);
throw e;
}
}
return child;
}
@ -10343,3 +10507,15 @@ export function getPropertyNameFromType(type: StringLiteralType | NumberLiteralT
}
return Debug.fail();
}
/**
* Tests whether the provided string can be parsed as a number.
* @param s The string to test.
* @param roundTripOnly Indicates the resulting number matches the input when converted back to a string.
* @internal
*/
export function isValidNumberString(s: string, roundTripOnly: boolean): boolean {
if (s === "") return false;
const n = +s;
return isFinite(n) && (!roundTripOnly || "" + n === s);
}

View File

@ -1605,8 +1605,8 @@ export function isEntityName(node: Node): node is EntityName {
|| kind === SyntaxKind.Identifier;
}
export function isPropertyName(node: Node): node is PropertyName {
const kind = node.kind;
/** @internal */
export function isPropertyNameKind(kind: SyntaxKind): kind is PropertyName["kind"] {
return kind === SyntaxKind.Identifier
|| kind === SyntaxKind.PrivateIdentifier
|| kind === SyntaxKind.StringLiteral
@ -1614,6 +1614,10 @@ export function isPropertyName(node: Node): node is PropertyName {
|| kind === SyntaxKind.ComputedPropertyName;
}
export function isPropertyName(node: Node): node is PropertyName {
return isPropertyNameKind(node.kind);
}
export function isBindingName(node: Node): node is BindingName {
const kind = node.kind;
return kind === SyntaxKind.Identifier

View File

@ -18,6 +18,7 @@ import {
createIncrementalCompilerHost,
createIncrementalProgram,
CreateProgram,
createRequestSourceFile,
createWriteFileMeasuringIO,
CustomTransformers,
Debug,
@ -95,6 +96,7 @@ import {
sys,
System,
targetOptionDeclaration,
ThreadPool,
WatchCompilerHost,
WatchCompilerHostOfConfigFile,
WatchCompilerHostOfFilesAndCompilerOptions,
@ -105,6 +107,7 @@ import {
WatchOptions,
WatchStatusReporter,
whitespaceOrMapCommentRegExp,
WorkerThreadsHost,
WriteFileCallback,
} from "./_namespaces/ts";
@ -741,10 +744,12 @@ export function createWatchFactory<Y = undefined>(host: WatchFactoryHost & { tra
export function createCompilerHostFromProgramHost(host: ProgramHost<any>, getCompilerOptions: () => CompilerOptions, directoryStructureHost: DirectoryStructureHost = host): CompilerHost {
const useCaseSensitiveFileNames = host.useCaseSensitiveFileNames();
const compilerHost: CompilerHost = {
requestSourceFile: host.threadPool && createRequestSourceFile(host.threadPool, /*setParentNodes*/ undefined),
getSourceFile: createGetSourceFile(
(fileName, encoding) => !encoding ? compilerHost.readFile(fileName) : host.readFile(fileName, encoding),
getCompilerOptions,
/*setParentNodes*/ undefined
/*setParentNodes*/ undefined,
/*overrideObjectAllocator*/ undefined,
),
getDefaultLibLocation: maybeBind(host, host.getDefaultLibLocation),
getDefaultLibFileName: options => host.getDefaultLibFileName(options),
@ -767,6 +772,8 @@ export function createCompilerHostFromProgramHost(host: ProgramHost<any>, getCom
createHash: maybeBind(host, host.createHash),
readDirectory: maybeBind(host, host.readDirectory),
storeFilesChangingSignatureDuringEmit: host.storeFilesChangingSignatureDuringEmit,
workerThreads: host.workerThreads,
threadPool: host.threadPool,
};
return compilerHost;
}
@ -820,6 +827,12 @@ export function setGetSourceFileAsHashVersioned(compilerHost: CompilerHost) {
}
return result;
};
const originalRequestSourceFile = compilerHost.requestSourceFile;
if (originalRequestSourceFile) {
compilerHost.requestSourceFile = (parserState, fileName, languageVersionOrOptions, shouldCreateNewSourceFile) => {
originalRequestSourceFile.call(compilerHost, parserState, fileName, languageVersionOrOptions, shouldCreateNewSourceFile, /*setFileVersion*/ true);
};
}
}
/**
@ -827,9 +840,11 @@ export function setGetSourceFileAsHashVersioned(compilerHost: CompilerHost) {
*
* @internal
*/
export function createProgramHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system: System, createProgram: CreateProgram<T> | undefined): ProgramHost<T> {
export function createProgramHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system: System, createProgram: CreateProgram<T> | undefined, workerThreads: WorkerThreadsHost | undefined, threadPool: ThreadPool | undefined): ProgramHost<T> {
const getDefaultLibLocation = memoize(() => getDirectoryPath(normalizePath(system.getExecutingFilePath())));
return {
workerThreads,
threadPool,
useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames,
getNewLine: () => system.newLine,
getCurrentDirectory: memoize(() => system.getCurrentDirectory()),
@ -855,9 +870,9 @@ export function createProgramHost<T extends BuilderProgram = EmitAndSemanticDiag
/**
* Creates the watch compiler host that can be extended with config file or root file names and options host
*/
function createWatchCompilerHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system = sys, createProgram: CreateProgram<T> | undefined, reportDiagnostic: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): WatchCompilerHost<T> {
function createWatchCompilerHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system = sys, createProgram: CreateProgram<T> | undefined, reportDiagnostic: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter, workerThreads?: WorkerThreadsHost, threadPool?: ThreadPool | undefined): WatchCompilerHost<T> {
const write = (s: string) => system.write(s + system.newLine);
const result = createProgramHost(system, createProgram) as WatchCompilerHost<T>;
const result = createProgramHost(system, createProgram, workerThreads, threadPool) as WatchCompilerHost<T>;
copyProperties(result, createWatchHost(system, reportWatchStatus));
result.afterProgramCreate = builderProgram => {
const compilerOptions = builderProgram.getCompilerOptions();
@ -892,6 +907,8 @@ export interface CreateWatchCompilerHostInput<T extends BuilderProgram> {
createProgram?: CreateProgram<T>;
reportDiagnostic?: DiagnosticReporter;
reportWatchStatus?: WatchStatusReporter;
workerThreads?: WorkerThreadsHost;
threadPool?: ThreadPool;
}
/** @internal */
@ -908,10 +925,11 @@ export interface CreateWatchCompilerHostOfConfigFileInput<T extends BuilderProgr
*/
export function createWatchCompilerHostOfConfigFile<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>({
configFileName, optionsToExtend, watchOptionsToExtend, extraFileExtensions,
system, createProgram, reportDiagnostic, reportWatchStatus
system, createProgram, reportDiagnostic, reportWatchStatus,
workerThreads, threadPool,
}: CreateWatchCompilerHostOfConfigFileInput<T>): WatchCompilerHostOfConfigFile<T> {
const diagnosticReporter = reportDiagnostic || createDiagnosticReporter(system);
const host = createWatchCompilerHost(system, createProgram, diagnosticReporter, reportWatchStatus) as WatchCompilerHostOfConfigFile<T>;
const host = createWatchCompilerHost(system, createProgram, diagnosticReporter, reportWatchStatus, workerThreads, threadPool) as WatchCompilerHostOfConfigFile<T>;
host.onUnRecoverableConfigFileDiagnostic = diagnostic => reportUnrecoverableDiagnostic(system, diagnosticReporter, diagnostic);
host.configFileName = configFileName;
host.optionsToExtend = optionsToExtend;
@ -934,9 +952,10 @@ export interface CreateWatchCompilerHostOfFilesAndCompilerOptionsInput<T extends
*/
export function createWatchCompilerHostOfFilesAndCompilerOptions<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>({
rootFiles, options, watchOptions, projectReferences,
system, createProgram, reportDiagnostic, reportWatchStatus
system, createProgram, reportDiagnostic, reportWatchStatus,
workerThreads, threadPool,
}: CreateWatchCompilerHostOfFilesAndCompilerOptionsInput<T>): WatchCompilerHostOfFilesAndCompilerOptions<T> {
const host = createWatchCompilerHost(system, createProgram, reportDiagnostic || createDiagnosticReporter(system), reportWatchStatus) as WatchCompilerHostOfFilesAndCompilerOptions<T>;
const host = createWatchCompilerHost(system, createProgram, reportDiagnostic || createDiagnosticReporter(system), reportWatchStatus, workerThreads, threadPool) as WatchCompilerHostOfFilesAndCompilerOptions<T>;
host.rootFiles = rootFiles;
host.options = options;
host.watchOptions = watchOptions;

View File

@ -80,6 +80,7 @@ import {
StringLiteralLike,
sys,
System,
ThreadPool,
toPath,
toPath as ts_toPath,
updateErrorForNoInputFiles,
@ -92,6 +93,7 @@ import {
WatchType,
WatchTypeRegistry,
WildcardDirectoryWatcher,
WorkerThreadsHost,
} from "./_namespaces/ts";
export interface ReadBuildProgramHost {
@ -118,8 +120,11 @@ export function readBuilderProgram(compilerOptions: CompilerOptions, host: ReadB
return createBuilderProgramUsingProgramBuildInfo(buildInfo, buildInfoPath, host);
}
export function createIncrementalCompilerHost(options: CompilerOptions, system = sys): CompilerHost {
const host = createCompilerHostWorker(options, /*setParentNodes*/ undefined, system);
export function createIncrementalCompilerHost(options: CompilerOptions, system?: System): CompilerHost;
/** @internal */
export function createIncrementalCompilerHost(options: CompilerOptions, system?: System, workerThreads?: WorkerThreadsHost, threadPool?: ThreadPool): CompilerHost;
export function createIncrementalCompilerHost(options: CompilerOptions, system = sys, workerThreads?: WorkerThreadsHost, threadPool?: ThreadPool): CompilerHost {
const host = createCompilerHostWorker(options, /*setParentNodes*/ undefined, system, workerThreads, threadPool);
host.createHash = maybeBind(system, system.createHash);
host.storeFilesChangingSignatureDuringEmit = system.storeFilesChangingSignatureDuringEmit;
setGetSourceFileAsHashVersioned(host);
@ -164,6 +169,8 @@ export interface WatchHost {
clearTimeout?(timeoutId: any): void;
}
export interface ProgramHost<T extends BuilderProgram> {
/** @internal */ workerThreads: WorkerThreadsHost | undefined;
/** @internal */ threadPool: ThreadPool | undefined;
/**
* Used to create the program when need for program creation or recreation detected
*/

73
src/compiler/worker.ts Normal file
View File

@ -0,0 +1,73 @@
import { createCompilerHostWorker, Debug, isNodeLikeSystem, setGetSourceFileAsHashVersioned, sys, System, ThreadPoolState, ThreadPoolThread, workerThreads, WorkerThreadWorkerThreadsHost } from "./_namespaces/ts";
import { SharedSourceFile } from "./sharing/sharedNode";
import { getSharedObjectAllocator } from "./sharing/sharedObjectAllocator";
import { SharedSourceFileEntry } from "./sharing/sharedParserState";
import { Condition } from "./threading/condition";
import { UniqueLock } from "./threading/uniqueLock";
/** @internal */
export function executeWorker(_system: System, host: WorkerThreadWorkerThreadsHost) {
// if (process.execArgv.includes("--inspect-brk")) {
// const inspector = require("node:inspector") as typeof import("node:inspector");
// inspector.open();
// inspector.waitForDebugger();
// }
if (isNodeLikeSystem()) {
const fs: typeof import("fs") = require("fs");
const util: typeof import("util") = require("util");
Debug.loggingHost = {
log(_level, s) {
fs.writeSync(2, `[worker#${host.threadId}] ${s || ""}${sys.newLine}`);
},
format(...args) {
return util.formatWithOptions({ colors: true }, ...args);
}
};
}
const { workerData } = host;
Debug.assert(workerData);
const { type } = workerData as { type: string };
switch (type) {
case "ThreadPoolThread": {
const { state } = workerData as { state: ThreadPoolState };
const thread = new ThreadPoolThread(state, (name, arg) => {
switch (name) {
case "Program.requestSourceFile":
programRequestSourceFile(arg as SharedSourceFileEntry);
break;
}
});
thread.run();
break;
}
default:
Debug.fail(`Unsupported worker type: '${type}'.`);
break;
}
function programRequestSourceFile(entry: SharedSourceFileEntry) {
let ok = false;
let sharedFile: SharedSourceFile | undefined;
try {
// Debug.log.trace(`parsing: ${entry.fileName}`);
const overideObjectAllocator = getSharedObjectAllocator();
const host = createCompilerHostWorker({}, entry.setParentNodes, sys, workerThreads, /*threadPool*/ undefined, overideObjectAllocator);
if (entry.setFileVersion) {
setGetSourceFileAsHashVersioned(host);
}
const file = host.getSourceFile(entry.fileName, entry.languageVersion, undefined, entry.shouldCreateNewSourceFile);
Debug.assert(file === undefined || file instanceof SharedSourceFile);
sharedFile = file;
ok = true;
}
finally {
using _ = new UniqueLock(entry.fileMutex);
entry.error = !ok;
entry.done = ok;
entry.file = sharedFile;
Condition.notify(entry.fileCondition);
}
}
}

View File

@ -0,0 +1,169 @@
import { Debug, isNodeLikeSystem, sys } from "./_namespaces/ts";
import "./sharing/structs/sharedStructsGlobals";
/** @internal */
export interface WorkerThreadsHost {
readonly workerData: unknown;
readonly parentPort: MessagePort | undefined;
readonly threadId: number;
isWorkerThread(): this is WorkerThreadWorkerThreadsHost;
isMainThread(): this is MainThreadWorkerThreadsHost;
createWorker(options?: WorkerOptions): Worker;
createMessageChannel(): MessageChannel;
getHostObject<T>(factory: (host: WorkerThreadsHost) => T): T;
addMessageListener(source: MessagePort | Worker, handler: (value: unknown) => void): void;
removeMessageListener(source: MessagePort | Worker, handler: (value: unknown) => void): void;
}
/** @internal */
export interface WorkerThreadWorkerThreadsHost extends WorkerThreadsHost {
readonly parentPort: MessagePort;
isMainThread(): false;
}
/** @internal */
export interface MainThreadWorkerThreadsHost extends WorkerThreadsHost {
readonly parentPort: undefined;
isWorkerThread(): false;
}
/** @internal */
export type Transferrable =
| ArrayBuffer
| MessagePort
;
/** @internal */
export interface WorkerOptions {
/** The path to the worker script. If not provided, then the typescript runtime is assumed. */
file?: string;
/** Name of the worker for debugging purposes. */
name?: string;
/** Initial data to pass to the worker. */
workerData?: unknown;
/** Initial data to transfer to the worker. */
transferList?: readonly Transferrable[];
}
/** @internal */
export interface Worker {
postMessage(value: unknown, transferList?: readonly Transferrable[]): void;
terminate(): Promise<void>;
}
/** @internal */
export interface MessageChannel {
readonly nativeMessageChannel: unknown;
readonly port1: MessagePort;
readonly port2: MessagePort;
}
/** @internal */
export interface MessagePort {
postMessage(value: unknown, transferList?: readonly Transferrable[]): void;
close(): void;
}
function createNodeWorkerThreadHost(): WorkerThreadsHost {
const fs: typeof import("fs") = require("fs");
const path: typeof import("path") = require("path");
const worker_threads: typeof import("worker_threads") = require("worker_threads");
const hostObjectCircularitySentinel = {};
const hostObjects = new WeakMap<object, unknown>();
function isWorkerThread() {
return !worker_threads.isMainThread;
}
function isMainThread() {
return worker_threads.isMainThread;
}
function getEntrypointPath() {
const file = sys.getExecutingFilePath();
// The executing file path isn't valid when running unbundled, so try to use tsc.js if available.
// See `executingFilePath` in sys.ts for more information.
if (path.basename(file) === "__fake__.js") {
const tscPath = path.join(path.dirname(file), "tsc.js");
if (fs.existsSync(tscPath)) {
return tscPath;
}
throw new Error("Cannot determine entrypoint path.");
}
return file;
}
function createWorker({ file = getEntrypointPath(), name, workerData, transferList, }: WorkerOptions = {}): Worker {
const execArgv: string[] = [];
if (Debug.isDebugging) {
execArgv.push("--inspect");
}
if (process.execArgv.includes("--enable-source-maps")) {
execArgv.push("--enable-source-maps");
}
const worker = new worker_threads.Worker(file, {
name,
execArgv,
workerData,
transferList: transferList as unknown as import("worker_threads").TransferListItem[],
// stdout: true,
// stderr: true,
stdout: false,
stderr: false,
// ensure that we use the same seed value when hashing strings on all threads
env: { ...process.env, TS_STRING_SEED: `${sys.stringSeed}` }
});
// worker.stdout.pipe(process.stdout, { end: false });
// worker.stderr.pipe(process.stderr, { end: false });
worker.unref();
return worker as unknown as Worker;
}
function createMessageChannel(): MessageChannel {
return new worker_threads.MessageChannel() as unknown as MessageChannel;
}
function getHostObject<T>(factory: (host: WorkerThreadsHost) => T): T {
let value = hostObjects.get(factory);
if (value === hostObjectCircularitySentinel) throw new TypeError("Circularity detected in host object allocation");
if (value === undefined) {
hostObjects.set(factory, hostObjectCircularitySentinel);
hostObjects.set(factory, value = factory(workerThreads));
}
return value as T;
}
function addMessageListener(source: MessagePort | Worker, handler: (value: unknown) => void) {
(source as import("worker_threads").MessagePort | import("worker_threads").Worker).addListener("message", handler);
}
function removeMessageListener(source: MessagePort | Worker, handler: (value: unknown) => void) {
(source as import("worker_threads").MessagePort | import("worker_threads").Worker).removeListener("message", handler);
}
const workerThreads: WorkerThreadsHost = {
workerData: worker_threads.workerData,
parentPort: (worker_threads.parentPort ?? undefined) as MessagePort | undefined,
threadId: worker_threads.threadId,
isWorkerThread,
isMainThread,
createWorker,
createMessageChannel,
getHostObject,
addMessageListener,
removeMessageListener,
};
return workerThreads;
}
/** @internal */
export let workerThreads: WorkerThreadsHost | undefined = (() => {
if (isNodeLikeSystem() && typeof SharedStructType === "function") {
return createNodeWorkerThreadHost();
}
})();
/** @internal */
export function setWorkerThreadsHost(value: WorkerThreadsHost | undefined) {
workerThreads = value;
}

View File

@ -29,7 +29,6 @@ import {
createWatchCompilerHostOfConfigFile,
createWatchCompilerHostOfFilesAndCompilerOptions,
createWatchProgram,
createWatchStatusReporter as ts_createWatchStatusReporter,
Debug,
Diagnostic,
DiagnosticMessage,
@ -54,6 +53,7 @@ import {
getDiagnosticText,
getErrorSummaryText,
getLineStarts,
getMaxCpuCount,
getNormalizedAbsolutePath,
isIncrementalCompilation,
isWatchSet,
@ -67,7 +67,6 @@ import {
parseCommandLine,
parseConfigFileWithSystem,
ParsedCommandLine,
performIncrementalCompilation as ts_performIncrementalCompilation,
Program,
reduceLeftIterator,
ReportEmitErrorSummary,
@ -82,12 +81,16 @@ import {
supportedTSExtensionsFlat,
sys,
System,
ThreadPool,
toPath,
tracing,
createWatchStatusReporter as ts_createWatchStatusReporter,
performIncrementalCompilation as ts_performIncrementalCompilation,
validateLocaleAndSetLanguage,
version,
WatchCompilerHost,
WatchOptions,
WorkerThreadsHost,
} from "./_namespaces/ts";
interface Statistic {
@ -556,6 +559,7 @@ function executeCommandLineWorker(
sys: System,
cb: ExecuteCommandLineCallbacks,
commandLine: ParsedCommandLine,
workerThreads: WorkerThreadsHost | undefined,
) {
let reportDiagnostic = createDiagnosticReporter(sys);
if (commandLine.options.build) {
@ -634,6 +638,13 @@ function executeCommandLineWorker(
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
let threadPool: ThreadPool | undefined;
const maxCpuCount = getMaxCpuCount(commandLine.options);
if (maxCpuCount > 1 && workerThreads?.isMainThread()) {
threadPool = new ThreadPool(maxCpuCount, workerThreads);
threadPool.start();
}
const currentDirectory = sys.getCurrentDirectory();
const commandLineOptions = convertToOptionsWithAbsolutePaths(
commandLine.options,
@ -671,6 +682,8 @@ function executeCommandLineWorker(
commandLineOptions,
commandLine.watchOptions,
extendedConfigCache,
workerThreads,
threadPool,
);
}
else if (isIncrementalCompilation(configParseResult.options)) {
@ -678,7 +691,9 @@ function executeCommandLineWorker(
sys,
cb,
reportDiagnostic,
configParseResult
configParseResult,
workerThreads,
threadPool,
);
}
else {
@ -686,7 +701,9 @@ function executeCommandLineWorker(
sys,
cb,
reportDiagnostic,
configParseResult
configParseResult,
workerThreads,
threadPool,
);
}
}
@ -710,6 +727,8 @@ function executeCommandLineWorker(
commandLine.fileNames,
commandLineOptions,
commandLine.watchOptions,
workerThreads,
threadPool,
);
}
else if (isIncrementalCompilation(commandLineOptions)) {
@ -717,7 +736,9 @@ function executeCommandLineWorker(
sys,
cb,
reportDiagnostic,
{ ...commandLine, options: commandLineOptions }
{ ...commandLine, options: commandLineOptions },
workerThreads,
threadPool,
);
}
else {
@ -725,7 +746,9 @@ function executeCommandLineWorker(
sys,
cb,
reportDiagnostic,
{ ...commandLine, options: commandLineOptions }
{ ...commandLine, options: commandLineOptions },
workerThreads,
threadPool,
);
}
}
@ -744,6 +767,7 @@ export function executeCommandLine(
system: System,
cb: ExecuteCommandLineCallbacks,
commandLineArgs: readonly string[],
workerThreads?: WorkerThreadsHost
) {
if (isBuild(commandLineArgs)) {
const { buildOptions, watchOptions, projects, errors } = parseBuildCommand(commandLineArgs.slice(1));
@ -754,7 +778,8 @@ export function executeCommandLine(
buildOptions,
watchOptions,
projects,
errors
errors,
workerThreads
));
}
else {
@ -764,7 +789,8 @@ export function executeCommandLine(
buildOptions,
watchOptions,
projects,
errors
errors,
workerThreads
);
}
}
@ -775,10 +801,11 @@ export function executeCommandLine(
system,
cb,
commandLine,
workerThreads,
));
}
else {
return executeCommandLineWorker(system, cb, commandLine);
return executeCommandLineWorker(system, cb, commandLine, workerThreads);
}
}
@ -797,7 +824,8 @@ function performBuild(
buildOptions: BuildOptions,
watchOptions: WatchOptions | undefined,
projects: string[],
errors: Diagnostic[]
errors: Diagnostic[],
workerThreads: WorkerThreadsHost | undefined,
) {
// Update to pretty if host supports it
const reportDiagnostic = updateReportDiagnostic(
@ -832,14 +860,23 @@ function performBuild(
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
let threadPool: ThreadPool | undefined;
const maxCpuCount = getMaxCpuCount(buildOptions);
if (maxCpuCount > 1 && workerThreads?.isMainThread()) {
threadPool = new ThreadPool(maxCpuCount, workerThreads);
}
if (buildOptions.watch) {
if (reportWatchModeWithoutSysSupport(sys, reportDiagnostic)) return;
threadPool?.start();
const buildHost = createSolutionBuilderWithWatchHost(
sys,
/*createProgram*/ undefined,
reportDiagnostic,
createBuilderStatusReporter(sys, shouldBePretty(sys, buildOptions)),
createWatchStatusReporter(sys, buildOptions)
createWatchStatusReporter(sys, buildOptions),
workerThreads,
threadPool
);
const solutionPerformance = enableSolutionPerformance(sys, buildOptions);
updateSolutionBuilderHost(sys, cb, buildHost, solutionPerformance);
@ -861,12 +898,15 @@ function performBuild(
return builder;
}
threadPool?.start();
const buildHost = createSolutionBuilderHost(
sys,
/*createProgram*/ undefined,
reportDiagnostic,
createBuilderStatusReporter(sys, shouldBePretty(sys, buildOptions)),
createReportErrorSummary(sys, buildOptions)
createReportErrorSummary(sys, buildOptions),
workerThreads,
threadPool
);
const solutionPerformance = enableSolutionPerformance(sys, buildOptions);
updateSolutionBuilderHost(sys, cb, buildHost, solutionPerformance);
@ -874,6 +914,7 @@ function performBuild(
const exitStatus = buildOptions.clean ? builder.clean() : builder.build();
reportSolutionBuilderTimes(builder, solutionPerformance);
dumpTracingLegend(); // Will no-op if there hasn't been any tracing
threadPool?.stop();
return sys.exit(exitStatus);
}
@ -887,10 +928,12 @@ function performCompilation(
sys: System,
cb: ExecuteCommandLineCallbacks,
reportDiagnostic: DiagnosticReporter,
config: ParsedCommandLine
config: ParsedCommandLine,
workerThreads: WorkerThreadsHost | undefined,
threadPool: ThreadPool | undefined,
) {
const { fileNames, options, projectReferences } = config;
const host = createCompilerHostWorker(options, /*setParentNodes*/ undefined, sys);
const host = createCompilerHostWorker(options, /*setParentNodes*/ undefined, sys, workerThreads, threadPool);
const currentDirectory = host.getCurrentDirectory();
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames());
changeCompilerHostLikeToUseCache(host, fileName => toPath(fileName, currentDirectory, getCanonicalFileName));
@ -912,6 +955,7 @@ function performCompilation(
);
reportStatistics(sys, program, /*solutionPerformance*/ undefined);
cb(program);
threadPool?.stop();
return sys.exit(exitStatus);
}
@ -919,11 +963,13 @@ function performIncrementalCompilation(
sys: System,
cb: ExecuteCommandLineCallbacks,
reportDiagnostic: DiagnosticReporter,
config: ParsedCommandLine
config: ParsedCommandLine,
workerThreads: WorkerThreadsHost | undefined,
threadPool: ThreadPool | undefined,
) {
const { options, fileNames, projectReferences } = config;
enableStatisticsAndTracing(sys, options, /*isBuildMode*/ false);
const host = createIncrementalCompilerHost(options, sys);
const host = createIncrementalCompilerHost(options, sys, workerThreads, threadPool);
const exitStatus = ts_performIncrementalCompilation({
host,
system: sys,
@ -938,6 +984,7 @@ function performIncrementalCompilation(
cb(builderProgram);
}
});
threadPool?.stop();
return sys.exit(exitStatus);
}
@ -996,6 +1043,8 @@ function createWatchOfConfigFile(
optionsToExtend: CompilerOptions,
watchOptionsToExtend: WatchOptions | undefined,
extendedConfigCache: Map<string, ExtendedConfigCacheEntry>,
workerThreads: WorkerThreadsHost | undefined,
threadPool: ThreadPool | undefined,
) {
const watchCompilerHost = createWatchCompilerHostOfConfigFile({
configFileName: configParseResult.options.configFilePath!,
@ -1003,7 +1052,9 @@ function createWatchOfConfigFile(
watchOptionsToExtend,
system,
reportDiagnostic,
reportWatchStatus: createWatchStatusReporter(system, configParseResult.options)
reportWatchStatus: createWatchStatusReporter(system, configParseResult.options),
workerThreads,
threadPool,
});
updateWatchCompilationHost(system, cb, watchCompilerHost);
watchCompilerHost.configFileParsingResult = configParseResult;
@ -1018,6 +1069,8 @@ function createWatchOfFilesAndCompilerOptions(
rootFiles: string[],
options: CompilerOptions,
watchOptions: WatchOptions | undefined,
workerThreads: WorkerThreadsHost | undefined,
threadPool: ThreadPool | undefined,
) {
const watchCompilerHost = createWatchCompilerHostOfFilesAndCompilerOptions({
rootFiles,
@ -1025,7 +1078,9 @@ function createWatchOfFilesAndCompilerOptions(
watchOptions,
system,
reportDiagnostic,
reportWatchStatus: createWatchStatusReporter(system, options)
reportWatchStatus: createWatchStatusReporter(system, options),
workerThreads,
threadPool,
});
updateWatchCompilationHost(system, cb, watchCompilerHost);
return createWatchProgram(watchCompilerHost);

View File

@ -554,6 +554,10 @@ export function patchHostForBuildInfoWrite<T extends ts.System>(sys: T, version:
}
export class SolutionBuilderHost extends CompilerHost implements ts.SolutionBuilderHost<ts.BuilderProgram> {
/** @internal */ declare workerThreads: ts.WorkerThreadsHost | undefined;
/** @internal */ declare threadPool: ts.ThreadPool | undefined;
/** @internal */ declare membrane: ts.Membrane | undefined;
createProgram: ts.CreateProgram<ts.BuilderProgram>;
private constructor(sys: System | vfs.FileSystem, options?: ts.CompilerOptions, setParentNodes?: boolean, createProgram?: ts.CreateProgram<ts.BuilderProgram>) {

View File

@ -225,6 +225,7 @@ import {
normalizePath,
NumberLiteralType,
NumericLiteral,
objectAllocator,
ObjectAllocator,
ObjectFlags,
ObjectLiteralElement,
@ -1259,6 +1260,7 @@ class SourceMapSourceObject implements SourceMapSource {
function getServicesObjectAllocator(): ObjectAllocator {
return {
getNodeArrayConstructor: objectAllocator.getNodeArrayConstructor,
getNodeConstructor: () => NodeObject,
getTokenConstructor: () => TokenObject,

View File

@ -1,4 +1,5 @@
import "./unittests/asserts";
import "./unittests/utf8";
import "./unittests/base64";
import "./unittests/builder";
import "./unittests/canWatch";
@ -18,6 +19,8 @@ import "./unittests/programApi";
import "./unittests/publicApi";
import "./unittests/reuseProgramStructure";
import "./unittests/semver";
import "./unittests/sharing/hashData";
import "./unittests/sharing/resizableArray";
import "./unittests/transform";
import "./unittests/typeParameterIsPossiblyReferenced";
import "./unittests/config/commandLineParsing";

View File

@ -0,0 +1,15 @@
import { HashData } from "../../../compiler/sharing/collections/hashData";
describe("unittests:: hashData", () => {
describe("findEntryIndex", () => {
it("when empty", () => {
const hashData = new HashData(0);
expect(HashData.findEntryIndex(hashData, 0)).to.equal(-1);
});
it("when has element", () => {
const hashData = new HashData(0);
HashData.insertEntry(hashData, "a", "a");
expect(HashData.findEntryIndex(hashData, "a")).to.equal(0);
});
});
});

View File

@ -0,0 +1,9 @@
import { Membrane, ProxyResizableArray } from "../../_namespaces/ts";
describe("unittests:: resizableArray", () => {
it("push", () => {
const membrane = new Membrane();
const array = new ProxyResizableArray(membrane);
array.push(1);
});
});

View File

@ -0,0 +1,55 @@
import { utf8decodeCompat, utf8encodeIntoCompat } from "../_namespaces/ts";
describe("unittests:: utf8", () => {
describe("utf8encodeIntoCompat matches TextEncoder.encode", () => {
const data = [
"日本語",
"🐱",
"\ud83d\udc31",
"\u{1F431}",
"\x00\x01",
"\t\n\r\\\"'\u0062",
"====",
"",
];
for (const item of data) {
it(item, () => {
const buffer1 = new Uint8Array(65536);
const buffer2 = new Uint8Array(65536);
const written1 = utf8encodeIntoCompat(item, buffer1);
const { written: written2 } = new TextEncoder().encodeInto(item, buffer2);
const bytes1 = toBytes(buffer1.slice(0, written1)), bytes2 = toBytes(buffer2.slice(0, written2));
assert.equal(bytes1, bytes2);
});
}
});
function toBytes(buffer: Uint8Array) {
let s = "";
for (const byte of buffer) {
if (byte < 16) s += "0";
s += byte.toString(16).toUpperCase();
}
return s;
}
describe("utf8decodeCompat matches TextDecoder.decode", () => {
const data = [
"日本語",
"🐱",
"\ud83d\udc31",
"\u{1F431}",
"\x00\x01",
"\t\n\r\\\"'\u0062",
"====",
"",
];
for (const item of data) {
it(item, () => {
const buffer = new Uint8Array(65536);
const { written } = new TextEncoder().encodeInto(item, buffer);
const decoded1 = utf8decodeCompat(buffer.slice(0, written));
const decoded2 = new TextDecoder().decode(buffer.slice(0, written));
assert.equal(decoded1, decoded2);
});
}
});
});

View File

@ -9,6 +9,8 @@ ts.Debug.loggingHost = {
}
};
ts.sys.tryEnableSharedStructs?.();
if (ts.Debug.isDebugging) {
ts.Debug.enableDebugInfo();
}
@ -21,4 +23,9 @@ if (ts.sys.setBlocking) {
ts.sys.setBlocking();
}
ts.executeCommandLine(ts.sys, ts.noop, ts.sys.args);
if (ts.workerThreads?.isWorkerThread()) {
ts.executeWorker(ts.sys, ts.workerThreads);
}
else {
ts.executeCommandLine(ts.sys, ts.noop, ts.sys.args, ts.workerThreads);
}

View File

@ -7218,6 +7218,7 @@ declare namespace ts {
lib?: string[];
locale?: string;
mapRoot?: string;
maxCpuCount?: number;
maxNodeModuleJsDepth?: number;
module?: ModuleKind;
moduleResolution?: ModuleResolutionKind;
@ -7494,6 +7495,7 @@ declare namespace ts {
readonly resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective | undefined;
}
interface CompilerHost extends ModuleResolutionHost {
requestSourceFile?(fileName: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, shouldCreateNewSourceFile?: boolean): void;
getSourceFile(fileName: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined;
getSourceFileByPath?(fileName: string, path: Path, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined;
getCancellationToken?(): CancellationToken;
@ -9874,6 +9876,7 @@ declare namespace ts {
emitDeclarationOnly?: boolean;
sourceMap?: boolean;
inlineSourceMap?: boolean;
maxCpuCount?: number;
traceResolution?: boolean;
[option: string]: CompilerOptionsValue | undefined;
}

View File

@ -3158,6 +3158,7 @@ declare namespace ts {
lib?: string[];
locale?: string;
mapRoot?: string;
maxCpuCount?: number;
maxNodeModuleJsDepth?: number;
module?: ModuleKind;
moduleResolution?: ModuleResolutionKind;
@ -3434,6 +3435,7 @@ declare namespace ts {
readonly resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective | undefined;
}
interface CompilerHost extends ModuleResolutionHost {
requestSourceFile?(fileName: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, shouldCreateNewSourceFile?: boolean): void;
getSourceFile(fileName: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined;
getSourceFileByPath?(fileName: string, path: Path, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined;
getCancellationToken?(): CancellationToken;
@ -5814,6 +5816,7 @@ declare namespace ts {
emitDeclarationOnly?: boolean;
sourceMap?: boolean;
inlineSourceMap?: boolean;
maxCpuCount?: number;
traceResolution?: boolean;
[option: string]: CompilerOptionsValue | undefined;
}

0
thread.log Normal file
View File