Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC] TS migration patterns #1379

Closed
wants to merge 22 commits into from
Closed
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
0fd4a33
LPS-X Enables TypeScript in frontend-js-web module
jbalsas Aug 18, 2021
c603114
LPS-X Adds an initial global Liferay interface
jbalsas Aug 19, 2021
70b78e0
LPS-X Converts autosize and html_util to TS
jbalsas Aug 19, 2021
7a98e2d
LPS-X Converts some trivial modules to TS
jbalsas Aug 19, 2021
0d69c50
LPS-X Converts utility navigate to TS
jbalsas Aug 19, 2021
2c1f090
LPS-X Converts another batch of semi-trivial conversions to TS
jbalsas Aug 19, 2021
2c3f8f8
LPS-X Converts create_X_url utilities to TS
jbalsas Aug 20, 2021
4b22739
LPS-X Refactors logic to help TS infer the proper type for `portletID`
jbalsas Aug 23, 2021
be8ced0
LPS-X Replaces {[key: string]: string} with Record<string, string>
jbalsas Aug 24, 2021
fb25f07
LPS-X Ports fetch utility to TS
jbalsas Aug 24, 2021
6ea38eb
LPS-X Migrates align utility to TS
jbalsas Aug 24, 2021
eda40b9
LPS-X Removes CompatibilityEventProxy
jbalsas Aug 25, 2021
0a7714e
LPS-X Updates form utilities to TS
jbalsas Aug 25, 2021
5f872ac
LPS-X Converts Address utilities to TS
jbalsas Aug 25, 2021
17e37e7
LPS-X Provides a global env.d.ts file for ambient types
jbalsas Sep 7, 2021
23ce0ab
LPS-X Converts EventEmitter utilities to TS
jbalsas Oct 15, 2021
b4136f9
LPS-X Converts run_scripts_in_element utility to TS
jbalsas Oct 15, 2021
be97819
LPS-X Modularizes Liferay global interface for better readability
jbalsas Oct 15, 2021
29a4640
LPS-X Converts module frontend-js-spa-web to TS
jbalsas Oct 15, 2021
c2201e3
LPS-X Converts toggle_disabled utility to TS
jbalsas Oct 15, 2021
bd30aa4
LPS-X Converts focus_form_field, in_browser_view, get_element and get…
jbalsas Oct 18, 2021
092dd31
LPS-X WIP convert module frontend-js-spa-web to TS
jbalsas Oct 20, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
LPS-X Converts create_X_url utilities to TS
This conversion is pretty straightforward but showcases some interesting
things to consider:

1. Mocking the Liferay global object

Since we declare `Liferay` as a global var, mocking it is not straightforward.

  - Partial mocks are flagged as TS as incompatible
  - Reassignments in setup or teardown methods won't get picked up by TS
  forcing us to cast to `Jest.Mock` and/or other types

For the sake of progress I simply resorted to "as any" here, but maybe we
should consider a better approach. Some possible options include:
  - Simply export the type and locally cast usages of the global API
  instead of defining a global var
  - Further split the definition so we can use Partial<ILiferay> and
  Partial<IThemeDisplay> to construct partial mocks

2. String-indexed objects

We do a lot of willy-nilly object string-index access in our codebase. Maybe
as we progress our types will become more explicit, but for now it seems
like we want the type `{ [key: string]: string }` more often than not.
  - Does this type have a common name?
  - Should we export this type for general consumption or define it locally?

3. To TS or not to TS

This stems a bit from the previous point and connects with prior commits
where we addressed the fact that we keep runtime type guards in our code.

In this case, we consume `getPortletNamespace(portletID: string): string`,
but do so trying to pass `portletID: string | null`.

This hints that we might have cases of `createPortletURL` throwing exceptions
in runtime through the `getPortletNamespace` utility. Again, in the spirit of
progress, I just added a non-null assertion here `portletID!` which we know
is obviously wrong.

Updating the definition to `getPortletNamespace(portledID: string | null)`
seems to be technically correct but pushes the problem down the system. We
likely don't want a system where `null`s are being passed around unknowingly.

I'm not sure what the best approach is here, so just writing this down here
to see how this goes, specially as we move onto other modules.
jbalsas committed Nov 18, 2021
commit 2c3f8f81a4885545e9ce808fa6d3f2d7d6524d16
Original file line number Diff line number Diff line change
@@ -90,21 +90,10 @@ export {default as openSimpleInputModal} from './liferay/modal/commands/OpenSimp

// PortletURL API

// @ts-ignore

export {default as createActionURL} from './liferay/util/portlet_url/create_action_url.es';

// @ts-ignore

export {default as createPortletURL} from './liferay/util/portlet_url/create_portlet_url.es';

