UNPKG

3.26 kBJavaScriptView Raw
1const crypto = require('crypto');
2
3const tryRequire = require('try-require');
4// We need to have React in scope as it is used when we eval the svgr output
5// eslint-disable-next-line no-unused-vars
6const React = require('react');
7const {renderToStaticMarkup} = require('react-dom/server');
8const {default: convert} = require('@svgr/core');
9
10// If @shopify/polaris-icons is available to be required, check them too
11const polarisIcons = tryRequire('@shopify/polaris-icons') || {};
12
13function audit({filenames, contentPerFilename}) {
14 const polarisIconsComponentsPerFilename = Object.entries(polarisIcons).reduce(
15 (memo, [importKey, component]) => {
16 memo[`@shopify/polaris-icons/${importKey}.svg`] = component;
17 return memo;
18 },
19 {},
20 );
21 filenames.unshift(...Object.keys(polarisIconsComponentsPerFilename));
22
23 const contentsPerFilename = filenames.map((filename) => {
24 const reactComponent = filename.startsWith('@shopify/polaris-icons')
25 ? polarisIconsComponentsPerFilename[filename]
26 : componentFromSvgFile(filename, contentPerFilename);
27 return renderToStaticMarkup(reactComponent());
28 });
29
30 const dependentsByHash = filenames.reduce((memo, filename, i) => {
31 const fileContents = contentsPerFilename[i];
32 const contentHash = md5String(JSON.stringify(fileContents));
33
34 // eslint-disable-next-line no-prototype-builtins
35 if (!memo.hasOwnProperty(contentHash)) {
36 memo[contentHash] = [];
37 }
38 memo[contentHash].push(filename);
39
40 return memo;
41 }, {});
42
43 const duplicatedDependentsByHash = Object.entries(dependentsByHash).reduce(
44 (memo, [filename, dependents]) => {
45 if (dependents.length > 1) {
46 memo[filename] = dependents;
47 }
48 return memo;
49 },
50 {},
51 );
52
53 const duplicatedHashesCount = Object.keys(duplicatedDependentsByHash).length;
54
55 return {
56 summary: `Found ${duplicatedHashesCount} content hashes shared by multiple files`,
57 status: duplicatedHashesCount > 0 ? 'error' : 'pass',
58 info: Object.entries(duplicatedDependentsByHash)
59 .map(([hash, duplicatedDependents]) => {
60 const count = duplicatedDependents.length;
61
62 const filesStr = duplicatedDependents
63 .map((file) => ` ${file}`)
64 .join('\n');
65
66 return ` ${hash} matches content used in ${count} files:\n${filesStr}`;
67 })
68 .join('\n'),
69 };
70}
71
72function md5String(string) {
73 return crypto.createHash('md5').update(string).digest('hex');
74}
75
76function componentFromSvgFile(filename, contentPerFilename) {
77 const source = contentPerFilename[filename];
78
79 const svgrOutput = convert.sync(
80 source,
81 {
82 plugins: ['@svgr/plugin-svgo', '@svgr/plugin-jsx'],
83 svgoConfig: {
84 plugins: [{removeTitle: true}, {removeViewBox: false}],
85 },
86 replaceAttrValues: {
87 '#5C5F62': '{undefined}',
88 '#5c5f62': '{undefined}',
89 },
90 template: ({template}, opts, {jsx}) => template.ast`(props) => ${jsx}`,
91 jsx: {
92 babelConfig: {
93 plugins: [['@babel/plugin-transform-react-jsx']],
94 },
95 },
96 },
97 {
98 filePath: filename,
99 },
100 );
101
102 // eslint-disable-next-line no-eval
103 return eval(svgrOutput);
104}
105
106audit.auditName = 'duplicate-content';
107module.exports = audit;