@samitouri / QOS-React / commits / 54d86eb822

Improve display of filenames in component stack (#12059)

* Improve display of filenames in component stack * Add explanatory comment * tests: add tests for component stack trace displaying * Tweak test * Rewrite test and revert implementation * Extract a variable * Rewrite implementation

Billy Janitsch committed Aug 8, 2018 at 22:33 UTC 54d86eb8227255b207c8a82619d374738b542ca8
2 files changed +133 -13
packages/shared/__tests__/describeComponentFrame-test.js new
+111
@@ -0,0 +1,111 @@
1 +/**
2 + * Copyright (c) 2016-present, Facebook, Inc.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @emails react-core
8 + */
9 +
10 +'use strict';
11 +
12 +let React;
13 +let ReactDOM;
14 +
15 +describe('Component stack trace displaying', () => {
16 + beforeEach(() => {
17 + React = require('react');
18 + ReactDOM = require('react-dom');
19 + });
20 +
21 + it('should provide filenames in stack traces', () => {
22 + class Component extends React.Component {
23 + render() {
24 + return [<span>a</span>, <span>b</span>];
25 + }
26 + }
27 +
28 + spyOnDev(console, 'error');
29 + const container = document.createElement('div');
30 + const fileNames = {
31 + '': '',
32 + '/': '',
33 + '\\': '',
34 + Foo: 'Foo',
35 + 'Bar/Foo': 'Foo',
36 + 'Bar\\Foo': 'Foo',
37 + 'Baz/Bar/Foo': 'Foo',
38 + 'Baz\\Bar\\Foo': 'Foo',
39 +
40 + 'Foo.js': 'Foo.js',
41 + 'Foo.jsx': 'Foo.jsx',
42 + '/Foo.js': 'Foo.js',
43 + '/Foo.jsx': 'Foo.jsx',
44 + '\\Foo.js': 'Foo.js',
45 + '\\Foo.jsx': 'Foo.jsx',
46 + 'Bar/Foo.js': 'Foo.js',
47 + 'Bar/Foo.jsx': 'Foo.jsx',
48 + 'Bar\\Foo.js': 'Foo.js',
49 + 'Bar\\Foo.jsx': 'Foo.jsx',
50 + '/Bar/Foo.js': 'Foo.js',
51 + '/Bar/Foo.jsx': 'Foo.jsx',
52 + '\\Bar\\Foo.js': 'Foo.js',
53 + '\\Bar\\Foo.jsx': 'Foo.jsx',
54 + 'Bar/Baz/Foo.js': 'Foo.js',
55 + 'Bar/Baz/Foo.jsx': 'Foo.jsx',
56 + 'Bar\\Baz\\Foo.js': 'Foo.js',
57 + 'Bar\\Baz\\Foo.jsx': 'Foo.jsx',
58 + '/Bar/Baz/Foo.js': 'Foo.js',
59 + '/Bar/Baz/Foo.jsx': 'Foo.jsx',
60 + '\\Bar\\Baz\\Foo.js': 'Foo.js',
61 + '\\Bar\\Baz\\Foo.jsx': 'Foo.jsx',
62 + 'C:\\funny long (path)/Foo.js': 'Foo.js',
63 + 'C:\\funny long (path)/Foo.jsx': 'Foo.jsx',
64 +
65 + 'index.js': 'index.js',
66 + 'index.jsx': 'index.jsx',
67 + '/index.js': 'index.js',
68 + '/index.jsx': 'index.jsx',
69 + '\\index.js': 'index.js',
70 + '\\index.jsx': 'index.jsx',
71 + 'Bar/index.js': 'Bar/index.js',
72 + 'Bar/index.jsx': 'Bar/index.jsx',
73 + 'Bar\\index.js': 'Bar/index.js',
74 + 'Bar\\index.jsx': 'Bar/index.jsx',
75 + '/Bar/index.js': 'Bar/index.js',
76 + '/Bar/index.jsx': 'Bar/index.jsx',
77 + '\\Bar\\index.js': 'Bar/index.js',
78 + '\\Bar\\index.jsx': 'Bar/index.jsx',
79 + 'Bar/Baz/index.js': 'Baz/index.js',
80 + 'Bar/Baz/index.jsx': 'Baz/index.jsx',
81 + 'Bar\\Baz\\index.js': 'Baz/index.js',
82 + 'Bar\\Baz\\index.jsx': 'Baz/index.jsx',
83 + '/Bar/Baz/index.js': 'Baz/index.js',
84 + '/Bar/Baz/index.jsx': 'Baz/index.jsx',
85 + '\\Bar\\Baz\\index.js': 'Baz/index.js',
86 + '\\Bar\\Baz\\index.jsx': 'Baz/index.jsx',
87 + 'C:\\funny long (path)/index.js': 'funny long (path)/index.js',
88 + 'C:\\funny long (path)/index.jsx': 'funny long (path)/index.jsx',
89 + };
90 + Object.keys(fileNames).forEach((fileName, i) => {
91 + ReactDOM.render(
92 + <Component __source={{fileName, lineNumber: i}} />,
93 + container,
94 + );
95 + });
96 + if (__DEV__) {
97 + let i = 0;
98 + expect(console.error.calls.count()).toBe(Object.keys(fileNames).length);
99 + for (let fileName in fileNames) {
100 + if (!fileNames.hasOwnProperty(fileName)) {
101 + continue;
102 + }
103 + const args = console.error.calls.argsFor(i);
104 + const stack = args[args.length - 1];
105 + const expected = fileNames[fileName];
106 + expect(stack).toContain(`at ${expected}:`);
107 + i++;
108 + }
109 + }
110 + });
111 +});
packages/shared/describeComponentFrame.js
+22 -13
@@ -7,22 +7,31 @@
7 * @flow
8 */
9
10 +const BEFORE_SLASH_RE = /^(.*)[\\\/]/;
11 +
12 export default function(
13 name: null | string,
14 source: any,
15 ownerName: null | string,
16 ) {
15 - return (
16 - '\n in ' +
17 - (name || 'Unknown') +
18 - (source
19 - ? ' (at ' +
20 - source.fileName.replace(/^.*[\\\/]/, '') +
21 - ':' +
22 - source.lineNumber +
23 - ')'
24 - : ownerName
25 - ? ' (created by ' + ownerName + ')'
26 - : '')
27 - );
17 + let sourceInfo = '';
18 + if (source) {
19 + let path = source.fileName;
20 + let fileName = path.replace(BEFORE_SLASH_RE, '');
21 + if (/^index\./.test(fileName)) {
22 + // Special case: include closest folder name for `index.*` filenames.
23 + const match = path.match(BEFORE_SLASH_RE);
24 + if (match) {
25 + const pathBeforeSlash = match[1];
26 + if (pathBeforeSlash) {
27 + const folderName = pathBeforeSlash.replace(BEFORE_SLASH_RE, '');
28 + fileName = folderName + '/' + fileName;
29 + }
30 + }
31 + }
32 + sourceInfo = ' (at ' + fileName + ':' + source.lineNumber + ')';
33 + } else if (ownerName) {
34 + sourceInfo = ' (created by ' + ownerName + ')';
35 + }
36 + return '\n in ' + (name || 'Unknown') + sourceInfo;
37 }