@automattic/i18n-check-webpack-plugin 1.0.36 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.1.1] - 2023-10-16
9
+ ### Changed
10
+ - Updated package dependencies. [#33429]
11
+
12
+ ## [1.1.0] - 2023-10-03
13
+ ### Added
14
+ - Add a sub-plugin, `I18nSafeMangleExportsPlugin`, to allow for avoiding problems with Webpack's `optimization.mangleExports` option occasionally mangling an export to one of the i18n function names. [#33392]
15
+
8
16
  ## [1.0.36] - 2023-09-13
9
17
  ### Changed
10
18
  - Updated package dependencies. [#33001]
@@ -172,6 +180,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
172
180
  ### Added
173
181
  - Initial release.
174
182
 
183
+ [1.1.1]: https://github.com/Automattic/i18n-check-webpack-plugin/compare/v1.1.0...v1.1.1
184
+ [1.1.0]: https://github.com/Automattic/i18n-check-webpack-plugin/compare/v1.0.36...v1.1.0
175
185
  [1.0.36]: https://github.com/Automattic/i18n-check-webpack-plugin/compare/v1.0.35...v1.0.36
176
186
  [1.0.35]: https://github.com/Automattic/i18n-check-webpack-plugin/compare/v1.0.34...v1.0.35
177
187
  [1.0.34]: https://github.com/Automattic/i18n-check-webpack-plugin/compare/v1.0.33...v1.0.34
package/README.md CHANGED
@@ -32,6 +32,31 @@ Parameters recognized by the plugin are:
32
32
  - `babelOptions`: Supply options for Babel.
33
33
  - `functions`: Supply a custom list of i18n functions to recognize.
34
34
 
35
+ ## Export mangling
36
+
37
+ Webpack's [optimization.mangleExports option](https://webpack.js.org/configuration/optimization/#optimizationmangleexports), enabled by default in production mode, can on occasion mangle an export to the name of one of the i18n functions this module looks for.
38
+
39
+ If you want export mangling and run into this issue, a slightly modified copy of Webpack's internal `MangleExportsPlugin` is provided. Require it as
40
+ ```
41
+ const I18nSafeMangleExportsPlugin = require( '@automattic/i18n-check-webpack-plugin/I18nSafeMangleExportsPlugin' );
42
+ ```
43
+ or
44
+ ```
45
+ import I18nSafeMangleExportsPlugin from '@automattic/i18n-check-webpack-plugin/I18nSafeMangleExportsPlugin';
46
+ ```
47
+ and add it to the `plugins` section of your Webpack config like
48
+ ```js
49
+ {
50
+ plugins: [
51
+ new I18nSafeMangleExportsPlugin(),
52
+ ],
53
+ };
54
+ ```
55
+
56
+ Parameters recognized by the plugin are:
57
+
58
+ - `deterministic`: Set false to use the 'size' mode.
59
+
35
60
  ## Known problematic code patterns
36
61
 
37
62
  These are some code patterns that are known to cause translations to be mangled.
package/SECURITY.md CHANGED
@@ -4,11 +4,20 @@ Full details of the Automattic Security Policy can be found on [automattic.com](
4
4
 
5
5
  ## Supported Versions
6
6
 
7
- Generally, only the latest version of Jetpack has continued support. If a critical vulnerability is found in the current version of Jetpack, we may opt to backport any patches to previous versions.
7
+ Generally, only the latest version of Jetpack and its associated plugins have continued support. If a critical vulnerability is found in the current version of a plugin, we may opt to backport any patches to previous versions.
8
8
 
9
9
  ## Reporting a Vulnerability
10
10
 
11
- [Jetpack](https://jetpack.com/) is an open-source plugin for WordPress. Our HackerOne program covers the plugin software, as well as a variety of related projects and infrastructure.
11
+ Our HackerOne program covers the below plugin software, as well as a variety of related projects and infrastructure:
12
+
13
+ * [Jetpack](https://jetpack.com/)
14
+ * Jetpack Backup
15
+ * Jetpack Boost
16
+ * Jetpack CRM
17
+ * Jetpack Protect
18
+ * Jetpack Search
19
+ * Jetpack Social
20
+ * Jetpack VideoPress
12
21
 
13
22
  **For responsible disclosure of security issues and to be eligible for our bug bounty program, please submit your report via the [HackerOne](https://hackerone.com/automattic) portal.**
14
23
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/i18n-check-webpack-plugin",
3
- "version": "1.0.36",
3
+ "version": "1.1.1",
4
4
  "description": "A Webpack plugin to check that WordPress i18n hasn't been mangled by Webpack optimizations.",
5
5
  "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/i18n-check-webpack-plugin/#readme",
6
6
  "bugs": {
@@ -20,7 +20,7 @@
20
20
  "debug": "^4.3.2"
21
21
  },
22
22
  "devDependencies": {
23
- "@babel/core": "7.22.17",
23
+ "@babel/core": "7.23.0",
24
24
  "jest": "29.4.3",
25
25
  "webpack": "5.76.0",
26
26
  "webpack-cli": "4.9.1"
@@ -31,6 +31,7 @@
31
31
  },
32
32
  "exports": {
33
33
  ".": "./src/I18nCheckPlugin.js",
34
+ "./I18nSafeMangleExportsPlugin": "./src/I18nSafeMangleExportsPlugin.js",
34
35
  "./GettextExtractor": "./src/GettextExtractor.js",
35
36
  "./GettextEntries": "./src/GettextEntries.js",
36
37
  "./GettextEntry": "./src/GettextEntry.js"
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Webpack plugin to make export mangling safe to use with I18nCheckWebapckPlugin.
3
+ *
4
+ * Based on Webpack's MangleExportsPlugin. This is the same except for the
5
+ * addition of some default values in the `usedNames` set and the options
6
+ * accepted by the constructor.
7
+ */
8
+
9
+ const {
10
+ UsageState,
11
+ Template: {
12
+ numberToIdentifier,
13
+ NUMBER_OF_IDENTIFIER_START_CHARS,
14
+ NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS,
15
+ },
16
+ util: {
17
+ comparators: { compareSelect, compareStringsNumeric },
18
+ },
19
+ } = require( 'webpack' );
20
+ const { assignDeterministicIds } = require( 'webpack/lib/ids/IdHelpers' );
21
+
22
+ /** @typedef {import("../Compiler")} Compiler */
23
+ /** @typedef {import("../ExportsInfo")} ExportsInfo */
24
+ /** @typedef {import("../ExportsInfo").ExportInfo} ExportInfo */
25
+
26
+ /**
27
+ * Determine if we can mangle.
28
+ * @param {ExportsInfo} exportsInfo - exports info
29
+ * @returns {boolean} mangle is possible
30
+ */
31
+ const canMangle = exportsInfo => {
32
+ if ( exportsInfo.otherExportsInfo.getUsed( undefined ) !== UsageState.Unused ) {
33
+ return false;
34
+ }
35
+ let hasSomethingToMangle = false;
36
+ for ( const exportInfo of exportsInfo.exports ) {
37
+ if ( exportInfo.canMangle === true ) {
38
+ hasSomethingToMangle = true;
39
+ }
40
+ }
41
+ return hasSomethingToMangle;
42
+ };
43
+
44
+ // Sort by name
45
+ const comparator = compareSelect( e => e.name, compareStringsNumeric );
46
+ /**
47
+ * Mangle exports.
48
+ * @param {boolean} deterministic - use deterministic names
49
+ * @param {ExportsInfo} exportsInfo - exports info
50
+ * @param {boolean | undefined} isNamespace - is namespace object
51
+ * @returns {void}
52
+ */
53
+ const mangleExportsInfo = ( deterministic, exportsInfo, isNamespace ) => {
54
+ if ( ! canMangle( exportsInfo ) ) {
55
+ return;
56
+ }
57
+ const usedNames = new Set( [ '__', '_x', '_n', '_nx' ] );
58
+ /** @type {ExportInfo[]} */
59
+ const mangleableExports = [];
60
+
61
+ // Avoid to renamed exports that are not provided when
62
+ // 1. it's not a namespace export: non-provided exports can be found in prototype chain
63
+ // 2. there are other provided exports and deterministic mode is chosen:
64
+ // non-provided exports would break the determinism
65
+ let avoidMangleNonProvided = ! isNamespace;
66
+ if ( ! avoidMangleNonProvided && deterministic ) {
67
+ for ( const exportInfo of exportsInfo.ownedExports ) {
68
+ if ( exportInfo.provided !== false ) {
69
+ avoidMangleNonProvided = true;
70
+ break;
71
+ }
72
+ }
73
+ }
74
+ for ( const exportInfo of exportsInfo.ownedExports ) {
75
+ const name = exportInfo.name;
76
+ if ( ! exportInfo.hasUsedName() ) {
77
+ if (
78
+ // Can the export be mangled?
79
+ exportInfo.canMangle !== true ||
80
+ // Never rename 1 char exports
81
+ ( name.length === 1 && /^[a-zA-Z0-9_$]/.test( name ) ) ||
82
+ // Don't rename 2 char exports in deterministic mode
83
+ ( deterministic &&
84
+ name.length === 2 &&
85
+ /^[a-zA-Z_$][a-zA-Z0-9_$]|^[1-9][0-9]/.test( name ) ) ||
86
+ // Don't rename exports that are not provided
87
+ ( avoidMangleNonProvided && exportInfo.provided !== true )
88
+ ) {
89
+ exportInfo.setUsedName( name );
90
+ usedNames.add( name );
91
+ } else {
92
+ mangleableExports.push( exportInfo );
93
+ }
94
+ }
95
+ if ( exportInfo.exportsInfoOwned ) {
96
+ const used = exportInfo.getUsed( undefined );
97
+ if ( used === UsageState.OnlyPropertiesUsed || used === UsageState.Unused ) {
98
+ mangleExportsInfo( deterministic, exportInfo.exportsInfo, false );
99
+ }
100
+ }
101
+ }
102
+ if ( deterministic ) {
103
+ assignDeterministicIds(
104
+ mangleableExports,
105
+ e => e.name,
106
+ comparator,
107
+ ( e, id ) => {
108
+ const name = numberToIdentifier( id );
109
+ const size = usedNames.size;
110
+ usedNames.add( name );
111
+ if ( size === usedNames.size ) {
112
+ return false;
113
+ }
114
+ e.setUsedName( name );
115
+ return true;
116
+ },
117
+ [
118
+ NUMBER_OF_IDENTIFIER_START_CHARS,
119
+ NUMBER_OF_IDENTIFIER_START_CHARS * NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS,
120
+ ],
121
+ NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS,
122
+ usedNames.size
123
+ );
124
+ } else {
125
+ const usedExports = [];
126
+ const unusedExports = [];
127
+ for ( const exportInfo of mangleableExports ) {
128
+ if ( exportInfo.getUsed( undefined ) === UsageState.Unused ) {
129
+ unusedExports.push( exportInfo );
130
+ } else {
131
+ usedExports.push( exportInfo );
132
+ }
133
+ }
134
+ usedExports.sort( comparator );
135
+ unusedExports.sort( comparator );
136
+ let i = 0;
137
+ for ( const list of [ usedExports, unusedExports ] ) {
138
+ for ( const exportInfo of list ) {
139
+ let name;
140
+ do {
141
+ name = numberToIdentifier( i++ );
142
+ } while ( usedNames.has( name ) );
143
+ exportInfo.setUsedName( name );
144
+ }
145
+ }
146
+ }
147
+ };
148
+
149
+ class MangleExportsPlugin {
150
+ constructor( options ) {
151
+ this._deterministic = options?.deterministic ?? true;
152
+ }
153
+ /**
154
+ * Apply the plugin
155
+ * @param {Compiler} compiler - the compiler instance
156
+ * @returns {void}
157
+ */
158
+ apply( compiler ) {
159
+ const { _deterministic: deterministic } = this;
160
+ compiler.hooks.compilation.tap( 'MangleExportsPlugin', compilation => {
161
+ const moduleGraph = compilation.moduleGraph;
162
+ compilation.hooks.optimizeCodeGeneration.tap( 'MangleExportsPlugin', modules => {
163
+ if ( compilation.moduleMemCaches ) {
164
+ throw new Error(
165
+ "optimization.mangleExports can't be used with cacheUnaffected as export mangling is a global effect"
166
+ );
167
+ }
168
+ for ( const module of modules ) {
169
+ const isNamespace = module.buildMeta && module.buildMeta.exportsType === 'namespace';
170
+ const exportsInfo = moduleGraph.getExportsInfo( module );
171
+ mangleExportsInfo( deterministic, exportsInfo, isNamespace );
172
+ }
173
+ } );
174
+ } );
175
+ }
176
+ }
177
+
178
+ module.exports = MangleExportsPlugin;