Skip to content

Commit 4b2018a

Browse files
authored
feat(sourcemap): enable rfc-3986 urls (#3100)
ensure that sourceMapUrls are compliant with RFC-3986, per the sourcemap [specification](https://sourcemaps.info/spec.html#h.crcf4lqeivt8): > Note: <url> is a URL as defined in RFC3986; in particular, characters outside the set permitted to appear in URIs must be percent-encoded. STENCIL-173: Make sourceMapUrl values RFC 3986 compliant
1 parent adf843a commit 4b2018a

File tree

2 files changed

+94
-2
lines changed

2 files changed

+94
-2
lines changed

src/utils/sourcemaps.ts

+18-2
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,30 @@ export const rollupToStencilSourceMap = (rollupSourceMap: RollupSourceMap | unde
2828
*/
2929
const JS_SOURCE_MAPPING_URL_LINKER = '//# sourceMappingURL=';
3030

31+
/**
32+
* Generates an RFC-3986 compliant string for the given input.
33+
* More information about RFC-3986 can be found [here](https://datatracker.ietf.org/doc/html/rfc3986)
34+
* This function's original source is derived from
35+
* [MDN's encodeURIComponent documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent#description)
36+
* @param filename the filename to encode
37+
* @returns the encoded URI
38+
*/
39+
const encodeFilenameToRfc3986 = (filename: string): string => {
40+
const encodedUri = encodeURIComponent(filename);
41+
// replace all '!', single quotes, '(', ')', and '*' with their hexadecimal values (UTF-16)
42+
return encodedUri.replace(/[!'()*]/g, (matchedCharacter) => {
43+
return '%' + matchedCharacter.charCodeAt(0).toString(16);
44+
});
45+
};
46+
3147
/**
3248
* Generates a string used to link generated code with the original source, to be placed at the end of the generated
33-
* code. Note that at this time, this method is _not_ RFC3986 compliant.
49+
* code.
3450
* @param url the url of the source map
3551
* @returns a linker string, of the format {@link JS_SOURCE_MAPPING_URL_LINKER}=<url>
3652
*/
3753
export const getSourceMappingUrlLinker = (url: string): string => {
38-
return `${JS_SOURCE_MAPPING_URL_LINKER}${url}`;
54+
return `${JS_SOURCE_MAPPING_URL_LINKER}${encodeFilenameToRfc3986(url)}`;
3955
};
4056

4157
/**

src/utils/test/sourcemaps.spec.ts

+76
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,87 @@ describe('sourcemaps', () => {
4646
it('returns a correctly formatted url', () => {
4747
expect(getSourceMappingUrlLinker('some-pkg')).toBe('//# sourceMappingURL=some-pkg');
4848
});
49+
50+
it('handles question marks in URLs', () => {
51+
expect(getSourceMappingUrlLinker('some-pkg?')).toBe('//# sourceMappingURL=some-pkg%3F');
52+
});
53+
54+
it('handles equal signs in URLs', () => {
55+
expect(getSourceMappingUrlLinker('some-pkg=')).toBe('//# sourceMappingURL=some-pkg%3D');
56+
});
57+
58+
it('handles ampersands in URLs', () => {
59+
expect(getSourceMappingUrlLinker('some-pkg&')).toBe('//# sourceMappingURL=some-pkg%26');
60+
});
61+
62+
it('handles slashes in URLs', () => {
63+
expect(getSourceMappingUrlLinker('some-pkg/')).toBe('//# sourceMappingURL=some-pkg%2F');
64+
});
65+
66+
it('handles exclamation points in URLs', () => {
67+
expect(getSourceMappingUrlLinker('some-pkg!')).toBe('//# sourceMappingURL=some-pkg%21');
68+
});
69+
70+
it('handles single quotes in URLs', () => {
71+
expect(getSourceMappingUrlLinker("some-'pkg'")).toBe('//# sourceMappingURL=some-%27pkg%27');
72+
});
73+
74+
it('handles parenthesis in URLs', () => {
75+
expect(getSourceMappingUrlLinker('some-(pkg)')).toBe('//# sourceMappingURL=some-%28pkg%29');
76+
});
77+
78+
it('handles asterisks in URLs', () => {
79+
expect(getSourceMappingUrlLinker('some-pkg*')).toBe('//# sourceMappingURL=some-pkg%2a');
80+
});
81+
82+
it('encodes multiple disallowed characters at once', () => {
83+
expect(getSourceMappingUrlLinker('!some-(pkg)*')).toBe('//# sourceMappingURL=%21some-%28pkg%29%2a');
84+
});
4985
});
5086

5187
describe('getSourceMappingUrlLinkerWithNewline', () => {
5288
it('returns a correctly formatted url', () => {
5389
expect(getSourceMappingUrlForEndOfFile('some-pkg')).toBe('\n//# sourceMappingURL=some-pkg.map');
5490
});
91+
92+
it('returns a correctly formatted url', () => {
93+
expect(getSourceMappingUrlForEndOfFile('some-pkg')).toBe('\n//# sourceMappingURL=some-pkg.map');
94+
});
95+
96+
it('handles question marks in URLs', () => {
97+
expect(getSourceMappingUrlForEndOfFile('some-pkg?')).toBe('\n//# sourceMappingURL=some-pkg%3F.map');
98+
});
99+
100+
it('handles equal signs in URLs', () => {
101+
expect(getSourceMappingUrlForEndOfFile('some-pkg=')).toBe('\n//# sourceMappingURL=some-pkg%3D.map');
102+
});
103+
104+
it('handles ampersands in URLs', () => {
105+
expect(getSourceMappingUrlForEndOfFile('some-pkg&')).toBe('\n//# sourceMappingURL=some-pkg%26.map');
106+
});
107+
108+
it('handles slashes in URLs', () => {
109+
expect(getSourceMappingUrlForEndOfFile('some-pkg/')).toBe('\n//# sourceMappingURL=some-pkg%2F.map');
110+
});
111+
112+
it('handles exclamation points in URLs', () => {
113+
expect(getSourceMappingUrlForEndOfFile('some-pkg!')).toBe('\n//# sourceMappingURL=some-pkg%21.map');
114+
});
115+
116+
it('handles single quotes in URLs', () => {
117+
expect(getSourceMappingUrlForEndOfFile("some-'pkg'")).toBe('\n//# sourceMappingURL=some-%27pkg%27.map');
118+
});
119+
120+
it('handles parenthesis in URLs', () => {
121+
expect(getSourceMappingUrlForEndOfFile('some-(pkg)')).toBe('\n//# sourceMappingURL=some-%28pkg%29.map');
122+
});
123+
124+
it('handles asterisks in URLs', () => {
125+
expect(getSourceMappingUrlForEndOfFile('some-pkg*')).toBe('\n//# sourceMappingURL=some-pkg%2a.map');
126+
});
127+
128+
it('encodes multiple disallowed characters at once', () => {
129+
expect(getSourceMappingUrlForEndOfFile('!some-(pkg)*')).toBe('\n//# sourceMappingURL=%21some-%28pkg%29%2a.map');
130+
});
55131
});
56132
});

0 commit comments

Comments
 (0)