frontend-plugin-api: add helper for locating the callsite of a function

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2023-10-12 11:01:10 +02:00
parent 720b6cda3d
commit 49b0d0dfa9
2 changed files with 143 additions and 0 deletions
@@ -0,0 +1,84 @@
/*
* Copyright 2023 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { describeParentCallSite } from './describeParentCallSite';
class ChromeError {
name = 'Error';
message = 'eHgtF5hmbrXyiEvo';
get stack() {
return `Error: eHgtF5hmbrXyiEvo
at describeParentCallSite (describeParentCallSite.js:1:11)
at parent (parent.js:2:22)
at caller (parent-caller.js:3:33)
at other.js:3:33`;
}
}
class SafariError {
name = 'Error';
message = 'eHgtF5hmbrXyiEvo';
get stack() {
return `describeParentCallSite@describeParentCallSite.js:1:11
parent@parent.js:2:22
caller@parent-caller.js:3:33
code@other.js:3:33`;
}
}
class FirefoxError {
name = 'Error';
message = 'eHgtF5hmbrXyiEvo';
get stack() {
return `describeParentCallSite@describeParentCallSite.js:1:11
parent@parent.js:2:22
caller@parent-caller.js:3:33
@other.js:3:33
`;
}
}
describe('describeParentCallSite', () => {
it('should work in Chrome', () => {
function myFactory() {
return describeParentCallSite(ChromeError);
}
function myCaller() {
return myFactory();
}
expect(myCaller()).toBe('parent-caller.js:3:33');
});
it('should work in Safari', () => {
function myFactory() {
return describeParentCallSite(SafariError);
}
function myCaller() {
return myFactory();
}
expect(myCaller()).toBe('parent-caller.js:3:33');
});
it('should work in Firefox', () => {
function myFactory() {
return describeParentCallSite(FirefoxError);
}
function myCaller() {
return myFactory();
}
expect(myCaller()).toBe('parent-caller.js:3:33');
});
});
@@ -0,0 +1,59 @@
/*
* Copyright 2023 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const MESSAGE_MARKER = 'eHgtF5hmbrXyiEvo';
/**
* Internal helper that describes the location of the parent caller.
* @internal
*/
export function describeParentCallSite(
ErrorConstructor: { new (message: string): Error } = Error,
): string {
const { stack } = new ErrorConstructor(MESSAGE_MARKER);
if (!stack) {
return '<unknown>';
}
// Safari and Firefox don't include the error itself in the stack
const startIndex = stack.includes(MESSAGE_MARKER)
? stack.indexOf('\n') + 1
: 0;
const secondEntryStart =
stack.indexOf('\n', stack.indexOf('\n', startIndex) + 1) + 1;
const secondEntryEnd = stack.indexOf('\n', secondEntryStart);
const line = stack.substring(secondEntryStart, secondEntryEnd).trim();
if (!line) {
return 'unknown';
}
// Below we try to extract the location for different browsers.
// Since RouteRefs are declared at the top-level of modules the caller name isn't interesting.
// Chrome
if (line.includes('(')) {
return line.substring(line.indexOf('(') + 1, line.indexOf(')'));
}
// Safari & Firefox
if (line.includes('@')) {
return line.substring(line.indexOf('@') + 1);
}
// Give up
return line;
}