// @ts-ignore

export {default as createRenderURL} from './liferay/util/portlet_url/create_render_url.es';

// @ts-ignore

export {default as createResourceURL} from './liferay/util/portlet_url/create_resource_url.es';
export {default as createActionURL} from './liferay/util/portlet_url/create_action_url';
export {default as createPortletURL} from './liferay/util/portlet_url/create_portlet_url';
export {default as createRenderURL} from './liferay/util/portlet_url/create_render_url';
export {default as createResourceURL} from './liferay/util/portlet_url/create_resource_url';

// Align API

Original file line number Diff line number Diff line change
@@ -68,10 +68,10 @@ import navigate from './util/navigate';
import normalizeFriendlyURL from './util/normalize_friendly_url';
import ns from './util/ns.es';
import objectToURLSearchParams from './util/object_to_url_search_params.es';
import createActionURL from './util/portlet_url/create_action_url.es';
import createPortletURL from './util/portlet_url/create_portlet_url.es';
import createRenderURL from './util/portlet_url/create_render_url.es';
import createResourceURL from './util/portlet_url/create_resource_url.es';
import createActionURL from './util/portlet_url/create_action_url';
import createPortletURL from './util/portlet_url/create_portlet_url';
import createRenderURL from './util/portlet_url/create_render_url';
import createResourceURL from './util/portlet_url/create_resource_url';
import {getSessionValue, setSessionValue} from './util/session.es';
import toCharCode from './util/to_char_code.es';
import toggleDisabled from './util/toggle_disabled';
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@
import fetch from '../util/fetch.es';
import objectToFormData from '../util/form/object_to_form_data.es';
import getPortletId from '../util/get_portlet_id';
import createPortletURL from '../util/portlet_url/create_portlet_url.es';
import createPortletURL from '../util/portlet_url/create_portlet_url';
import register from './register.es';

/**
@@ -43,8 +43,7 @@ export function minimizePortlet(portletSelector, trigger, options) {
content.classList.remove('d-none');

portlet.classList.remove('portlet-minimized');
}
else {
} else {
content.classList.add('d-none');

portlet.classList.add('portlet-minimized');
@@ -72,8 +71,7 @@ export function minimizePortlet(portletSelector, trigger, options) {
if (minimized) {
icon.classList.add('icon-minus');
icon.classList.remove('icon-resize-vertical');
}
else {
} else {
icon.classList.add('icon-resize-vertical');
icon.classList.remove('icon-minus');
}
Original file line number Diff line number Diff line change
@@ -12,16 +12,15 @@
* details.
*/

import createPortletURL from './create_portlet_url.es';
import createPortletURL from './create_portlet_url';

