copy vscode sources, add base/common/yaml (#1468)

* copy vscode sources, add base/common/yaml

* fix tests (due to change of toStringEdit)
This commit is contained in:
Martin Aeschlimann 2025-10-21 19:22:20 +02:00 committed by GitHub
parent cbb7fcf894
commit 3f4cb59fc5
55 changed files with 1402 additions and 181 deletions

View File

@ -160,6 +160,7 @@ async function doIt(filepaths: string[]) {
'vs/base/common/themables.ts',
'vs/base/common/uri.ts',
'vs/base/common/uuid.ts',
'vs/base/common/yaml.ts',
'vs/editor/common/core/ranges/offsetRange.ts',
'vs/editor/common/core/wordHelper.ts',
'vs/editor/common/model/prefixSumComputer.ts',

View File

@ -55,20 +55,14 @@ class Point3D {
expect(res).toBeTypeOf('object');
const result = res as Exclude<typeof res, string | undefined>;
expect(result[0].rebasedEditIndex).toBe(1);
expect(result[0].rebasedEdit.toString()).toMatchInlineSnapshot(`
"[68, 76) -> "
this.z = z;""
`);
expect(result[0].rebasedEdit.toString()).toMatchInlineSnapshot(`"[68, 76) -> "\\n\\t\\tthis.z = z;""`);
}
{
const res = tryRebase(originalDocument, undefined, decomposeStringEdit(suggestedEdit).edits, [], userEdit, currentDocument, [], 'lenient', tracer);
expect(res).toBeTypeOf('object');
const result = res as Exclude<typeof res, string | undefined>;
expect(result[0].rebasedEditIndex).toBe(1);
expect(result[0].rebasedEdit.toString()).toMatchInlineSnapshot(`
"[68, 76) -> "
this.z = z;""
`);
expect(result[0].rebasedEdit.toString()).toMatchInlineSnapshot(`"[68, 76) -> "\\n\\t\\tthis.z = z;""`);
}
});
@ -227,12 +221,7 @@ int main()
const result = res as Exclude<typeof res, string | undefined>;
expect(result[0].rebasedEditIndex).toBe(0);
expect(StringEdit.single(result[0].rebasedEdit).apply(currentDocument)).toStrictEqual(final);
expect(result[0].rebasedEdit.removeCommonSuffixAndPrefix(currentDocument).toString()).toMatchInlineSnapshot(`
"[87, 164) -> "esult42.empty())
return result42.size();
result42.clear();
return result42""
`);
expect(result[0].rebasedEdit.removeCommonSuffixAndPrefix(currentDocument).toString()).toMatchInlineSnapshot(`"[87, 164) -> "esult42.empty())\\n return result42.size();\\n result42.clear();\\n return result42""`);
}
{
const res = tryRebase(originalDocument, undefined, suggestedEdit.replacements, [], userEdit, currentDocument, [], 'lenient', tracer);
@ -240,12 +229,7 @@ int main()
const result = res as Exclude<typeof res, string | undefined>;
expect(result[0].rebasedEditIndex).toBe(0);
expect(StringEdit.single(result[0].rebasedEdit).apply(currentDocument)).toStrictEqual(final);
expect(result[0].rebasedEdit.removeCommonSuffixAndPrefix(currentDocument).toString()).toMatchInlineSnapshot(`
"[87, 164) -> "esult42.empty())
return result42.size();
result42.clear();
return result42""
`);
expect(result[0].rebasedEdit.removeCommonSuffixAndPrefix(currentDocument).toString()).toMatchInlineSnapshot(`"[87, 164) -> "esult42.empty())\\n return result42.size();\\n result42.clear();\\n return result42""`);
}
});
});
@ -346,16 +330,10 @@ class Point3D {
const strict = tryRebaseStringEdits(text, edit, base, 'strict')?.removeCommonSuffixAndPrefix(current);
expect(strict?.apply(current)).toStrictEqual(final);
expect(strict?.replacements.toString()).toMatchInlineSnapshot(`
"[69, 69) -> " this.z = z;
""
`);
expect(strict?.replacements.toString()).toMatchInlineSnapshot(`"[69, 69) -> "\\t\\tthis.z = z;\\n""`);
const lenient = tryRebaseStringEdits(text, edit, base, 'lenient')?.removeCommonSuffixAndPrefix(current);
expect(lenient?.apply(current)).toStrictEqual(final);
expect(lenient?.replacements.toString()).toMatchInlineSnapshot(`
"[69, 69) -> " this.z = z;
""
`);
expect(lenient?.replacements.toString()).toMatchInlineSnapshot(`"[69, 69) -> "\\t\\tthis.z = z;\\n""`);
});
test('insert 2 and 2 edits', () => {
const text = `

View File

@ -14,7 +14,7 @@ import { RootedLineEdit } from './rootedLineEdit';
export class RootedEdit<TEdit extends BaseStringEdit<BaseStringReplacement<any>, any> = StringEdit> {
public static toLineEdit(edit: RootedEdit<BaseStringEdit<BaseStringReplacement<any>, any>>): LineEdit {
return LineEdit.fromEdit(edit.edit as StringEdit, edit.base);
return LineEdit.fromStringEdit(edit.edit as StringEdit, edit.base);
}
constructor(

View File

@ -13,7 +13,7 @@ ensureDependenciesAreSet();
export class RootedLineEdit {
public static fromEdit<TEdit extends BaseStringEdit>(edit: RootedEdit<TEdit>): RootedLineEdit {
const lineEdit = LineEdit.fromEdit(edit.edit as BaseStringEdit as StringEdit, edit.base);
const lineEdit = LineEdit.fromStringEdit(edit.edit as BaseStringEdit as StringEdit, edit.base);
return new RootedLineEdit(edit.base, lineEdit);
}

View File

@ -54,6 +54,11 @@ export interface IActionChangeEvent {
readonly checked?: boolean;
}
/**
* A concrete implementation of {@link IAction}.
*
* Note that in most cases you should use the lighter-weight {@linkcode toAction} function instead.
*/
export class Action extends Disposable implements IAction {
protected _onDidChange = this._register(new Emitter<IActionChangeEvent>());

View File

@ -193,6 +193,10 @@ export interface ITask<T> {
(): T;
}
export interface ICancellableTask<T> {
(token: CancellationToken): T;
}
/**
* A helper to prevent accumulation of sequential async tasks.
*
@ -223,18 +227,19 @@ export class Throttler implements IDisposable {
private activePromise: Promise<any> | null;
private queuedPromise: Promise<any> | null;
private queuedPromiseFactory: ITask<Promise<any>> | null;
private isDisposed = false;
private queuedPromiseFactory: ICancellableTask<Promise<any>> | null;
private cancellationTokenSource: CancellationTokenSource;
constructor() {
this.activePromise = null;
this.queuedPromise = null;
this.queuedPromiseFactory = null;
this.cancellationTokenSource = new CancellationTokenSource();
}
queue<T>(promiseFactory: ITask<Promise<T>>): Promise<T> {
if (this.isDisposed) {
queue<T>(promiseFactory: ICancellableTask<Promise<T>>): Promise<T> {
if (this.cancellationTokenSource.token.isCancellationRequested) {
return Promise.reject(new Error('Throttler is disposed'));
}
@ -245,7 +250,7 @@ export class Throttler implements IDisposable {
const onComplete = () => {
this.queuedPromise = null;
if (this.isDisposed) {
if (this.cancellationTokenSource.token.isCancellationRequested) {
return;
}
@ -265,7 +270,7 @@ export class Throttler implements IDisposable {
});
}
this.activePromise = promiseFactory();
this.activePromise = promiseFactory(this.cancellationTokenSource.token);
return new Promise((resolve, reject) => {
this.activePromise!.then((result: T) => {
@ -279,7 +284,7 @@ export class Throttler implements IDisposable {
}
dispose(): void {
this.isDisposed = true;
this.cancellationTokenSource.cancel();
}
}
@ -460,7 +465,7 @@ export class ThrottledDelayer<T> {
this.throttler = new Throttler();
}
trigger(promiseFactory: ITask<Promise<T>>, delay?: number): Promise<T> {
trigger(promiseFactory: ICancellableTask<Promise<T>>, delay?: number): Promise<T> {
return this.delayer.trigger(() => this.throttler.queue(promiseFactory), delay) as unknown as Promise<T>;
}
@ -1717,6 +1722,12 @@ const enum DeferredOutcome {
*/
export class DeferredPromise<T> {
public static fromPromise<T>(promise: Promise<T>): DeferredPromise<T> {
const deferred = new DeferredPromise<T>();
deferred.settleWith(promise);
return deferred;
}
private completeCallback!: ValueCallback<T>;
private errorCallback!: (err: unknown) => void;
private outcome?: { outcome: DeferredOutcome.Rejected; value: unknown } | { outcome: DeferredOutcome.Resolved; value: T };
@ -1747,6 +1758,10 @@ export class DeferredPromise<T> {
}
public complete(value: T) {
if (this.isSettled) {
return Promise.resolve();
}
return new Promise<void>(resolve => {
this.completeCallback(value);
this.outcome = { outcome: DeferredOutcome.Resolved, value };
@ -1755,6 +1770,10 @@ export class DeferredPromise<T> {
}
public error(err: unknown) {
if (this.isSettled) {
return Promise.resolve();
}
return new Promise<void>(resolve => {
this.errorCallback(err);
this.outcome = { outcome: DeferredOutcome.Rejected, value: err };

View File

@ -10,7 +10,7 @@ import * as streams from './stream';
interface NodeBuffer {
allocUnsafe(size: number): Uint8Array;
isBuffer(obj: any): obj is NodeBuffer;
isBuffer(obj: unknown): obj is NodeBuffer;
from(arrayBuffer: ArrayBufferLike, byteOffset?: number, length?: number): Uint8Array;
from(data: string): Uint8Array;
}

View File

@ -120,3 +120,36 @@ export class CachedFunction<TArg, TComputed> {
return value;
}
}
/**
* Uses an unbounded cache to memoize the results of the given function.
*/
export class WeakCachedFunction<TArg, TComputed> {
private readonly _map = new WeakMap<WeakKey, TComputed>();
private readonly _fn: (arg: TArg) => TComputed;
private readonly _computeKey: (arg: TArg) => unknown;
constructor(fn: (arg: TArg) => TComputed);
constructor(options: ICacheOptions<TArg>, fn: (arg: TArg) => TComputed);
constructor(arg1: ICacheOptions<TArg> | ((arg: TArg) => TComputed), arg2?: (arg: TArg) => TComputed) {
if (typeof arg1 === 'function') {
this._fn = arg1;
this._computeKey = identity;
} else {
this._fn = arg2!;
this._computeKey = arg1.getCacheKey;
}
}
public get(arg: TArg): TComputed {
const key = this._computeKey(arg) as WeakKey;
if (this._map.has(key)) {
return this._map.get(key)!;
}
const value = this._fn(arg);
this._map.set(key, value);
return value;
}
}

View File

@ -148,3 +148,61 @@ export function cancelOnDispose(store: DisposableStore): CancellationToken {
store.add({ dispose() { source.cancel(); } });
return source.token;
}
/**
* A pool that aggregates multiple cancellation tokens. The pool's own token
* (accessible via `pool.token`) is cancelled only after every token added
* to the pool has been cancelled. Adding tokens after the pool token has
* been cancelled has no effect.
*/
export class CancellationTokenPool {
private readonly _source = new CancellationTokenSource();
private readonly _listeners = new DisposableStore();
private _total: number = 0;
private _cancelled: number = 0;
private _isDone: boolean = false;
get token(): CancellationToken {
return this._source.token;
}
/**
* Add a token to the pool. If the token is already cancelled it is counted
* immediately. Tokens added after the pool token has been cancelled are ignored.
*/
add(token: CancellationToken): void {
if (this._isDone) {
return;
}
this._total++;
if (token.isCancellationRequested) {
this._cancelled++;
this._check();
return;
}
const d = token.onCancellationRequested(() => {
d.dispose();
this._cancelled++;
this._check();
});
this._listeners.add(d);
}
private _check(): void {
if (!this._isDone && this._total > 0 && this._total === this._cancelled) {
this._isDone = true;
this._listeners.dispose();
this._source.cancel();
}
}
dispose(): void {
this._listeners.dispose();
this._source.dispose();
}
}

View File

@ -84,7 +84,6 @@ export const codiconsLibrary = {
vm: register('vm', 0xea7a),
deviceDesktop: register('device-desktop', 0xea7a),
file: register('file', 0xea7b),
fileText: register('file-text', 0xea7b),
more: register('more', 0xea7c),
ellipsis: register('ellipsis', 0xea7c),
kebabHorizontal: register('kebab-horizontal', 0xea7c),
@ -615,4 +614,20 @@ export const codiconsLibrary = {
commentDiscussionSparkle: register('comment-discussion-sparkle', 0xec54),
chatSparkleWarning: register('chat-sparkle-warning', 0xec55),
chatSparkleError: register('chat-sparkle-error', 0xec56),
collection: register('collection', 0xec57),
newCollection: register('new-collection', 0xec58),
thinking: register('thinking', 0xec59),
build: register('build', 0xec5a),
commentDiscussionQuote: register('comment-discussion-quote', 0xec5b),
cursor: register('cursor', 0xec5c),
eraser: register('eraser', 0xec5d),
fileText: register('file-text', 0xec5e),
gitLens: register('git-lens', 0xec5f),
quotes: register('quotes', 0xec60),
rename: register('rename', 0xec61),
runWithDeps: register('run-with-deps', 0xec62),
debugConnected: register('debug-connected', 0xec63),
strikethrough: register('strikethrough', 0xec64),
openInProduct: register('open-in-product', 0xec65),
indexZero: register('index-zero', 0xec66),
} as const;

View File

@ -141,6 +141,7 @@ export function transformErrorForSerialization(error: any): any;
export function transformErrorForSerialization(error: any): any {
if (error instanceof Error) {
const { name, message, cause } = error;
// eslint-disable-next-line local/code-no-any-casts
const stack: string = (<any>error).stacktrace || (<any>error).stack;
return {
$isError: true,

View File

@ -361,14 +361,14 @@ export interface IPathWithLineAndColumn {
export function parseLineAndColumnAware(rawPath: string): IPathWithLineAndColumn {
const segments = rawPath.split(':'); // C:\file.txt:<line>:<column>
let path: string | undefined = undefined;
let line: number | undefined = undefined;
let column: number | undefined = undefined;
let path: string | undefined;
let line: number | undefined;
let column: number | undefined;
for (const segment of segments) {
const segmentAsNumber = Number(segment);
if (!isNumber(segmentAsNumber)) {
path = !!path ? [path, segment].join(':') : segment; // a colon can well be part of a path (e.g. C:\...)
path = path ? [path, segment].join(':') : segment; // a colon can well be part of a path (e.g. C:\...)
} else if (line === undefined) {
line = segmentAsNumber;
} else if (column === undefined) {

View File

@ -510,7 +510,7 @@ function toRegExp(pattern: string): ParsedStringPattern {
return typeof path === 'string' && regExp.test(path) ? pattern : null;
};
} catch (error) {
} catch {
return NULL;
}
}

View File

@ -58,16 +58,16 @@ export function stringHash(s: string, hashVal: number) {
return hashVal;
}
function arrayHash(arr: any[], initialHashVal: number): number {
function arrayHash(arr: unknown[], initialHashVal: number): number {
initialHashVal = numberHash(104579, initialHashVal);
return arr.reduce((hashVal, item) => doHash(item, hashVal), initialHashVal);
return arr.reduce<number>((hashVal, item) => doHash(item, hashVal), initialHashVal);
}
function objectHash(obj: any, initialHashVal: number): number {
function objectHash(obj: object, initialHashVal: number): number {
initialHashVal = numberHash(181387, initialHashVal);
return Object.keys(obj).sort().reduce((hashVal, key) => {
hashVal = stringHash(key, hashVal);
return doHash(obj[key], hashVal);
return doHash((obj as Record<string, unknown>)[key], hashVal);
}, initialHashVal);
}

View File

@ -201,12 +201,15 @@ export function parseHrefAndDimensions(href: string): { href: string; dimensions
return { href, dimensions };
}
export function markdownCommandLink(command: { title: string; id: string; arguments?: unknown[] }, escapeTokens = true): string {
const uri = URI.from({
scheme: Schemas.command,
path: command.id,
query: command.arguments?.length ? encodeURIComponent(JSON.stringify(command.arguments)) : undefined,
}).toString();
return `[${escapeTokens ? escapeMarkdownSyntaxTokens(command.title) : command.title}](${uri})`;
export function markdownCommandLink(command: { title: string; id: string; arguments?: unknown[]; tooltip?: string }, escapeTokens = true): string {
const uri = createCommandUri(command.id, ...(command.arguments || [])).toString();
return `[${escapeTokens ? escapeMarkdownSyntaxTokens(command.title) : command.title}](${uri}${command.tooltip ? ` "${escapeMarkdownSyntaxTokens(command.tooltip)}"` : ''})`;
}
export function createCommandUri(commandId: string, ...commandArgs: unknown[]): URI {
return URI.from({
scheme: Schemas.command,
path: commandId,
query: commandArgs.length ? encodeURIComponent(JSON.stringify(commandArgs)) : undefined,
});
}

View File

@ -208,7 +208,9 @@ export class DisposableTracker implements IDisposableTracker {
const continuations = groupBy([...prevStarts].map(d => getStackTracePath(d)[i]), v => v);
delete continuations[stackTracePath[i]];
for (const [cont, set] of Object.entries(continuations)) {
stackTraceFormattedLines.unshift(` - stacktraces of ${set.length} other leaks continue with ${cont}`);
if (set) {
stackTraceFormattedLines.unshift(` - stacktraces of ${set.length} other leaks continue with ${cont}`);
}
}
stackTraceFormattedLines.unshift(line);
@ -235,6 +237,7 @@ if (TRACK_DISPOSABLES) {
trackDisposable(x: IDisposable): void {
const stack = new Error('Potentially leaked disposable').stack!;
setTimeout(() => {
// eslint-disable-next-line local/code-no-any-casts
if (!(x as any)[__is_disposable_tracked__]) {
console.log(stack);
}
@ -244,6 +247,7 @@ if (TRACK_DISPOSABLES) {
setParent(child: IDisposable, parent: IDisposable | null): void {
if (child && child !== Disposable.None) {
try {
// eslint-disable-next-line local/code-no-any-casts
(child as any)[__is_disposable_tracked__] = true;
} catch {
// noop
@ -254,6 +258,7 @@ if (TRACK_DISPOSABLES) {
markAsDisposed(disposable: IDisposable): void {
if (disposable && disposable !== Disposable.None) {
try {
// eslint-disable-next-line local/code-no-any-casts
(disposable as any)[__is_disposable_tracked__] = true;
} catch {
// noop
@ -313,6 +318,7 @@ export interface IDisposable {
* Check if `thing` is {@link IDisposable disposable}.
*/
export function isDisposable<E extends any>(thing: E): thing is E & IDisposable {
// eslint-disable-next-line local/code-no-any-casts
return typeof thing === 'object' && thing !== null && typeof (<IDisposable><any>thing).dispose === 'function' && (<IDisposable><any>thing).dispose.length === 0;
}
@ -369,19 +375,36 @@ export function combinedDisposable(...disposables: IDisposable[]): IDisposable {
return parent;
}
class FunctionDisposable implements IDisposable {
private _isDisposed: boolean;
private readonly _fn: () => void;
constructor(fn: () => void) {
this._isDisposed = false;
this._fn = fn;
trackDisposable(this);
}
dispose() {
if (this._isDisposed) {
return;
}
if (!this._fn) {
throw new Error(`Unbound disposable context: Need to use an arrow function to preserve the value of this`);
}
this._isDisposed = true;
markAsDisposed(this);
this._fn();
}
}
/**
* Turn a function that implements dispose into an {@link IDisposable}.
*
* @param fn Clean up function, guaranteed to be called only **once**.
*/
export function toDisposable(fn: () => void): IDisposable {
const self = trackDisposable({
dispose: createSingleCallFunction(() => {
markAsDisposed(self);
fn();
})
});
return self;
return new FunctionDisposable(fn);
}
/**
@ -549,10 +572,25 @@ export class MutableDisposable<T extends IDisposable> implements IDisposable {
trackDisposable(this);
}
/**
* Get the currently held disposable value, or `undefined` if this MutableDisposable has been disposed
*/
get value(): T | undefined {
return this._isDisposed ? undefined : this._value;
}
/**
* Set a new disposable value.
*
* Behaviour:
* - If the MutableDisposable has been disposed, the setter is a no-op.
* - If the new value is strictly equal to the current value, the setter is a no-op.
* - Otherwise the previous value (if any) is disposed and the new value is stored.
*
* Related helpers:
* - clear() resets the value to `undefined` (and disposes the previous value).
* - clearAndLeak() returns the old value without disposing it and removes its parent.
*/
set value(value: T | undefined) {
if (this._isDisposed || value === this._value) {
return;
@ -651,7 +689,7 @@ export abstract class ReferenceCollection<T> {
private readonly references: Map<string, { readonly object: T; counter: number }> = new Map();
acquire(key: string, ...args: any[]): IReference<T> {
acquire(key: string, ...args: unknown[]): IReference<T> {
let reference = this.references.get(key);
if (!reference) {
@ -672,7 +710,7 @@ export abstract class ReferenceCollection<T> {
return { object, dispose };
}
protected abstract createReferencedObject(key: string, ...args: any[]): T;
protected abstract createReferencedObject(key: string, ...args: unknown[]): T;
protected abstract destroyReferencedObject(key: string, object: T): void;
}

View File

@ -123,7 +123,7 @@ export class ResourceMap<T> implements Map<URI, T> {
clb = clb.bind(thisArg);
}
for (const [_, entry] of this.map) {
clb(entry.value, entry.uri, <any>this);
clb(entry.value, entry.uri, this);
}
}

View File

@ -415,6 +415,7 @@ export namespace COI {
* isn't enabled the current context
*/
export function addSearchParam(urlOrSearch: URLSearchParams | Record<string, string>, coop: boolean, coep: boolean): void {
// eslint-disable-next-line local/code-no-any-casts
if (!(<any>globalThis).crossOriginIsolated) {
// depends on the current context being COI
return;

View File

@ -101,65 +101,6 @@ export function isPointWithinTriangle(
return u >= 0 && v >= 0 && u + v < 1;
}
/**
* Function to get a (pseudo)random integer from a provided `max`...[`min`] range.
* Both `min` and `max` values are inclusive. The `min` value is optional (defaults to `0`).
*
* @throws in the next cases:
* - if provided `min` or `max` is not a number
* - if provided `min` or `max` is not finite
* - if provided `min` is larger than `max` value
*
* ## Examples
*
* Specifying a `max` value only uses `0` as the `min` value by default:
*
* ```typescript
* // get a random integer between 0 and 10
* const randomInt = randomInt(10);
*
* assert(
* randomInt >= 0,
* 'Should be greater than or equal to 0.',
* );
*
* assert(
* randomInt <= 10,
* 'Should be less than or equal to 10.',
* );
* ```
* * Specifying both `max` and `min` values:
*
* ```typescript
* // get a random integer between 5 and 8
* const randomInt = randomInt(8, 5);
*
* assert(
* randomInt >= 5,
* 'Should be greater than or equal to 5.',
* );
*
* assert(
* randomInt <= 8,
* 'Should be less than or equal to 8.',
* );
* ```
*/
export function randomInt(max: number, min: number = 0): number {
assert(!isNaN(min), '"min" param is not a number.');
assert(!isNaN(max), '"max" param is not a number.');
assert(isFinite(max), '"max" param is not finite.');
assert(isFinite(min), '"min" param is not finite.');
assert(max > min, `"max"(${max}) param should be greater than "min"(${min}).`);
const delta = max - min;
const randomFloat = delta * Math.random();
return Math.round(min + randomFloat);
}
export function randomChance(p: number): boolean {
assert(p >= 0 && p <= 1, 'p must be between 0 and 1');
return Math.random() < p;

View File

@ -71,10 +71,10 @@ function _cloneAndChange(obj: any, changer: (orig: any) => any, seen: Set<any>):
throw new Error('Cannot clone recursive data-structure');
}
seen.add(obj);
const r2 = {};
const r2: Record<string, unknown> = {};
for (const i2 in obj) {
if (_hasOwnProperty.call(obj, i2)) {
(r2 as any)[i2] = _cloneAndChange(obj[i2], changer, seen);
r2[i2] = _cloneAndChange(obj[i2], changer, seen);
}
}
seen.delete(obj);

View File

@ -97,6 +97,11 @@ export interface IObservableWithChange<T, TChange = unknown> {
*/
readonly debugName: string;
/**
* ONLY FOR DEBUGGING!
*/
debugGetDependencyGraph(): string;
/**
* This property captures the type of the change object. Do not use it at runtime!
*/

View File

@ -33,6 +33,7 @@ export function recordChanges<TObs extends Record<any, IObservableWithChange<any
& { changes: readonly ({ [TKey in keyof TObs]: { key: TKey; change: TObs[TKey]['TChange'] } }[keyof TObs])[] }> {
return {
createChangeSummary: (_previousChangeSummary) => {
// eslint-disable-next-line local/code-no-any-casts
return {
changes: [],
} as any;
@ -40,6 +41,7 @@ export function recordChanges<TObs extends Record<any, IObservableWithChange<any
handleChange(ctx, changeSummary) {
for (const key in obs) {
if (ctx.didChange(obs[key])) {
// eslint-disable-next-line local/code-no-any-casts
(changeSummary.changes as any).push({ key, change: ctx.change });
}
}
@ -66,6 +68,7 @@ export function recordChangesLazy<TObs extends Record<any, IObservableWithChange
let obs: TObs | undefined = undefined;
return {
createChangeSummary: (_previousChangeSummary) => {
// eslint-disable-next-line local/code-no-any-casts
return {
changes: [],
} as any;
@ -76,6 +79,7 @@ export function recordChangesLazy<TObs extends Record<any, IObservableWithChange
}
for (const key in obs) {
if (ctx.didChange(obs[key])) {
// eslint-disable-next-line local/code-no-any-casts
(changeSummary.changes as any).push({ key, change: ctx.change });
}
}

View File

@ -18,6 +18,7 @@ export namespace DebugLocation {
if (!enabled) {
return undefined;
}
// eslint-disable-next-line local/code-no-any-casts
const Err = Error as any as { stackTraceLimit: number }; // For the monaco editor checks, which don't have the nodejs types.
const l = Err.stackTraceLimit;

View File

@ -105,6 +105,7 @@ function computeDebugName(self: object, data: DebugNameData): string | undefined
function findKey(obj: object, value: object): string | undefined {
for (const key in obj) {
// eslint-disable-next-line local/code-no-any-casts
if ((obj as any)[key] === value) {
return key;
}

View File

@ -42,8 +42,10 @@ import { addLogger, setLogObservableFn } from './logging/logging';
import { ConsoleObservableLogger, logObservableToConsole } from './logging/consoleObservableLogger';
import { DevToolsLogger } from './logging/debugger/devToolsLogger';
import { env } from '../process';
import { _setDebugGetDependencyGraph } from './observables/baseObservable';
import { debugGetDependencyGraph } from './logging/debugGetDependencyGraph';
_setDebugGetDependencyGraph(debugGetDependencyGraph);
setLogObservableFn(logObservableToConsole);
// Remove "//" in the next line to enable logging

View File

@ -79,6 +79,7 @@ export class ConsoleObservableLogger implements IObservableLogger {
const debugTrackUpdating = false;
if (debugTrackUpdating) {
const updating: IObservable<any>[] = [];
// eslint-disable-next-line local/code-no-any-casts
(derived as any).__debugUpdating = updating;
const existingBeginUpdate = derived.beginUpdate;

View File

@ -0,0 +1,117 @@
//!!! DO NOT modify, this file was COPIED from 'microsoft/vscode'
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IObservable, IObserver } from '../base';
import { Derived } from '../observables/derivedImpl';
import { FromEventObservable } from '../observables/observableFromEvent';
import { ObservableValue } from '../observables/observableValue';
import { AutorunObserver } from '../reactions/autorunImpl';
import { formatValue } from './consoleObservableLogger';
export function debugGetDependencyGraph(obs: IObservable<any> | IObserver, options?: { debugNamePostProcessor?: (name: string) => string }): string {
const debugNamePostProcessor = options?.debugNamePostProcessor ?? ((str: string) => str);
const info = Info.from(obs, debugNamePostProcessor);
if (!info) {
return '';
}
const alreadyListed = new Set<IObservable<any> | IObserver>();
return formatObservableInfo(info, 0, alreadyListed).trim();
}
function formatObservableInfo(info: Info, indentLevel: number, alreadyListed: Set<IObservable<any> | IObserver>): string {
const indent = '\t\t'.repeat(indentLevel);
const lines: string[] = [];
const isAlreadyListed = alreadyListed.has(info.sourceObj);
if (isAlreadyListed) {
lines.push(`${indent}* ${info.type} ${info.name} (already listed)`);
return lines.join('\n');
}
alreadyListed.add(info.sourceObj);
lines.push(`${indent}* ${info.type} ${info.name}:`);
lines.push(`${indent} value: ${formatValue(info.value, 50)}`);
lines.push(`${indent} state: ${info.state}`);
if (info.dependencies.length > 0) {
lines.push(`${indent} dependencies:`);
for (const dep of info.dependencies) {
lines.push(formatObservableInfo(dep, indentLevel + 1, alreadyListed));
}
}
return lines.join('\n');
}
class Info {
public static from(obs: IObservable<any> | IObserver, debugNamePostProcessor: (name: string) => string): Info | undefined {
if (obs instanceof AutorunObserver) {
const state = obs.debugGetState();
return new Info(
obs,
debugNamePostProcessor(obs.debugName),
'autorun',
undefined,
state.stateStr,
Array.from(state.dependencies).map(dep => Info.from(dep, debugNamePostProcessor) || Info.unknown(dep))
);
} else if (obs instanceof Derived) {
const state = obs.debugGetState();
return new Info(
obs,
debugNamePostProcessor(obs.debugName),
'derived',
state.value,
state.stateStr,
Array.from(state.dependencies).map(dep => Info.from(dep, debugNamePostProcessor) || Info.unknown(dep))
);
} else if (obs instanceof ObservableValue) {
const state = obs.debugGetState();
return new Info(
obs,
debugNamePostProcessor(obs.debugName),
'observableValue',
state.value,
'upToDate',
[]
);
} else if (obs instanceof FromEventObservable) {
const state = obs.debugGetState();
return new Info(
obs,
debugNamePostProcessor(obs.debugName),
'fromEvent',
state.value,
state.hasValue ? 'upToDate' : 'initial',
[]
);
}
return undefined;
}
public static unknown(obs: IObservable<any> | IObserver): Info {
return new Info(
obs,
'(unknown)',
'unknown',
undefined,
'unknown',
[]
);
}
constructor(
public readonly sourceObj: IObservable<any> | IObserver,
public readonly name: string,
public readonly type: string,
public readonly value: any,
public readonly state: string,
public readonly dependencies: Info[]
) { }
}

View File

@ -11,6 +11,7 @@ export function registerDebugChannel<T extends { channelId: string } & API>(
channelId: T['channelId'],
createClient: () => T['client'],
): SimpleTypedRpcConnection<MakeSideAsync<T['host']>> {
// eslint-disable-next-line local/code-no-any-casts
const g = globalThis as any as GlobalObj;
let queuedNotifications: unknown[] = [];

View File

@ -93,6 +93,7 @@ export class SimpleTypedRpcConnection<T extends Side> {
}
});
// eslint-disable-next-line local/code-no-any-casts
this.api = { notifications: notifications, requests: requests } as any;
}
}

View File

@ -9,6 +9,7 @@ import { IObservableWithChange, IObserver, IReader, IObservable } from '../base'
import { DisposableStore } from '../commonFacade/deps';
import { DebugLocation } from '../debugLocation';
import { DebugOwner, getFunctionName } from '../debugName';
import { debugGetDependencyGraph } from '../logging/debugGetDependencyGraph';
import { getLogger, logObservable } from '../logging/logging';
import type { keepObserved, recomputeInitiallyAndOnChange } from '../utils/utils';
import { derivedOpts } from './derived';
@ -32,6 +33,11 @@ export function _setKeepObserved(keepObserved: typeof _keepObserved) {
_keepObserved = keepObserved;
}
let _debugGetDependencyGraph: typeof debugGetDependencyGraph;
export function _setDebugGetDependencyGraph(debugGetDependencyGraph: typeof _debugGetDependencyGraph) {
_debugGetDependencyGraph = debugGetDependencyGraph;
}
export abstract class ConvenientObservable<T, TChange> implements IObservableWithChange<T, TChange> {
get TChange(): TChange { return null!; }
@ -123,6 +129,10 @@ export abstract class ConvenientObservable<T, TChange> implements IObservableWit
protected get debugValue() {
return this.get();
}
debugGetDependencyGraph(): string {
return _debugGetDependencyGraph(this);
}
}
export abstract class BaseObservable<T, TChange = void> extends ConvenientObservable<T, TChange> {

View File

@ -37,7 +37,9 @@ export function derived<T, TChange = void>(
);
}
return new Derived(
// eslint-disable-next-line local/code-no-any-casts
new DebugNameData(undefined, undefined, computeFnOrOwner as any),
// eslint-disable-next-line local/code-no-any-casts
computeFnOrOwner as any,
undefined,
undefined,
@ -121,10 +123,12 @@ export function derivedWithStore<T>(computeFnOrOwner: ((reader: IReader, store:
let computeFn: (reader: IReader, store: DisposableStore) => T;
let owner: DebugOwner;
if (computeFnOrUndefined === undefined) {
// eslint-disable-next-line local/code-no-any-casts
computeFn = computeFnOrOwner as any;
owner = undefined;
} else {
owner = computeFnOrOwner;
// eslint-disable-next-line local/code-no-any-casts
computeFn = computeFnOrUndefined as any;
}
@ -155,10 +159,12 @@ export function derivedDisposable<T extends IDisposable | undefined>(computeFnOr
let computeFn: (reader: IReader) => T;
let owner: DebugOwner;
if (computeFnOrUndefined === undefined) {
// eslint-disable-next-line local/code-no-any-casts
computeFn = computeFnOrOwner as any;
owner = undefined;
} else {
owner = computeFnOrOwner;
// eslint-disable-next-line local/code-no-any-casts
computeFn = computeFnOrUndefined as any;
}

View File

@ -42,6 +42,16 @@ export const enum DerivedState {
upToDate = 3,
}
function derivedStateToString(state: DerivedState): string {
switch (state) {
case DerivedState.initial: return 'initial';
case DerivedState.dependenciesMightHaveChanged: return 'dependenciesMightHaveChanged';
case DerivedState.stale: return 'stale';
case DerivedState.upToDate: return 'upToDate';
default: return '<unknown>';
}
}
export class Derived<T, TChangeSummary = any, TChange = void> extends BaseObservable<T, TChange> implements IDerivedReader<TChange>, IObserver {
private _state = DerivedState.initial;
private _value: T | undefined = undefined;
@ -302,6 +312,7 @@ export class Derived<T, TChangeSummary = any, TChange = void> extends BaseObserv
shouldReact = this._changeTracker ? this._changeTracker.handleChange({
changedObservable: observable,
change,
// eslint-disable-next-line local/code-no-any-casts
didChange: (o): this is any => o === observable as any,
}, this._changeSummary!) : true;
} catch (e) {
@ -393,6 +404,7 @@ export class Derived<T, TChangeSummary = any, TChange = void> extends BaseObserv
public debugGetState() {
return {
state: this._state,
stateStr: derivedStateToString(this._state),
updateCount: this._updateCount,
isComputing: this._isComputing,
dependencies: this._dependencies,
@ -401,6 +413,7 @@ export class Derived<T, TChangeSummary = any, TChange = void> extends BaseObserv
}
public debugSetValue(newValue: unknown) {
// eslint-disable-next-line local/code-no-any-casts
this._value = newValue as any;
}

View File

@ -149,9 +149,14 @@ export class FromEventObservable<TArgs, T> extends BaseObservable<T> {
}
}
public debugSetValue(value: unknown) {
public debugSetValue(value: unknown): void {
// eslint-disable-next-line local/code-no-any-casts
this._value = value as any;
}
public debugGetState() {
return { value: this._value, hasValue: this._hasValue };
}
}
export namespace observableFromEvent {

View File

@ -26,6 +26,15 @@ export const enum AutorunState {
upToDate = 3,
}
function autorunStateToString(state: AutorunState): string {
switch (state) {
case AutorunState.dependenciesMightHaveChanged: return 'dependenciesMightHaveChanged';
case AutorunState.stale: return 'stale';
case AutorunState.upToDate: return 'upToDate';
default: return '<unknown>';
}
}
export class AutorunObserver<TChangeSummary = any> implements IObserver, IReaderWithStore, IDisposable {
private _state = AutorunState.stale;
private _updateCount = 0;
@ -175,6 +184,7 @@ export class AutorunObserver<TChangeSummary = any> implements IObserver, IReader
const shouldReact = this._changeTracker ? this._changeTracker.handleChange({
changedObservable: observable,
change,
// eslint-disable-next-line local/code-no-any-casts
didChange: (o): this is any => o === observable as any,
}, this._changeSummary!) : true;
if (shouldReact) {
@ -243,6 +253,7 @@ export class AutorunObserver<TChangeSummary = any> implements IObserver, IReader
updateCount: this._updateCount,
dependencies: this._dependencies,
state: this._state,
stateStr: autorunStateToString(this._state),
};
}

View File

@ -50,6 +50,7 @@ export class ObservableSet<T> implements Set<T> {
forEach(callbackfn: (value: T, value2: T, set: Set<T>) => void, thisArg?: any): void {
this._data.forEach((value, value2, _set) => {
// eslint-disable-next-line local/code-no-any-casts
callbackfn.call(thisArg, value, value2, this as any);
});
}

View File

@ -43,6 +43,10 @@ export class ObservablePromise<T> {
return new ObservablePromise(fn());
}
public static resolved<T>(value: T): ObservablePromise<T> {
return new ObservablePromise(Promise.resolve(value));
}
private readonly _value = observableValue<PromiseResult<T> | undefined>(this, undefined);
/**

View File

@ -77,10 +77,12 @@ export function derivedWithCancellationToken<T>(computeFnOrOwner: ((reader: IRea
let computeFn: (reader: IReader, store: CancellationToken) => T;
let owner: DebugOwner;
if (computeFnOrUndefined === undefined) {
// eslint-disable-next-line local/code-no-any-casts
computeFn = computeFnOrOwner as any;
owner = undefined;
} else {
owner = computeFnOrOwner;
// eslint-disable-next-line local/code-no-any-casts
computeFn = computeFnOrUndefined as any;
}

View File

@ -280,3 +280,7 @@ export const isAndroid = !!(userAgent && userAgent.indexOf('Android') >= 0);
export function isBigSurOrNewer(osVersion: string): boolean {
return parseFloat(osVersion) >= 20;
}
export function isTahoeOrNewer(osVersion: string): boolean {
return parseFloat(osVersion) >= 25;
}

View File

@ -11,7 +11,7 @@ let safeProcess: Omit<INodeProcess, 'arch'> & { arch: string | undefined };
declare const process: INodeProcess;
// Native sandbox environment
const vscodeGlobal = (globalThis as any).vscode;
const vscodeGlobal = (globalThis as { vscode?: { process?: INodeProcess } }).vscode;
if (typeof vscodeGlobal !== 'undefined' && typeof vscodeGlobal.process !== 'undefined') {
const sandboxProcess: INodeProcess = vscodeGlobal.process;
safeProcess = {

View File

@ -333,14 +333,14 @@ class WriteableStreamImpl<T> implements WriteableStream<T> {
on(event: 'data', callback: (data: T) => void): void;
on(event: 'error', callback: (err: Error) => void): void;
on(event: 'end', callback: () => void): void;
on(event: 'data' | 'error' | 'end', callback: (arg0?: any) => void): void {
on(event: 'data' | 'error' | 'end', callback: ((data: T) => void) | ((err: Error) => void) | (() => void)): void {
if (this.state.destroyed) {
return;
}
switch (event) {
case 'data':
this.listeners.data.push(callback);
this.listeners.data.push(callback as (data: T) => void);
// switch into flowing mode as soon as the first 'data'
// listener is added and we are not yet in flowing mode
@ -349,7 +349,7 @@ class WriteableStreamImpl<T> implements WriteableStream<T> {
break;
case 'end':
this.listeners.end.push(callback);
this.listeners.end.push(callback as () => void);
// emit 'end' event directly if we are flowing
// and the end has already been reached
@ -362,7 +362,7 @@ class WriteableStreamImpl<T> implements WriteableStream<T> {
break;
case 'error':
this.listeners.error.push(callback);
this.listeners.error.push(callback as (err: Error) => void);
// emit buffered 'error' events unless done already
// now that we know that we have at least one listener

View File

@ -194,10 +194,6 @@ export function convertSimple2RegExpPattern(pattern: string): string {
return pattern.replace(/[\-\\\{\}\+\?\|\^\$\.\,\[\]\(\)\#\s]/g, '\\$&').replace(/[\*]/g, '.*');
}
export function stripWildcards(pattern: string): string {
return pattern.replace(/\*/g, '');
}
export interface RegExpOptions {
matchCase?: boolean;
wholeWord?: boolean;

View File

@ -103,4 +103,17 @@ export namespace ThemeIcon {
return ti1.id === ti2.id && ti1.color?.id === ti2.color?.id;
}
/**
* Returns whether specified icon is defined and has 'file' ID.
*/
export function isFile(icon: ThemeIcon | undefined): boolean {
return icon?.id === Codicon.file.id;
}
/**
* Returns whether specified icon is defined and has 'folder' ID.
*/
export function isFolder(icon: ThemeIcon | undefined): boolean {
return icon?.id === Codicon.folder.id;
}
}

View File

@ -18,7 +18,14 @@ export function isString(str: unknown): str is string {
* @returns whether the provided parameter is a JavaScript Array and each element in the array is a string.
*/
export function isStringArray(value: unknown): value is string[] {
return Array.isArray(value) && (<unknown[]>value).every(elem => isString(elem));
return isArrayOf(value, isString);
}
/**
* @returns whether the provided parameter is a JavaScript Array and each element in the array satisfies the provided type guard.
*/
export function isArrayOf<T>(value: unknown, check: (item: unknown) => item is T): value is T[] {
return Array.isArray(value) && value.every(check);
}
/**
@ -57,6 +64,7 @@ export function isNumber(obj: unknown): obj is number {
* @returns whether the provided parameter is an Iterable, casting to the given generic
*/
export function isIterable<T>(obj: unknown): obj is Iterable<T> {
// eslint-disable-next-line local/code-no-any-casts
return !!obj && typeof (obj as any)[Symbol.iterator] === 'function';
}
@ -64,6 +72,7 @@ export function isIterable<T>(obj: unknown): obj is Iterable<T> {
* @returns whether the provided parameter is an Iterable, casting to the given generic
*/
export function isAsyncIterable<T>(obj: unknown): obj is AsyncIterable<T> {
// eslint-disable-next-line local/code-no-any-casts
return !!obj && typeof (obj as any)[Symbol.asyncIterator] === 'function';
}
@ -265,6 +274,7 @@ export function validateConstraint(arg: unknown, constraint: TypeConstraint | un
} catch {
// ignore
}
// eslint-disable-next-line local/code-no-any-casts
if (!isUndefinedOrNull(arg) && (arg as any).constructor === constraint) {
return;
}

View File

@ -0,0 +1,894 @@
//!!! DO NOT modify, this file was COPIED from 'microsoft/vscode'
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Parses a simplified YAML-like input from a single string.
* Supports objects, arrays, primitive types (string, number, boolean, null).
* Tracks positions for error reporting and node locations.
*
* Limitations:
* - No multi-line strings or block literals
* - No anchors or references
* - No complex types (dates, binary)
* - No special handling for escape sequences in strings
* - Indentation must be consistent (spaces only, no tabs)
*
* Notes:
* - New line separators can be either "\n" or "\r\n". The input string is split into lines internally.
*
* @param input A string containing the YAML-like input
* @param errors Array to collect parsing errors
* @param options Parsing options
* @returns The parsed representation (ObjectNode, ArrayNode, or primitive node)
*/
export function parse(input: string, errors: YamlParseError[] = [], options: ParseOptions = {}): YamlNode | undefined {
// Normalize both LF and CRLF by splitting on either; CR characters are not retained as part of line text.
// This keeps the existing line/character based lexer logic intact.
const lines = input.length === 0 ? [] : input.split(/\r\n|\n/);
const parser = new YamlParser(lines, errors, options);
return parser.parse();
}
export interface YamlParseError {
readonly message: string;
readonly start: Position;
readonly end: Position;
readonly code: string;
}
export interface ParseOptions {
readonly allowDuplicateKeys?: boolean;
}
export interface Position {
readonly line: number;
readonly character: number;
}
export interface YamlStringNode {
readonly type: 'string';
readonly value: string;
readonly start: Position;
readonly end: Position;
}
export interface YamlNumberNode {
readonly type: 'number';
readonly value: number;
readonly start: Position;
readonly end: Position;
}
export interface YamlBooleanNode {
readonly type: 'boolean';
readonly value: boolean;
readonly start: Position;
readonly end: Position;
}
export interface YamlNullNode {
readonly type: 'null';
readonly value: null;
readonly start: Position;
readonly end: Position;
}
export interface YamlObjectNode {
readonly type: 'object';
readonly properties: { key: YamlStringNode; value: YamlNode }[];
readonly start: Position;
readonly end: Position;
}
export interface YamlArrayNode {
readonly type: 'array';
readonly items: YamlNode[];
readonly start: Position;
readonly end: Position;
}
export type YamlNode = YamlStringNode | YamlNumberNode | YamlBooleanNode | YamlNullNode | YamlObjectNode | YamlArrayNode;
// Helper functions for position and node creation
function createPosition(line: number, character: number): Position {
return { line, character };
}
// Specialized node creation functions using a more concise approach
function createStringNode(value: string, start: Position, end: Position): YamlStringNode {
return { type: 'string', value, start, end };
}
function createNumberNode(value: number, start: Position, end: Position): YamlNumberNode {
return { type: 'number', value, start, end };
}
function createBooleanNode(value: boolean, start: Position, end: Position): YamlBooleanNode {
return { type: 'boolean', value, start, end };
}
function createNullNode(start: Position, end: Position): YamlNullNode {
return { type: 'null', value: null, start, end };
}
function createObjectNode(properties: { key: YamlStringNode; value: YamlNode }[], start: Position, end: Position): YamlObjectNode {
return { type: 'object', start, end, properties };
}
function createArrayNode(items: YamlNode[], start: Position, end: Position): YamlArrayNode {
return { type: 'array', start, end, items };
}
// Utility functions for parsing
function isWhitespace(char: string): boolean {
return char === ' ' || char === '\t';
}
// Simplified number validation using regex
function isValidNumber(value: string): boolean {
return /^-?\d*\.?\d+$/.test(value);
}
// Lexer/Tokenizer for YAML content
class YamlLexer {
private lines: string[];
private currentLine: number = 0;
private currentChar: number = 0;
constructor(lines: string[]) {
this.lines = lines;
}
getCurrentPosition(): Position {
return createPosition(this.currentLine, this.currentChar);
}
getCurrentLineNumber(): number {
return this.currentLine;
}
getCurrentCharNumber(): number {
return this.currentChar;
}
getCurrentLineText(): string {
return this.currentLine < this.lines.length ? this.lines[this.currentLine] : '';
}
savePosition(): { line: number; char: number } {
return { line: this.currentLine, char: this.currentChar };
}
restorePosition(pos: { line: number; char: number }): void {
this.currentLine = pos.line;
this.currentChar = pos.char;
}
isAtEnd(): boolean {
return this.currentLine >= this.lines.length;
}
getCurrentChar(): string {
if (this.isAtEnd() || this.currentChar >= this.lines[this.currentLine].length) {
return '';
}
return this.lines[this.currentLine][this.currentChar];
}
peek(offset: number = 1): string {
const newChar = this.currentChar + offset;
if (this.currentLine >= this.lines.length || newChar >= this.lines[this.currentLine].length) {
return '';
}
return this.lines[this.currentLine][newChar];
}
advance(): string {
const char = this.getCurrentChar();
if (this.currentChar >= this.lines[this.currentLine].length && this.currentLine < this.lines.length - 1) {
this.currentLine++;
this.currentChar = 0;
} else {
this.currentChar++;
}
return char;
}
advanceLine(): void {
this.currentLine++;
this.currentChar = 0;
}
skipWhitespace(): void {
while (!this.isAtEnd() && this.currentChar < this.lines[this.currentLine].length && isWhitespace(this.getCurrentChar())) {
this.advance();
}
}
skipToEndOfLine(): void {
this.currentChar = this.lines[this.currentLine].length;
}
getIndentation(): number {
if (this.isAtEnd()) {
return 0;
}
let indent = 0;
for (let i = 0; i < this.lines[this.currentLine].length; i++) {
if (this.lines[this.currentLine][i] === ' ') {
indent++;
} else if (this.lines[this.currentLine][i] === '\t') {
indent += 4; // Treat tab as 4 spaces
} else {
break;
}
}
return indent;
}
moveToNextNonEmptyLine(): void {
while (this.currentLine < this.lines.length) {
// First check current line from current position
if (this.currentChar < this.lines[this.currentLine].length) {
const remainingLine = this.lines[this.currentLine].substring(this.currentChar).trim();
if (remainingLine.length > 0 && !remainingLine.startsWith('#')) {
this.skipWhitespace();
return;
}
}
// Move to next line and check from beginning
this.currentLine++;
this.currentChar = 0;
if (this.currentLine < this.lines.length) {
const line = this.lines[this.currentLine].trim();
if (line.length > 0 && !line.startsWith('#')) {
this.skipWhitespace();
return;
}
}
}
}
}
// Parser class for handling YAML parsing
class YamlParser {
private lexer: YamlLexer;
private errors: YamlParseError[];
private options: ParseOptions;
// Track nesting level of flow (inline) collections '[' ']' '{' '}'
private flowLevel: number = 0;
constructor(lines: string[], errors: YamlParseError[], options: ParseOptions) {
this.lexer = new YamlLexer(lines);
this.errors = errors;
this.options = options;
}
addError(message: string, code: string, start: Position, end: Position): void {
this.errors.push({ message, code, start, end });
}
parseValue(expectedIndent?: number): YamlNode {
this.lexer.skipWhitespace();
if (this.lexer.isAtEnd()) {
const pos = this.lexer.getCurrentPosition();
return createStringNode('', pos, pos);
}
const char = this.lexer.getCurrentChar();
// Handle quoted strings
if (char === '"' || char === `'`) {
return this.parseQuotedString(char);
}
// Handle inline arrays
if (char === '[') {
return this.parseInlineArray();
}
// Handle inline objects
if (char === '{') {
return this.parseInlineObject();
}
// Handle unquoted values
return this.parseUnquotedValue();
}
parseQuotedString(quote: string): YamlNode {
const start = this.lexer.getCurrentPosition();
this.lexer.advance(); // Skip opening quote
let value = '';
while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '' && this.lexer.getCurrentChar() !== quote) {
value += this.lexer.advance();
}
if (this.lexer.getCurrentChar() === quote) {
this.lexer.advance(); // Skip closing quote
}
const end = this.lexer.getCurrentPosition();
return createStringNode(value, start, end);
}
parseUnquotedValue(): YamlNode {
const start = this.lexer.getCurrentPosition();
let value = '';
let endPos = start;
// Helper function to check for value terminators
const isTerminator = (char: string): boolean => {
if (char === '#') { return true; }
// Comma, ']' and '}' only terminate inside flow collections
if (this.flowLevel > 0 && (char === ',' || char === ']' || char === '}')) { return true; }
return false;
};
// Handle opening quote that might not be closed
const firstChar = this.lexer.getCurrentChar();
if (firstChar === '"' || firstChar === `'`) {
value += this.lexer.advance();
endPos = this.lexer.getCurrentPosition();
while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '') {
const char = this.lexer.getCurrentChar();
if (char === firstChar || isTerminator(char)) {
break;
}
value += this.lexer.advance();
endPos = this.lexer.getCurrentPosition();
}
} else {
while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '') {
const char = this.lexer.getCurrentChar();
if (isTerminator(char)) {
break;
}
value += this.lexer.advance();
endPos = this.lexer.getCurrentPosition();
}
}
const trimmed = value.trimEnd();
const diff = value.length - trimmed.length;
if (diff) {
endPos = createPosition(start.line, endPos.character - diff);
}
const finalValue = (firstChar === '"' || firstChar === `'`) ? trimmed.substring(1) : trimmed;
return this.createValueNode(finalValue, start, endPos);
}
private createValueNode(value: string, start: Position, end: Position): YamlNode {
if (value === '') {
return createStringNode('', start, start);
}
// Boolean values
if (value === 'true') {
return createBooleanNode(true, start, end);
}
if (value === 'false') {
return createBooleanNode(false, start, end);
}
// Null values
if (value === 'null' || value === '~') {
return createNullNode(start, end);
}
// Number values
const numberValue = Number(value);
if (!isNaN(numberValue) && isFinite(numberValue) && isValidNumber(value)) {
return createNumberNode(numberValue, start, end);
}
// Default to string
return createStringNode(value, start, end);
}
parseInlineArray(): YamlArrayNode {
const start = this.lexer.getCurrentPosition();
this.lexer.advance(); // Skip '['
this.flowLevel++;
const items: YamlNode[] = [];
while (!this.lexer.isAtEnd()) {
this.lexer.skipWhitespace();
// Handle end of array
if (this.lexer.getCurrentChar() === ']') {
this.lexer.advance();
break;
}
// Handle end of line - continue to next line for multi-line arrays
if (this.lexer.getCurrentChar() === '') {
this.lexer.advanceLine();
continue;
}
// Handle comments - comments should terminate the array parsing
if (this.lexer.getCurrentChar() === '#') {
// Skip the rest of the line (comment)
this.lexer.skipToEndOfLine();
this.lexer.advanceLine();
continue;
}
// Save position before parsing to detect if we're making progress
const positionBefore = this.lexer.savePosition();
// Parse array item
const item = this.parseValue();
// Skip implicit empty items that arise from a leading comma at the beginning of a new line
// (e.g. a line starting with ",foo" after a comment). A legitimate empty string element
// would have quotes and thus a non-zero span. We only filter zero-length spans.
if (!(item.type === 'string' && item.value === '' && item.start.line === item.end.line && item.start.character === item.end.character)) {
items.push(item);
}
// Check if we made progress - if not, we're likely stuck
const positionAfter = this.lexer.savePosition();
if (positionBefore.line === positionAfter.line && positionBefore.char === positionAfter.char) {
// No progress made, advance at least one character to prevent infinite loop
if (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '') {
this.lexer.advance();
} else {
break;
}
}
this.lexer.skipWhitespace();
// Handle comma separator
if (this.lexer.getCurrentChar() === ',') {
this.lexer.advance();
}
}
const end = this.lexer.getCurrentPosition();
this.flowLevel--;
return createArrayNode(items, start, end);
}
parseInlineObject(): YamlObjectNode {
const start = this.lexer.getCurrentPosition();
this.lexer.advance(); // Skip '{'
this.flowLevel++;
const properties: { key: YamlStringNode; value: YamlNode }[] = [];
while (!this.lexer.isAtEnd()) {
this.lexer.skipWhitespace();
// Handle end of object
if (this.lexer.getCurrentChar() === '}') {
this.lexer.advance();
break;
}
// Handle comments - comments should terminate the object parsing
if (this.lexer.getCurrentChar() === '#') {
// Skip the rest of the line (comment)
this.lexer.skipToEndOfLine();
this.lexer.advanceLine();
continue;
}
// Save position before parsing to detect if we're making progress
const positionBefore = this.lexer.savePosition();
// Parse key - read until colon
const keyStart = this.lexer.getCurrentPosition();
let keyValue = '';
// Handle quoted keys
if (this.lexer.getCurrentChar() === '"' || this.lexer.getCurrentChar() === `'`) {
const quote = this.lexer.getCurrentChar();
this.lexer.advance(); // Skip opening quote
while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '' && this.lexer.getCurrentChar() !== quote) {
keyValue += this.lexer.advance();
}
if (this.lexer.getCurrentChar() === quote) {
this.lexer.advance(); // Skip closing quote
}
} else {
// Handle unquoted keys - read until colon
while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '' && this.lexer.getCurrentChar() !== ':') {
keyValue += this.lexer.advance();
}
}
keyValue = keyValue.trim();
const keyEnd = this.lexer.getCurrentPosition();
const key = createStringNode(keyValue, keyStart, keyEnd);
this.lexer.skipWhitespace();
// Expect colon
if (this.lexer.getCurrentChar() === ':') {
this.lexer.advance();
}
this.lexer.skipWhitespace();
// Parse value
const value = this.parseValue();
properties.push({ key, value });
// Check if we made progress - if not, we're likely stuck
const positionAfter = this.lexer.savePosition();
if (positionBefore.line === positionAfter.line && positionBefore.char === positionAfter.char) {
// No progress made, advance at least one character to prevent infinite loop
if (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '') {
this.lexer.advance();
} else {
break;
}
}
this.lexer.skipWhitespace();
// Handle comma separator
if (this.lexer.getCurrentChar() === ',') {
this.lexer.advance();
}
}
const end = this.lexer.getCurrentPosition();
this.flowLevel--;
return createObjectNode(properties, start, end);
}
parseBlockArray(baseIndent: number): YamlArrayNode {
const start = this.lexer.getCurrentPosition();
const items: YamlNode[] = [];
while (!this.lexer.isAtEnd()) {
this.lexer.moveToNextNonEmptyLine();
if (this.lexer.isAtEnd()) {
break;
}
const currentIndent = this.lexer.getIndentation();
// If indentation is less than expected, we're done with this array
if (currentIndent < baseIndent) {
break;
}
this.lexer.skipWhitespace();
// Check for array item marker
if (this.lexer.getCurrentChar() === '-') {
this.lexer.advance(); // Skip '-'
this.lexer.skipWhitespace();
const itemStart = this.lexer.getCurrentPosition();
// Check if this is a nested structure
if (this.lexer.getCurrentChar() === '' || this.lexer.getCurrentChar() === '#') {
// Empty item - check if next lines form a nested structure
this.lexer.advanceLine();
if (!this.lexer.isAtEnd()) {
const nextIndent = this.lexer.getIndentation();
if (nextIndent > currentIndent) {
// Check if the next line starts with a dash (nested array) or has properties (nested object)
this.lexer.skipWhitespace();
if (this.lexer.getCurrentChar() === '-') {
// It's a nested array
const nestedArray = this.parseBlockArray(nextIndent);
items.push(nestedArray);
} else {
// Check if it looks like an object property (has a colon)
const currentLine = this.lexer.getCurrentLineText();
const currentPos = this.lexer.getCurrentCharNumber();
const remainingLine = currentLine.substring(currentPos);
if (remainingLine.includes(':') && !remainingLine.trim().startsWith('#')) {
// It's a nested object
const nestedObject = this.parseBlockObject(nextIndent, this.lexer.getCurrentCharNumber());
items.push(nestedObject);
} else {
// Not a nested structure, create empty string
items.push(createStringNode('', itemStart, itemStart));
}
}
} else {
// No nested content, empty item
items.push(createStringNode('', itemStart, itemStart));
}
} else {
// End of input, empty item
items.push(createStringNode('', itemStart, itemStart));
}
} else {
// Parse the item value
// Check if this is a multi-line object by looking for a colon and checking next lines
const currentLine = this.lexer.getCurrentLineText();
const currentPos = this.lexer.getCurrentCharNumber();
const remainingLine = currentLine.substring(currentPos);
// Check if there's a colon on this line (indicating object properties)
const hasColon = remainingLine.includes(':');
if (hasColon) {
// Any line with a colon should be treated as an object
// Parse as an object with the current item's indentation as the base
const item = this.parseBlockObject(itemStart.character, itemStart.character);
items.push(item);
} else {
// No colon, parse as regular value
const item = this.parseValue();
items.push(item);
// Skip to end of line
while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '' && this.lexer.getCurrentChar() !== '#') {
this.lexer.advance();
}
this.lexer.advanceLine();
}
}
} else {
// No dash found at expected indent level, break
break;
}
}
// Calculate end position based on the last item
let end = start;
if (items.length > 0) {
const lastItem = items[items.length - 1];
end = lastItem.end;
} else {
// If no items, end is right after the start
end = createPosition(start.line, start.character + 1);
}
return createArrayNode(items, start, end);
}
parseBlockObject(baseIndent: number, baseCharPosition?: number): YamlObjectNode {
const start = this.lexer.getCurrentPosition();
const properties: { key: YamlStringNode; value: YamlNode }[] = [];
const localKeysSeen = new Set<string>();
// For parsing from current position (inline object parsing)
const fromCurrentPosition = baseCharPosition !== undefined;
let firstIteration = true;
while (!this.lexer.isAtEnd()) {
if (!firstIteration || !fromCurrentPosition) {
this.lexer.moveToNextNonEmptyLine();
}
firstIteration = false;
if (this.lexer.isAtEnd()) {
break;
}
const currentIndent = this.lexer.getIndentation();
if (fromCurrentPosition) {
// For current position parsing, check character position alignment
this.lexer.skipWhitespace();
const currentCharPosition = this.lexer.getCurrentCharNumber();
if (currentCharPosition < baseCharPosition) {
break;
}
} else {
// For normal block parsing, check indentation level
if (currentIndent < baseIndent) {
break;
}
// Check for incorrect indentation
if (currentIndent > baseIndent) {
const lineStart = createPosition(this.lexer.getCurrentLineNumber(), 0);
const lineEnd = createPosition(this.lexer.getCurrentLineNumber(), this.lexer.getCurrentLineText().length);
this.addError('Unexpected indentation', 'indentation', lineStart, lineEnd);
// Try to recover by treating it as a property anyway
this.lexer.skipWhitespace();
} else {
this.lexer.skipWhitespace();
}
}
// Parse key
const keyStart = this.lexer.getCurrentPosition();
let keyValue = '';
while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '' && this.lexer.getCurrentChar() !== ':') {
keyValue += this.lexer.advance();
}
keyValue = keyValue.trim();
const keyEnd = this.lexer.getCurrentPosition();
const key = createStringNode(keyValue, keyStart, keyEnd);
// Check for duplicate keys
if (!this.options.allowDuplicateKeys && localKeysSeen.has(keyValue)) {
this.addError(`Duplicate key '${keyValue}'`, 'duplicateKey', keyStart, keyEnd);
}
localKeysSeen.add(keyValue);
// Expect colon
if (this.lexer.getCurrentChar() === ':') {
this.lexer.advance();
}
this.lexer.skipWhitespace();
// Determine if value is on same line or next line(s)
let value: YamlNode;
const valueStart = this.lexer.getCurrentPosition();
if (this.lexer.getCurrentChar() === '' || this.lexer.getCurrentChar() === '#') {
// Value is on next line(s) or empty
this.lexer.advanceLine();
// Check next line for nested content
if (!this.lexer.isAtEnd()) {
const nextIndent = this.lexer.getIndentation();
if (nextIndent > currentIndent) {
// Nested content - determine if it's an object, array, or just a scalar value
this.lexer.skipWhitespace();
if (this.lexer.getCurrentChar() === '-') {
value = this.parseBlockArray(nextIndent);
} else {
// Check if this looks like an object property (has a colon)
const currentLine = this.lexer.getCurrentLineText();
const currentPos = this.lexer.getCurrentCharNumber();
const remainingLine = currentLine.substring(currentPos);
if (remainingLine.includes(':') && !remainingLine.trim().startsWith('#')) {
// It's a nested object
value = this.parseBlockObject(nextIndent);
} else {
// It's just a scalar value on the next line
value = this.parseValue();
}
}
} else if (!fromCurrentPosition && nextIndent === currentIndent) {
// Same indentation level - check if it's an array item
this.lexer.skipWhitespace();
if (this.lexer.getCurrentChar() === '-') {
value = this.parseBlockArray(currentIndent);
} else {
value = createStringNode('', valueStart, valueStart);
}
} else {
value = createStringNode('', valueStart, valueStart);
}
} else {
value = createStringNode('', valueStart, valueStart);
}
} else {
// Value is on the same line
value = this.parseValue();
// Skip any remaining content on this line (comments, etc.)
while (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() !== '' && this.lexer.getCurrentChar() !== '#') {
if (isWhitespace(this.lexer.getCurrentChar())) {
this.lexer.advance();
} else {
break;
}
}
// Skip to end of line if we hit a comment
if (this.lexer.getCurrentChar() === '#') {
this.lexer.skipToEndOfLine();
}
// Move to next line for next iteration
if (!this.lexer.isAtEnd() && this.lexer.getCurrentChar() === '') {
this.lexer.advanceLine();
}
}
properties.push({ key, value });
}
// Calculate the end position based on the last property
let end = start;
if (properties.length > 0) {
const lastProperty = properties[properties.length - 1];
end = lastProperty.value.end;
}
return createObjectNode(properties, start, end);
}
parse(): YamlNode | undefined {
if (this.lexer.isAtEnd()) {
return undefined;
}
this.lexer.moveToNextNonEmptyLine();
if (this.lexer.isAtEnd()) {
return undefined;
}
// Determine the root structure type
this.lexer.skipWhitespace();
if (this.lexer.getCurrentChar() === '-') {
// Check if this is an array item or a negative number
// Look at the character after the dash
const nextChar = this.lexer.peek();
if (nextChar === ' ' || nextChar === '\t' || nextChar === '' || nextChar === '#') {
// It's an array item (dash followed by whitespace/end/comment)
return this.parseBlockArray(0);
} else {
// It's likely a negative number or other value, treat as single value
return this.parseValue();
}
} else if (this.lexer.getCurrentChar() === '[') {
// Root is an inline array
return this.parseInlineArray();
} else if (this.lexer.getCurrentChar() === '{') {
// Root is an inline object
return this.parseInlineObject();
} else {
// Check if this looks like a key-value pair by looking for a colon
// For single values, there shouldn't be a colon
const currentLine = this.lexer.getCurrentLineText();
const currentPos = this.lexer.getCurrentCharNumber();
const remainingLine = currentLine.substring(currentPos);
// Check if there's a colon that's not inside quotes
let hasColon = false;
let inQuotes = false;
let quoteChar = '';
for (let i = 0; i < remainingLine.length; i++) {
const char = remainingLine[i];
if (!inQuotes && (char === '"' || char === `'`)) {
inQuotes = true;
quoteChar = char;
} else if (inQuotes && char === quoteChar) {
inQuotes = false;
quoteChar = '';
} else if (!inQuotes && char === ':') {
hasColon = true;
break;
} else if (!inQuotes && char === '#') {
// Comment starts, stop looking
break;
}
}
if (hasColon) {
// Root is an object
return this.parseBlockObject(0);
} else {
// Root is a single value
return this.parseValue();
}
}
}
}

