Skip to content

Commit a933b37

Browse files
committed
l10n error handling and unit tests
1 parent b98724e commit a933b37

File tree

9 files changed

+210
-49
lines changed

9 files changed

+210
-49
lines changed

NOTICE

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
Oracle Java Platform Extension for Visual Studio Code
2+
Copyright (c) 2023-2025, Oracle and/or its affiliates.
3+
14
This product includes software developed at
25
The Apache Software Foundation (http://www.apache.org/).
36

4-
Oracle Java Platform Extension for Visual Studio Code
5-
Copyright (c) 2023-2024, Oracle and/or its affiliates.
6-
77
Apache NetBeans
88
Copyright 2017-2024 The Apache Software Foundation
99

@@ -14,3 +14,9 @@ The code was Copyright 1997-2016 Oracle and/or its affiliates. The Initial
1414
Developer of the Original Software was Sun Microsystems, Inc. Portions
1515
Copyright 1997-2006 Sun Microsystems, Inc.
1616

17+
---
18+
19+
vscode/src/localiser.ts
20+
This file includes portions derived from the Localization tooling for Visual Studio Code
21+
@vscode/l10n software developed at Microsoft Corporation licensed under the MIT License.
22+
Copyright (c) Microsoft Corporation

THIRD_PARTY_LICENSES.txt

Lines changed: 38 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9448,42 +9448,6 @@ SOFTWARE.
94489448

94499449
------------------ END OF DEPENDENCY LICENSE --------------------
94509450

9451-
Dependency: VS Code tooling for localizing Visual Studio Code extensions
9452-
========================================================================
9453-
9454-
------------------ START OF DEPENDENCY LICENCE --------------------
9455-
- @vscode/l10n
9456-
Accessible at: vscode/src/localiser.ts
9457-
Modified from source at: https://github.com/microsoft/vscode-l10n/blob/57b5918f3b247a03387432037669e8ae5aff886b/l10n/src/main.ts#L222
9458-
License: MIT License
9459-
9460-
Copyright (c) Microsoft Corporation
9461-
9462-
All rights reserved.
9463-
9464-
MIT License
9465-
9466-
Permission is hereby granted, free of charge, to any person obtaining a copy
9467-
of this software and associated documentation files (the "Software"), to deal
9468-
in the Software without restriction, including without limitation the rights
9469-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9470-
copies of the Software, and to permit persons to whom the Software is
9471-
furnished to do so, subject to the following conditions:
9472-
9473-
The above copyright notice and this permission notice shall be included in all
9474-
copies or substantial portions of the Software.
9475-
9476-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
9477-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
9478-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
9479-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
9480-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9481-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
9482-
SOFTWARE
9483-
9484-
------------------ END OF DEPENDENCY LICENCE --------------------
9485-
9486-
94879451
Dependency: VS Code Debug Protocol and Debug Adapter
94889452
====================================================
94899453

@@ -9880,3 +9844,41 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
98809844
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
98819845

98829846
------------------ END OF DEPENDENCY LICENSE --------------------
9847+
9848+
vscode/src/localiser.ts
9849+
=======================
9850+
Accessible at: vscode/src/localiser.ts
9851+
9852+
- @vscode/l10n
9853+
This file includes portions derived from the Localization tooling for Visual Studio Code
9854+
@vscode/l10n software developed at Microsoft Corporation licensed under the MIT License.
9855+
9856+
Derived from source at: https://github.com/microsoft/vscode-l10n/blob/57b5918f3b247a03387432037669e8ae5aff886b/l10n/src/main.ts#L222
9857+
License: MIT License
9858+
9859+
------------------ START OF DEPENDENCY LICENSE --------------------
9860+
MIT License
9861+
9862+
Copyright (c) Microsoft Corporation.
9863+
9864+
Permission is hereby granted, free of charge, to any person obtaining a copy
9865+
of this software and associated documentation files (the "Software"), to deal
9866+
in the Software without restriction, including without limitation the rights
9867+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9868+
copies of the Software, and to permit persons to whom the Software is
9869+
furnished to do so, subject to the following conditions:
9870+
9871+
The above copyright notice and this permission notice shall be included in all
9872+
copies or substantial portions of the Software.
9873+
9874+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
9875+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
9876+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
9877+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
9878+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9879+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
9880+
SOFTWARE
9881+
9882+
------------------ END OF DEPENDENCY LICENSE --------------------
9883+
9884+

vscode/src/localiser.ts

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import * as vscode from 'vscode';
2020
import * as fs from 'fs';
2121
import { extConstants } from './constants';
2222
import { FileUtils } from './utils';
23+
import { LOGGER } from './logger';
2324

2425
const DEFAULT_LANGAUGE = "en";
2526
const DEFAULT_BUNDLE_FILE = `l10n/bundle.l10n.${DEFAULT_LANGAUGE}.json`;
@@ -32,10 +33,36 @@ export interface l10n {
3233

3334

3435
class l10Wrapper implements l10n {
35-
private defaultl10nContent:any;
36+
private defaultl10nMap:any;
3637
constructor(extensionId: string, defaultBundlePath: string) {
37-
let defaultBundleAbsoluteFsPath = FileUtils.toUri(`${vscode.extensions.getExtension(extensionId)?.extensionPath}/${defaultBundlePath}`).fsPath;
38-
this.defaultl10nContent = JSON.parse(fs.readFileSync(defaultBundleAbsoluteFsPath,'utf-8'));
38+
let extnPath = vscode.extensions.getExtension(extensionId)?.extensionPath;
39+
let defaultBundleAbsoluteFsPath = FileUtils.toUri(`${extnPath}/${defaultBundlePath}`).fsPath;
40+
let logAndThrowError = (message:string,err:unknown) => {
41+
let errMsg = "";
42+
if(err instanceof Error){
43+
errMsg = err?.message
44+
}
45+
LOGGER.error(message+errMsg);
46+
47+
throw err;
48+
}
49+
let fileContent:string = "";
50+
try {
51+
fileContent = fs.readFileSync(defaultBundleAbsoluteFsPath,'utf-8');
52+
} catch (err) {
53+
logAndThrowError("error occured while reading bundle file : ",err);
54+
}
55+
try {
56+
this.defaultl10nMap = JSON.parse(fileContent);
57+
}catch (err) {
58+
let msg = "";
59+
if(err instanceof Error){
60+
msg = err?.message
61+
}
62+
logAndThrowError("error occured while parsing bundle file : ",err);
63+
throw err;
64+
}
65+
3966
}
4067

4168
value(key: string, placeholderMap: Record<string, any>): string {
@@ -44,7 +71,7 @@ class l10Wrapper implements l10n {
4471
return isPresentInBundle ? valueFromBundle : this.defaultTranslation(key, placeholderMap);
4572
}
4673
defaultTranslation(key: string, placeholderMap:Record<string, any>):string{
47-
let value = this.defaultl10nContent[key];
74+
let value = this.defaultl10nMap[key];
4875
return value?this.format(value,placeholderMap):key;
4976
}
5077
nbLocaleCode(){
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
Copyright (c) 2025, Oracle and/or its affiliates.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
import { expect } from 'chai';
17+
import { describe, it, beforeEach, afterEach } from "mocha";
18+
import * as sinon from 'sinon';
19+
import * as vscode from 'vscode';
20+
import {Extension} from 'vscode';
21+
import { LOGGER } from '../../../logger';
22+
import { mock,instance, when, reset } from "ts-mockito";
23+
var l10n:any = null;
24+
import * as path from 'path';
25+
import assert = require('assert');
26+
27+
describe('localiser tests', () => {
28+
let loggerLogStub: sinon.SinonStub;
29+
let mockedExtns : typeof vscode.extensions;
30+
let extMock : Extension<any>;
31+
let mockedEnv : typeof vscode.env;
32+
let mockedL10n : typeof vscode.l10n;
33+
let currentDir = __dirname;
34+
35+
before(() => {
36+
let vscodeObj = (vscode as typeof vscode & { mockedExtns: typeof vscode.extensions ,mockedEnv: typeof vscode.env,mockedL10n: typeof vscode.l10n });
37+
mockedExtns = vscodeObj.mockedExtns;
38+
mockedEnv = vscodeObj.mockedEnv;
39+
mockedL10n = vscodeObj.mockedL10n;
40+
extMock = mock<Extension<any>>();
41+
loggerLogStub = sinon.stub(LOGGER,"error");
42+
43+
44+
});
45+
beforeEach(()=>{
46+
sinon.reset();
47+
reset(mockedExtns);
48+
reset(extMock);
49+
reset(mockedEnv);
50+
reset(mockedL10n);
51+
})
52+
53+
after(() => {
54+
sinon.reset();
55+
reset(mockedExtns);
56+
reset(extMock);
57+
reset(mockedEnv);
58+
reset(mockedL10n);
59+
});
60+
61+
describe('l10n tests', () => {
62+
describe('issue while reading bundle', () => {
63+
it('file not found error', () => {
64+
let msg:string|null =null;
65+
when(extMock?.extensionPath).thenReturn(path.join(currentDir, 'doesnt-exist'));
66+
var mkInst = instance(extMock);
67+
when(mockedExtns.getExtension("oracle.oracle-java")).thenReturn(mkInst);
68+
try{
69+
l10n = require('../../../localiser');
70+
}catch(e){
71+
msg = (e as any & {message:string}).message
72+
}
73+
assert.strictEqual(msg!!.includes("no such file or directory"),true);
74+
expect(loggerLogStub.called).to.be.true;
75+
});
76+
it('file parsing error', () => {
77+
let msg:string|null =null;
78+
when(extMock?.extensionPath).thenReturn(path.join(currentDir, 'resources','corrupt'));
79+
var mkInst = instance(extMock);
80+
when(mockedExtns.getExtension("oracle.oracle-java")).thenReturn(mkInst);
81+
try{
82+
l10n = require('../../../localiser');
83+
}catch(e){
84+
msg = (e as any & {message:string}).message
85+
}
86+
assert.strictEqual(msg!!.includes("Bad control character in string literal in JSON"),true);
87+
expect(loggerLogStub.called).to.be.true;
88+
});
89+
90+
});
91+
describe('l10n initialisation tests', () => {
92+
it('l10n initialized', () => {
93+
when(extMock?.extensionPath).thenReturn(path.join(currentDir, 'resources'));
94+
var mkInst = instance(extMock);
95+
when(mockedExtns.getExtension("oracle.oracle-java")).thenReturn(mkInst);
96+
l10n = require('../../../localiser');
97+
expect(loggerLogStub.called).to.be.false;
98+
});
99+
it('get nbLocaleCode and get value', () => {
100+
when(extMock?.extensionPath).thenReturn(path.join(currentDir, 'resources'));
101+
var mkExtInst = instance(extMock);
102+
when(mockedEnv.language).thenReturn("en");
103+
when(mockedExtns.getExtension("oracle.oracle-java")).thenReturn(mkExtInst);
104+
when(mockedL10n.bundle).thenReturn(undefined);
105+
let l10n = require('../../../localiser');
106+
let l10nObj = l10n.l10n as { nbLocaleCode(): string , value(key: string, placeholderMap?: Record<string, any>): string};
107+
assert.strictEqual(l10nObj.nbLocaleCode(),"en");
108+
assert.strictEqual(l10nObj.value("label1"),"label1 description");
109+
assert.strictEqual(l10nObj.value("label2",{"placeholder1":"sample data"}),"lable2 sample data description");
110+
expect(loggerLogStub.called).to.be.false;
111+
});
112+
});
113+
});
114+
});
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"label1":"label1 description",
3+
"label2":"lable2
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"label1":"label1 description",
3+
"label2":"lable2 {placeholder1} description"
4+
}

vscode/src/test/unit/mocks/init.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const replaceImportsWithMocks = (mocks: any) => {
3737
Module._load = function (request: any, _parent: any) {
3838
if (request === 'vscode') {
3939
return mocks.vscode;
40-
} else if (request.includes('localiser')) {
40+
} else if (request.includes('localiser') && !_parent?.filename?.includes("localetest.unit.test.ts") ) {
4141
return mocks.localiser;
4242
}
4343

vscode/src/test/unit/mocks/vscode/mockVscode.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ import { mockWindowNamespace } from './namespaces/window';
2020
import { mockEnvNamespace } from './namespaces/env';
2121
import { mockedEnums } from './vscodeHostedTypes';
2222
import { NotebookCellOutputItem } from './notebookCellOutputItem';
23-
23+
import { mock,instance } from "ts-mockito";
2424
type VSCode = typeof vscode;
25-
const mockedVSCode: Partial<VSCode> = {};
25+
const mockedVSCode: Partial<VSCode> & { mockedExtns?:typeof vscode.extensions,mockedL10n?:typeof vscode.l10n} = {};
2626

2727
const mockedVscodeClassesAndTypes = () => {
2828
mockedVSCode.Uri = URI as any;
@@ -33,12 +33,16 @@ const mockedVscodeClassesAndTypes = () => {
3333
const mockNamespaces = () => {
3434
mockWindowNamespace(mockedVSCode);
3535
mockEnvNamespace(mockedVSCode);
36+
mockedVSCode.mockedExtns = mock<typeof vscode.extensions>();
37+
mockedVSCode.mockedL10n = mock<typeof vscode.l10n>();
38+
mockedVSCode.extensions = instance(mockedVSCode.mockedExtns);
39+
mockedVSCode.l10n = instance(mockedVSCode.mockedL10n);
3640
}
3741

3842
export const initMockedVSCode = () => {
3943
mockedVscodeClassesAndTypes();
4044
mockNamespaces();
41-
45+
4246
return mockedVSCode;
4347
}
4448

vscode/src/test/unit/mocks/vscode/namespaces/env.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ import * as vscode from 'vscode';
1818
import { mock, when, anyString, anyOfClass, anything, instance } from "ts-mockito";
1919

2020
type VSCode = typeof vscode;
21-
2221
let mockedEnv: typeof vscode.env;
23-
export const mockEnvNamespace = (mockedVSCode: Partial<VSCode>) => {
22+
export const mockEnvNamespace = (mockedVSCode: Partial<VSCode> & { mockedEnv?:typeof vscode.env}) => {
2423
mockedEnv = mock<typeof vscode.env>();
24+
mockedVSCode.mockedEnv=mockedEnv;
2525
mockedVSCode.env = instance(mockedEnv);
2626
mockTelemetryFields();
2727
}

0 commit comments

Comments
 (0)