Skip to content

Commit 44cc351

Browse files
authored
debt - enforce lazy and safe Intl usage (microsoft#248063)
1 parent adf14a0 commit 44cc351

File tree

16 files changed

+79
-57
lines changed

16 files changed

+79
-57
lines changed

eslint.config.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,10 @@ export default tseslint.config(
434434
'local/code-no-global-document-listener': 'warn',
435435
'no-restricted-syntax': [
436436
'warn',
437+
{
438+
'selector': `NewExpression[callee.object.name='Intl']`,
439+
'message': 'Use safeIntl helper instead for safe and lazy use of potentially expensive Intl methods.'
440+
},
437441
{
438442
'selector': `BinaryExpression[operator='instanceof'][right.name='MouseEvent']`,
439443
'message': 'Use DOM.isMouseEvent() to support multi-window scenarios.'

src/vs/base/common/comparers.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import { safeIntl } from './date.js';
67
import { Lazy } from './lazy.js';
78
import { sep } from './path.js';
89

@@ -12,7 +13,7 @@ import { sep } from './path.js';
1213

1314
// A collator with numeric sorting enabled, and no sensitivity to case, accents or diacritics.
1415
const intlFileNameCollatorBaseNumeric: Lazy<{ collator: Intl.Collator; collatorIsNumeric: boolean }> = new Lazy(() => {
15-
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });
16+
const collator = safeIntl.Collator(undefined, { numeric: true, sensitivity: 'base' }).value;
1617
return {
1718
collator,
1819
collatorIsNumeric: collator.resolvedOptions().numeric
@@ -21,15 +22,15 @@ const intlFileNameCollatorBaseNumeric: Lazy<{ collator: Intl.Collator; collatorI
2122

2223
// A collator with numeric sorting enabled.
2324
const intlFileNameCollatorNumeric: Lazy<{ collator: Intl.Collator }> = new Lazy(() => {
24-
const collator = new Intl.Collator(undefined, { numeric: true });
25+
const collator = safeIntl.Collator(undefined, { numeric: true }).value;
2526
return {
2627
collator
2728
};
2829
});
2930

3031
// A collator with numeric sorting enabled, and sensitivity to accents and diacritics but not case.
3132
const intlFileNameCollatorNumericCaseInsensitive: Lazy<{ collator: Intl.Collator }> = new Lazy(() => {
32-
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'accent' });
33+
const collator = safeIntl.Collator(undefined, { numeric: true, sensitivity: 'accent' }).value;
3334
return {
3435
collator
3536
};

src/vs/base/common/date.ts

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { localize } from '../../nls.js';
7+
import { Lazy } from './lazy.js';
78
import { LANGUAGE_DEFAULT } from './platform.js';
89

910
const minute = 60;
@@ -264,32 +265,49 @@ export function toLocalISOString(date: Date): string {
264265
}
265266

266267
export const safeIntl = {
267-
DateTimeFormat(locales?: Intl.LocalesArgument, options?: Intl.DateTimeFormatOptions): Intl.DateTimeFormat {
268-
try {
269-
return new Intl.DateTimeFormat(locales, options);
270-
} catch {
271-
return new Intl.DateTimeFormat(undefined, options);
272-
}
268+
DateTimeFormat(locales?: Intl.LocalesArgument, options?: Intl.DateTimeFormatOptions): Lazy<Intl.DateTimeFormat> {
269+
return new Lazy(() => {
270+
try {
271+
return new Intl.DateTimeFormat(locales, options);
272+
} catch {
273+
return new Intl.DateTimeFormat(undefined, options);
274+
}
275+
});
273276
},
274-
Collator(locales?: Intl.LocalesArgument, options?: Intl.CollatorOptions): Intl.Collator {
275-
try {
276-
return new Intl.Collator(locales, options);
277-
} catch {
278-
return new Intl.Collator(undefined, options);
279-
}
277+
Collator(locales?: Intl.LocalesArgument, options?: Intl.CollatorOptions): Lazy<Intl.Collator> {
278+
return new Lazy(() => {
279+
try {
280+
return new Intl.Collator(locales, options);
281+
} catch {
282+
return new Intl.Collator(undefined, options);
283+
}
284+
});
280285
},
281-
Segmenter(locales?: Intl.LocalesArgument, options?: Intl.SegmenterOptions): Intl.Segmenter {
282-
try {
283-
return new Intl.Segmenter(locales, options);
284-
} catch {
285-
return new Intl.Segmenter(undefined, options);
286-
}
286+
Segmenter(locales?: Intl.LocalesArgument, options?: Intl.SegmenterOptions): Lazy<Intl.Segmenter> {
287+
return new Lazy(() => {
288+
try {
289+
return new Intl.Segmenter(locales, options);
290+
} catch {
291+
return new Intl.Segmenter(undefined, options);
292+
}
293+
});
287294
},
288-
Locale(tag: Intl.Locale | string, options?: Intl.LocaleOptions): Intl.Locale {
289-
try {
290-
return new Intl.Locale(tag, options);
291-
} catch {
292-
return new Intl.Locale(LANGUAGE_DEFAULT, options);
293-
}
295+
Locale(tag: Intl.Locale | string, options?: Intl.LocaleOptions): Lazy<Intl.Locale> {
296+
return new Lazy(() => {
297+
try {
298+
return new Intl.Locale(tag, options);
299+
} catch {
300+
return new Intl.Locale(LANGUAGE_DEFAULT, options);
301+
}
302+
});
303+
},
304+
NumberFormat(locales?: Intl.LocalesArgument, options?: Intl.NumberFormatOptions): Lazy<Intl.NumberFormat> {
305+
return new Lazy(() => {
306+
try {
307+
return new Intl.NumberFormat(locales, options);
308+
} catch {
309+
return new Intl.NumberFormat(undefined, options);
310+
}
311+
});
294312
}
295313
};

src/vs/base/test/common/date.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,13 @@ suite('Date', () => {
7474

7575
suite('safeIntl', () => {
7676
test('Collator fallback', () => {
77-
const collator = safeIntl.Collator('en_IT');
77+
const collator = safeIntl.Collator('en_IT').value;
7878
const comparison = collator.compare('a', 'b');
7979
strictEqual(comparison, -1);
8080
});
8181

8282
test('Locale fallback', () => {
83-
const locale = safeIntl.Locale('en_IT');
83+
const locale = safeIntl.Locale('en_IT').value;
8484
strictEqual(locale.baseName, LANGUAGE_DEFAULT);
8585
});
8686
});

src/vs/editor/browser/gpu/contentSegmenter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class GraphemeContentSegmenter implements IContentSegmenter {
5151

5252
constructor(lineData: ViewLineRenderingData) {
5353
const content = lineData.content;
54-
const segmenter = safeIntl.Segmenter(undefined, { granularity: 'grapheme' });
54+
const segmenter = safeIntl.Segmenter(undefined, { granularity: 'grapheme' }).value;
5555
const segmentedContent = Array.from(segmenter.segment(content));
5656
let segmenterIndex = 0;
5757

src/vs/editor/common/core/wordCharacterClassifier.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import { CharCode } from '../../../base/common/charCode.js';
77
import { safeIntl } from '../../../base/common/date.js';
8+
import { Lazy } from '../../../base/common/lazy.js';
89
import { LRUCache } from '../../../base/common/map.js';
910
import { CharacterClassifier } from './characterClassifier.js';
1011

@@ -17,7 +18,7 @@ export const enum WordCharacterClass {
1718
export class WordCharacterClassifier extends CharacterClassifier<WordCharacterClass> {
1819

1920
public readonly intlSegmenterLocales: Intl.UnicodeBCP47LocaleIdentifier[];
20-
private readonly _segmenter: Intl.Segmenter | null = null;
21+
private readonly _segmenter: Lazy<Intl.Segmenter> | null = null;
2122
private _cachedLine: string | null = null;
2223
private _cachedSegments: IntlWordSegmentData[] = [];
2324

@@ -71,7 +72,7 @@ export class WordCharacterClassifier extends CharacterClassifier<WordCharacterCl
7172

7273
// Update the cache with the new line
7374
this._cachedLine = line;
74-
this._cachedSegments = this._filterWordSegments(this._segmenter.segment(line));
75+
this._cachedSegments = this._filterWordSegments(this._segmenter.value.segment(line));
7576

7677
return this._cachedSegments;
7778
}

src/vs/editor/contrib/linesOperations/browser/sortLinesCommand.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import { safeIntl } from '../../../../base/common/date.js';
7+
import { Lazy } from '../../../../base/common/lazy.js';
68
import { EditOperation, ISingleEditOperation } from '../../../common/core/editOperation.js';
79
import { Range } from '../../../common/core/range.js';
810
import { Selection } from '../../../common/core/selection.js';
@@ -11,13 +13,7 @@ import { ITextModel } from '../../../common/model.js';
1113

1214
export class SortLinesCommand implements ICommand {
1315

14-
private static _COLLATOR: Intl.Collator | null = null;
15-
public static getCollator(): Intl.Collator {
16-
if (!SortLinesCommand._COLLATOR) {
17-
SortLinesCommand._COLLATOR = new Intl.Collator();
18-
}
19-
return SortLinesCommand._COLLATOR;
20-
}
16+
static _COLLATOR: Lazy<Intl.Collator> = safeIntl.Collator();
2117

2218
private readonly selection: Selection;
2319
private readonly descending: boolean;
@@ -84,7 +80,7 @@ function getSortData(model: ITextModel, selection: Selection, descending: boolea
8480
}
8581

8682
let sorted = linesToSort.slice(0);
87-
sorted.sort(SortLinesCommand.getCollator().compare);
83+
sorted.sort(SortLinesCommand._COLLATOR.value.compare);
8884

8985
// If descending, reverse the order.
9086
if (descending === true) {

src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { IQuickInputService, IQuickPickItem } from '../../../../platform/quickin
3333
import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js';
3434
import { IWorkspaceTrustManagementService } from '../../../../platform/workspace/common/workspaceTrust.js';
3535
import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js';
36+
import { safeIntl } from '../../../../base/common/date.js';
3637

3738
export const warningIcon = registerIcon('extensions-warning-message', Codicon.warning, nls.localize('warningIcon', 'Icon shown with a warning message in the extensions editor.'));
3839

@@ -164,7 +165,7 @@ export class UnicodeHighlighter extends Disposable implements IEditorContributio
164165
allowedCodePoints: Object.keys(options.allowedCharacters).map(c => c.codePointAt(0)!),
165166
allowedLocales: Object.keys(options.allowedLocales).map(locale => {
166167
if (locale === '_os') {
167-
const osLocale = new Intl.NumberFormat().resolvedOptions().locale;
168+
const osLocale = safeIntl.NumberFormat().value.resolvedOptions().locale;
168169
return osLocale;
169170
} else if (locale === '_vscode') {
170171
return platform.language;

src/vs/workbench/browser/parts/titlebar/titlebarPart.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart {
506506
// Check if the locale is RTL, macOS will move traffic lights in RTL locales
507507
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/textInfo
508508

509-
const localeInfo = safeIntl.Locale(platformLocale) as any;
509+
const localeInfo = safeIntl.Locale(platformLocale).value as any;
510510
if (localeInfo?.textInfo?.direction === 'rtl') {
511511
primaryWindowControlsLocation = 'right';
512512
}

src/vs/workbench/contrib/chat/browser/actions/chatActions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,7 @@ export function registerChatActions() {
685685
if (chatEntitlementService.quotas.resetDate) {
686686
const dateFormatter = safeIntl.DateTimeFormat(language, { year: 'numeric', month: 'long', day: 'numeric' });
687687
const quotaResetDate = new Date(chatEntitlementService.quotas.resetDate);
688-
message = [message, localize('quotaResetDate', "The allowance will reset on {0}.", dateFormatter.format(quotaResetDate))].join(' ');
688+
message = [message, localize('quotaResetDate', "The allowance will reset on {0}.", dateFormatter.value.format(quotaResetDate))].join(' ');
689689
}
690690

691691
const limited = chatEntitlementService.entitlement === ChatEntitlement.Limited;

0 commit comments

Comments
 (0)