/**
* Returns an action portlet URL in form of a URL object by setting the lifecycle parameter
* @param {!string} basePortletURL The base portlet URL to be modified in this utility
* @param {object} parameters Search parameters to be added or changed in the base URL
* @return {URL} Action Portlet URL
* @review
*/
export default function createActionURL(basePortletURL, parameters = {}) {
export default function createActionURL(
basePortletURL: string,
parameters: {[key: string]: string} = {}
) {
return createPortletURL(basePortletURL, {
...parameters,
p_p_lifecycle: '1',
Original file line number Diff line number Diff line change
@@ -16,18 +16,17 @@ import getPortletNamespace from '../get_portlet_namespace';

const SCHEME_REGEXP = /^[a-z][a-z0-9+.-]*:/i;

function isAbsolute_(urlString) {
function isAbsolute_(urlString: string) {
return SCHEME_REGEXP.test(urlString);
}

/**
* Returns a portlet URL in form of a URL Object
* @param {!string} basePortletURL The base portlet URL to be modified in this utility
* @param {object} parameters Search parameters to be added or changed in the base URL
* @return {URL} Portlet URL Object
* @review
*/
export default function createPortletURL(basePortletURL, parameters = {}) {
export default function createPortletURL(
basePortletURL: string,
parameters: {[key: string]: string} = {}
) {
if (typeof basePortletURL !== 'string') {
throw new TypeError('basePortletURL parameter must be a string');
}
@@ -95,7 +94,7 @@ export default function createPortletURL(basePortletURL, parameters = {}) {
let namespace = '';

if (Object.entries(parameters).length) {
namespace = getPortletNamespace(portletID);
namespace = getPortletNamespace(portletID!);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For sure portletID can be null here. That's a possible bug somewhere that we haven't yet seen. Thoughts on what should we really do here?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few lines above:

if (Object.entries(parameters).length && !portletID) {
	throw new TypeError(
		'Portlet ID must not be null if parameters are provided'
	);
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally speaking, I guess we could replace all manual type checks with TS. Unless we want them in runtime, to show some legitimate case in an error log 🤔

Copy link
Author

@jbalsas jbalsas Aug 20, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So that should be a || 😂 ?

We can add this one to the list of TS wins... doubt we would've ever seen that otherwise (other than with a bug report).

Copy link
Author

@jbalsas jbalsas Aug 20, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You see, the thing is that if parameters aren't provided, then portletID can be null, but then getPortletNamespace takes (or could take) non-nullable strings. That's the thing I wanted to raise the attention to. Need to decide how much we lean on the type system. I think the more the better, but maybe in this case null is a valid thing to pass to getPortletNamespace which doesn't seem to be the case since it will throw a TypeError in that case.

Copy link
Collaborator

@markocikos markocikos Aug 20, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If parameters are not provided, we never reach getPortletNamespace:

if (Object.entries(parameters).length) {
	namespace = getPortletNamespace(portletID);
}

In this case, the value of portletID, that we maybe took from basePortletURL, does not matter.

A bug here might be that we are not checking and namespacing params in basePortletURL. But, I'm guessing we are assuming the basePortletURL will be used as intended, a base URL with no portlet-specific params.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If parameters are not provided, we never reach getPortletNamespace:

lol, you're totally right... need to get me some better reading glasses 👴

I wonder why TS is unable to infer that portletID can't be null at that point. Maybe this could be written in a way that it would make it possible similar to what happens in #1379 (comment)

For this case I'll simply as string, then.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe putting everything inside one if...

let namespace = '';

if (Object.entries(parameters).length) {
    if (!portletID) {
        throw new TypeError(
            'Portlet ID must not be null if parameters are provided'
        );
    }
    namespace = getPortletNamespace(portletID);
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that should do it!

}

Object.keys(parameters).forEach((key) => {
Original file line number Diff line number Diff line change
@@ -12,16 +12,15 @@
* details.
*/

import createPortletURL from './create_portlet_url.es';
import createPortletURL from './create_portlet_url';

/**
* Returns a render portlet URL in form of a URL object by setting the lifecycle parameter
* @param {!string} basePortletURL The base portlet URL to be modified in this utility
* @param {object} parameters Search parameters to be added or changed in the base URL
* @return {URL} Render Portlet URL
* @review
*/
export default function createRenderURL(basePortletURL, parameters = {}) {
export default function createRenderURL(
basePortletURL: string,
parameters: {[key: string]: string} = {}
) {
return createPortletURL(basePortletURL, {
...parameters,
p_p_lifecycle: '0',
Original file line number Diff line number Diff line change
@@ -12,16 +12,15 @@
* details.
*/

import createPortletURL from './create_portlet_url.es';
import createPortletURL from './create_portlet_url';

/**
* Returns a resource portlet URL in form of a URL object by setting the lifecycle parameter
* @param {!string} basePortletURL The base portlet URL to be modified in this utility
* @param {object} parameters Search parameters to be added or changed in the base URL
* @return {URL} Resource Portlet URL
* @review
*/
export default function createResourceURL(basePortletURL, parameters = {}) {
export default function createResourceURL(
basePortletURL: string,
parameters: {[key: string]: string} = {}
) {
return createPortletURL(basePortletURL, {
...parameters,
p_p_lifecycle: '2',
Original file line number Diff line number Diff line change
@@ -12,17 +12,15 @@
* details.
*/

'use strict';

import createActionURL from '../../../../src/main/resources/META-INF/resources/liferay/util/portlet_url/create_action_url.es';
import createActionURL from '../../../../src/main/resources/META-INF/resources/liferay/util/portlet_url/create_action_url';

describe('Liferay.Util.PortletURL.createActionURL', () => {
it('returns a URL object with a href parameter containing the p_p_lifecycle parameter set to 1', () => {
Liferay = {
ThemeDisplay: {
getPortalURL: jest.fn(() => 'http://localhost:8080'),
},
};
} as any;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mocking can get tricky... 🤔

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. But that's expected. That's why you have to use libraries like mockito in Java, too 🤷

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe using as const can be more secure, at least we can have the viability of what we have inside the Liferay mock.


const portletURL = createActionURL(
'http://localhost:8080/group/control_panel/manage?p_p_id=foo'
Original file line number Diff line number Diff line change
@@ -12,34 +12,35 @@
* details.
*/

'use strict';

import createPortletURL from '../../../../src/main/resources/META-INF/resources/liferay/util/portlet_url/create_portlet_url.es';
import createPortletURL from '../../../../src/main/resources/META-INF/resources/liferay/util/portlet_url/create_portlet_url';

describe('Liferay.Util.PortletURL.createPortletURL', () => {
afterEach(() => {
Liferay.ThemeDisplay.getPortalURL.mockRestore();
(Liferay.ThemeDisplay.getPortalURL as jest.Mock<
string,
[]
>).mockRestore();
Comment on lines +19 to +22
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😬

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe to avoid things like this for global objects we can create a base interface for Liferay with generic types to be able to use it in tests and another one for development.

});

beforeEach(() => {
Liferay = {
ThemeDisplay: {
getPortalURL: jest.fn(() => 'http://localhost:8080'),
},
};
} as any;
});

it('throws an error if basePortletURL is not a string', () => {
expect(() => createPortletURL({portlet: 'url'}, {foo: 'bar'})).toThrow(
'basePortletURL parameter must be a string'
);
expect(() =>
createPortletURL({portlet: 'url'} as any, {foo: 'bar'})
).toThrow('basePortletURL parameter must be a string');
});

it('throws an error if parameters is not an object', () => {
expect(() =>
createPortletURL(
'http://localhost:8080/group/control_panel/manage',
'foo:bar'
'foo:bar' as any
)
).toThrow('parameters argument must be an object');
});
Original file line number Diff line number Diff line change
@@ -12,17 +12,15 @@
* details.
*/

'use strict';

import createRenderURL from '../../../../src/main/resources/META-INF/resources/liferay/util/portlet_url/create_render_url.es';
import createRenderURL from '../../../../src/main/resources/META-INF/resources/liferay/util/portlet_url/create_render_url';

describe('Liferay.Util.PortletURL.createRenderURL', () => {
it('returns a URL object with a href parameter containing the p_p_lifecycle parameter set to 0', () => {
Liferay = {
ThemeDisplay: {
getPortalURL: jest.fn(() => 'http://localhost:8080'),
},
};
} as any;

const portletURL = createRenderURL(
'http://localhost:8080/group/control_panel/manage?p_p_id=foo'
Original file line number Diff line number Diff line change
@@ -12,17 +12,15 @@
* details.
*/

'use strict';

import createResourceURL from '../../../../src/main/resources/META-INF/resources/liferay/util/portlet_url/create_resource_url.es';
import createResourceURL from '../../../../src/main/resources/META-INF/resources/liferay/util/portlet_url/create_resource_url';

describe('Liferay.Util.PortletURL.createResourceURL', () => {
it('returns a url with the p_p_lifecycle parameter set to 2', () => {
Liferay = {
ThemeDisplay: {
getPortalURL: jest.fn(() => 'http://localhost:8080'),
},
};
} as any;

const portletURL = createResourceURL(
'http://localhost:8080/group/control_panel/manage?p_p_id=foo'
Original file line number Diff line number Diff line change
@@ -27,10 +27,10 @@ export { default as ItemSelectorDialog } from './liferay/ItemSelectorDialog.es';
export { default as PortletBase } from './liferay/PortletBase.es';
export { openModal, openSelectionModal } from './liferay/modal/Modal';
export { default as openSimpleInputModal } from './liferay/modal/commands/OpenSimpleInputModal.es';
export { default as createActionURL } from './liferay/util/portlet_url/create_action_url.es';
export { default as createPortletURL } from './liferay/util/portlet_url/create_portlet_url.es';
export { default as createRenderURL } from './liferay/util/portlet_url/create_render_url.es';
export { default as createResourceURL } from './liferay/util/portlet_url/create_resource_url.es';
export { default as createActionURL } from './liferay/util/portlet_url/create_action_url';
export { default as createPortletURL } from './liferay/util/portlet_url/create_portlet_url';
export { default as createRenderURL } from './liferay/util/portlet_url/create_render_url';
export { default as createResourceURL } from './liferay/util/portlet_url/create_resource_url';
export { ALIGN_POSITIONS, align, getAlignBestRegion } from './liferay/align';
export { getAlignRegion, suggestAlignBestRegion } from './liferay/align';
export { getSessionValue, setSessionValue } from './liferay/util/session.es';
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
/**
* Returns an action portlet URL in form of a URL object by setting the lifecycle parameter
*/
export default function createActionURL(basePortletURL: string, parameters?: {
[key: string]: string;
}): URL;
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
/**
* Returns a portlet URL in form of a URL Object
*/
export default function createPortletURL(basePortletURL: string, parameters?: {
[key: string]: string;
}): URL;
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
/**
* Returns a render portlet URL in form of a URL object by setting the lifecycle parameter
*/
export default function createRenderURL(basePortletURL: string, parameters?: {
[key: string]: string;
}): URL;
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
/**
* Returns a resource portlet URL in form of a URL object by setting the lifecycle parameter
*/
export default function createResourceURL(basePortletURL: string, parameters?: {
[key: string]: string;
}): URL;
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
export {};
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
export {};
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
export {};
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
export {};