View File

@ -153,6 +153,10 @@ export function isPortFree(port: number, timeout: number): Promise<boolean> {
return findFreePortFaster(port, 0, timeout).then(port => port !== 0);
}
interface ServerError {
code?: string;
}
/**
* Uses listen instead of connect. Is faster, but if there is another listener on 0.0.0.0 then this will take 127.0.0.1 from that listener.
*/
@ -180,8 +184,8 @@ export function findFreePortFaster(startPort: number, giveUpAfter: number, timeo
server.on('listening', () => {
doResolve(startPort, resolve);
});
server.on('error', err => {
if (err && ((<any>err).code === 'EADDRINUSE' || (<any>err).code === 'EACCES') && (countTried < giveUpAfter)) {
server.on('error', (err: ServerError) => {
if (err && (err.code === 'EADDRINUSE' || err.code === 'EACCES') && (countTried < giveUpAfter)) {
startPort++;
countTried++;
server.listen(startPort, hostname);

View File

@ -9,6 +9,7 @@ import { sumBy } from '../../../../base/common/arrays';
import { BugIndicatingError } from '../../../../base/common/errors';
import { OffsetRange } from '../ranges/offsetRange';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export abstract class BaseEdit<T extends BaseReplacement<T> = BaseReplacement<any>, TEdit extends BaseEdit<T, TEdit> = BaseEdit<T, any>> {
constructor(
public readonly replacements: readonly T[],

View File

@ -22,7 +22,7 @@ export class LineEdit {
return new LineEdit(data.map(e => LineReplacement.deserialize(e)));
}
public static fromEdit(edit: BaseStringEdit, initialValue: AbstractText): LineEdit {
public static fromStringEdit(edit: BaseStringEdit, initialValue: AbstractText): LineEdit {
const textEdit = TextEdit.fromStringEdit(edit, initialValue);
return LineEdit.fromTextEdit(textEdit, initialValue);
}
@ -408,7 +408,7 @@ export namespace SerializedLineReplacement {
&& typeof thing[0] === 'number'
&& typeof thing[1] === 'number'
&& Array.isArray(thing[2])
&& thing[2].every((e: any) => typeof e === 'string')
&& thing[2].every((e: unknown) => typeof e === 'string')
);
}
}

View File

@ -11,6 +11,7 @@ import { StringText } from '../text/abstractText';
import { BaseEdit, BaseReplacement } from './edit';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export abstract class BaseStringEdit<T extends BaseStringReplacement<T> = BaseStringReplacement<any>, TEdit extends BaseStringEdit<T, TEdit> = BaseStringEdit<any, any>> extends BaseEdit<T, TEdit> {
get TReplacement(): T {
throw new Error('TReplacement is not defined for BaseStringEdit');
@ -22,6 +23,7 @@ export abstract class BaseStringEdit<T extends BaseStringReplacement<T> = BaseSt
}
let result = edits[0];
for (let i = 1; i < edits.length; i++) {
// eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any
result = result.compose(edits[i]) as any;
}
return result;
@ -171,11 +173,11 @@ export abstract class BaseStringEdit<T extends BaseStringReplacement<T> = BaseSt
return e.toEdit();
}
removeCommonSuffixAndPrefix(source: string): TEdit {
public removeCommonSuffixAndPrefix(source: string): TEdit {
return this._createNew(this.replacements.map(e => e.removeCommonSuffixAndPrefix(source))).normalize();
}
applyOnText(docContents: StringText): StringText {
public applyOnText(docContents: StringText): StringText {
return new StringText(this.apply(docContents.value));
}
@ -190,6 +192,7 @@ export abstract class BaseStringEdit<T extends BaseStringReplacement<T> = BaseSt
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export abstract class BaseStringReplacement<T extends BaseStringReplacement<T> = BaseStringReplacement<any>> extends BaseReplacement<T> {
constructor(
range: OffsetRange,
@ -201,7 +204,7 @@ export abstract class BaseStringReplacement<T extends BaseStringReplacement<T> =
getNewLength(): number { return this.newText.length; }
override toString(): string {
return `${this.replaceRange} -> "${this.newText}"`;
return `${this.replaceRange} -> ${JSON.stringify(this.newText)}`;
}
replace(str: string): string {
@ -523,8 +526,14 @@ export class AnnotatedStringEdit<T extends IEditData<T>> extends BaseStringEdit<
return new AnnotatedStringEdit<T>(replacements);
}
toStringEdit(): StringEdit {
return new StringEdit(this.replacements.map(e => new StringReplacement(e.replaceRange, e.newText)));
public toStringEdit(filter?: (replacement: AnnotatedStringReplacement<T>) => boolean): StringEdit {
const newReplacements: StringReplacement[] = [];
for (const r of this.replacements) {
if (!filter || filter(r)) {
newReplacements.push(new StringReplacement(r.replaceRange, r.newText));
}
}
return new StringEdit(newReplacements);
}
}

View File

@ -169,11 +169,11 @@ export class Position {
/**
* Test if `obj` is an `IPosition`.
*/
public static isIPosition(obj: any): obj is IPosition {
public static isIPosition(obj: unknown): obj is IPosition {
return (
obj
&& (typeof obj.lineNumber === 'number')
&& (typeof obj.column === 'number')
!!obj
&& (typeof (obj as IPosition).lineNumber === 'number')
&& (typeof (obj as IPosition).column === 'number')
);
}

View File

@ -392,13 +392,13 @@ export class Range {
/**
* Test if `obj` is an `IRange`.
*/
public static isIRange(obj: any): obj is IRange {
public static isIRange(obj: unknown): obj is IRange {
return (
obj
&& (typeof obj.startLineNumber === 'number')
&& (typeof obj.startColumn === 'number')
&& (typeof obj.endLineNumber === 'number')
&& (typeof obj.endColumn === 'number')
!!obj
&& (typeof (obj as IRange).startLineNumber === 'number')
&& (typeof (obj as IRange).startColumn === 'number')
&& (typeof (obj as IRange).endLineNumber === 'number')
&& (typeof (obj as IRange).endColumn === 'number')
);
}

View File

@ -7,7 +7,7 @@
import { BugIndicatingError } from '../../../../base/common/errors';
import { OffsetRange } from './offsetRange';
import { Range } from '../range';
import { IRange, Range } from '../range';
import { findFirstIdxMonotonousOrArrLen, findLastIdxMonotonous, findLastMonotonous } from '../../../../base/common/arraysFind';
import { Comparator, compareBy, numberComparator } from '../../../../base/common/arrays';
@ -19,11 +19,11 @@ export class LineRange {
return new LineRange(startLineNumber, startLineNumber + length);
}
public static fromRange(range: Range): LineRange {
public static fromRange(range: IRange): LineRange {
return new LineRange(range.startLineNumber, range.endLineNumber);
}
public static fromRangeInclusive(range: Range): LineRange {
public static fromRangeInclusive(range: IRange): LineRange {
return new LineRange(range.startLineNumber, range.endLineNumber + 1);
}

View File

@ -8,11 +8,11 @@
import { assert } from '../../../../base/common/assert';
import { splitLines } from '../../../../base/common/strings';
import { Position } from '../position';
import { PositionOffsetTransformer } from './positionToOffsetImpl';
import { Range } from '../range';
import { LineRange } from '../ranges/lineRange';
import { TextLength } from './textLength';
import { OffsetRange } from '../ranges/offsetRange';
import { TextLength } from './textLength';
import { PositionOffsetTransformer } from './positionToOffsetImpl';
export abstract class AbstractText {
abstract getValueOfRange(range: Range): string;
@ -124,4 +124,9 @@ export class StringText extends AbstractText {
get length(): TextLength {
return this._t.textLength;
}
// Override the getTransformer method to return the cached transformer
override getTransformer() {
return this._t;
}
}

View File

@ -8,10 +8,10 @@
export class SyncDescriptor<T> {
readonly ctor: any;
readonly staticArguments: any[];
readonly staticArguments: unknown[];
readonly supportsDelayedInstantiation: boolean;
constructor(ctor: new (...args: any[]) => T, staticArguments: any[] = [], supportsDelayedInstantiation: boolean = false) {
constructor(ctor: new (...args: any[]) => T, staticArguments: unknown[] = [], supportsDelayedInstantiation: boolean = false) {
this.ctor = ctor;
this.staticArguments = staticArguments;
this.supportsDelayedInstantiation = supportsDelayedInstantiation;

View File

@ -18,9 +18,14 @@ export namespace _util {
export const DI_TARGET = '$di$target';
export const DI_DEPENDENCIES = '$di$dependencies';
export function getServiceDependencies(ctor: any): { id: ServiceIdentifier<any>; index: number }[] {
export function getServiceDependencies(ctor: DI_TARGET_OBJ): { id: ServiceIdentifier<any>; index: number }[] {
return ctor[DI_DEPENDENCIES] || [];
}
export interface DI_TARGET_OBJ extends Function {
[DI_TARGET]: Function;
[DI_DEPENDENCIES]: { id: ServiceIdentifier<any>; index: number }[];
}
}
// --- interfaces ------
@ -91,12 +96,13 @@ export interface ServiceIdentifier<T> {
type: T;
}
function storeServiceDependency(id: Function, target: Function, index: number): void {
if ((target as any)[_util.DI_TARGET] === target) {
(target as any)[_util.DI_DEPENDENCIES].push({ id, index });
function storeServiceDependency(id: ServiceIdentifier<unknown>, target: Function, index: number): void {
if ((target as _util.DI_TARGET_OBJ)[_util.DI_TARGET] === target) {
(target as _util.DI_TARGET_OBJ)[_util.DI_DEPENDENCIES].push({ id, index });
} else {
(target as any)[_util.DI_DEPENDENCIES] = [{ id, index }];
(target as any)[_util.DI_TARGET] = target;
(target as _util.DI_TARGET_OBJ)[_util.DI_DEPENDENCIES] = [{ id, index }];
(target as _util.DI_TARGET_OBJ)[_util.DI_TARGET] = target;
}
}
@ -109,12 +115,12 @@ export function createDecorator<T>(serviceId: string): ServiceIdentifier<T> {
return _util.serviceIds.get(serviceId)!;
}
const id = <any>function (target: Function, key: string, index: number) {
const id = function (target: Function, key: string, index: number) {
if (arguments.length !== 3) {
throw new Error('@IServiceName-decorator can only be used to decorate a parameter');
}
storeServiceDependency(id, target, index);
};
} as ServiceIdentifier<T>;
id.toString = () => serviceId;

View File

@ -124,7 +124,7 @@ export class InstantiationService implements IInstantiationService {
createInstance<T>(descriptor: SyncDescriptor0<T>): T;
createInstance<Ctor extends new (...args: any[]) => unknown, R extends InstanceType<Ctor>>(ctor: Ctor, ...args: GetLeadingNonServiceArgs<ConstructorParameters<Ctor>>): R;
createInstance(ctorOrDescriptor: any | SyncDescriptor<any>, ...rest: any[]): unknown {
createInstance(ctorOrDescriptor: any | SyncDescriptor<any>, ...rest: unknown[]): unknown {
this._throwIfDisposed();
let _trace: Trace;
@ -140,11 +140,11 @@ export class InstantiationService implements IInstantiationService {
return result;
}
private _createInstance<T>(ctor: any, args: any[] = [], _trace: Trace): T {
private _createInstance<T>(ctor: any, args: unknown[] = [], _trace: Trace): T {
// arguments defined by service decorators
const serviceDependencies = _util.getServiceDependencies(ctor).sort((a, b) => a.index - b.index);
const serviceArgs: any[] = [];
const serviceArgs: unknown[] = [];
for (const dependency of serviceDependencies) {
const service = this._getOrCreateServiceInstance(dependency.id, _trace);
if (!service) {
@ -288,7 +288,7 @@ export class InstantiationService implements IInstantiationService {
return <T>this._getServiceInstanceOrDescriptor(id);
}
private _createServiceInstanceWithOwner<T>(id: ServiceIdentifier<T>, ctor: any, args: any[] = [], supportsDelayedInstantiation: boolean, _trace: Trace): T {
private _createServiceInstanceWithOwner<T>(id: ServiceIdentifier<T>, ctor: any, args: unknown[] = [], supportsDelayedInstantiation: boolean, _trace: Trace): T {
if (this._services.get(id) instanceof SyncDescriptor) {
return this._createServiceInstance(id, ctor, args, supportsDelayedInstantiation, _trace, this._servicesToMaybeDispose);
} else if (this._parent) {
@ -298,7 +298,7 @@ export class InstantiationService implements IInstantiationService {
}
}
private _createServiceInstance<T>(id: ServiceIdentifier<T>, ctor: any, args: any[] = [], supportsDelayedInstantiation: boolean, _trace: Trace, disposeBucket: Set<any>): T {
private _createServiceInstance<T>(id: ServiceIdentifier<T>, ctor: any, args: unknown[] = [], supportsDelayedInstantiation: boolean, _trace: Trace, disposeBucket: Set<any>): T {
if (!supportsDelayedInstantiation) {
// eager instantiation
const result = this._createInstance<T>(ctor, args, _trace);
@ -327,6 +327,7 @@ export class InstantiationService implements IInstantiationService {
// early listeners that we kept are now being subscribed to
// the real service
for (const [key, values] of earlyListeners) {
// eslint-disable-next-line local/code-no-any-casts
const candidate = <Event<any>>(<any>result)[key];
if (typeof candidate === 'function') {
for (const value of values) {