apache / cordova-common
Showing 1 of 2 files from the diff.

@@ -86,21 +86,13 @@
Loading
86 86
        const plugin_vars = is_top_level
87 87
            ? platform_config.installed_plugins[pluginInfo.id]
88 88
            : platform_config.dependent_plugins[pluginInfo.id];
89 -
        let edit_config_changes = null;
90 89
91 -
        if (pluginInfo.getEditConfigs) {
92 -
            edit_config_changes = pluginInfo.getEditConfigs(this.platform);
93 -
        }
90 +
        const edit_config_changes = this._getChanges(pluginInfo, 'EditConfig');
94 91
95 92
        // get config munge, aka how did this plugin change various config files
96 93
        const config_munge = this.generate_plugin_config_munge(pluginInfo, plugin_vars, edit_config_changes);
97 -
        // global munge looks at all plugins' changes to config files
98 -
        const global_munge = platform_config.config_munge;
99 -
        const munge = mungeutil.decrement_munge(global_munge, config_munge);
100 94
101 -
        for (const file in munge.files) {
102 -
            this.apply_file_munge(file, munge.files[file], /* remove = */ true);
103 -
        }
95 +
        this._munge_helper(config_munge, { remove: true });
104 96
105 97
        // Remove from installed_plugins
106 98
        this.platformJson.removePlugin(pluginInfo.id, is_top_level);
@@ -109,43 +101,28 @@
Loading
109 101
    }
110 102
111 103
    add_plugin_changes (pluginInfo, plugin_vars, is_top_level, should_increment, plugin_force) {
112 -
        const platform_config = this.platformJson.root;
113 -
        let config_munge;
114 -
        let edit_config_changes = null;
104 +
        const edit_config_changes = this._getChanges(pluginInfo, 'EditConfig');
115 105
116 -
        if (pluginInfo.getEditConfigs) {
117 -
            edit_config_changes = pluginInfo.getEditConfigs(this.platform);
106 +
        const { configConflicts, pluginConflicts } = this._is_conflicting(edit_config_changes);
107 +
        if (Object.keys(configConflicts.files).length > 0) {
108 +
            // plugin.xml cannot overwrite config.xml changes even if --force is used
109 +
            throw new Error(`${pluginInfo.id} cannot be added. <edit-config> changes in this plugin conflicts with <edit-config> changes in config.xml. Conflicts must be resolved before plugin can be added.`);
118 110
        }
119 -
120 -
        if (!edit_config_changes || edit_config_changes.length === 0) {
121 -
            // get config munge, aka how should this plugin change various config files
122 -
            config_munge = this.generate_plugin_config_munge(pluginInfo, plugin_vars);
123 -
        } else {
124 -
            const isConflictingInfo = this._is_conflicting(edit_config_changes, platform_config.config_munge, plugin_force);
125 -
126 -
            if (isConflictingInfo.conflictWithConfigxml) {
127 -
                throw new Error(`${pluginInfo.id} cannot be added. <edit-config> changes in this plugin conflicts with <edit-config> changes in config.xml. Conflicts must be resolved before plugin can be added.`);
128 -
            }
129 -
            if (plugin_force) {
130 -
                events.emit('warn', '--force is used. edit-config will overwrite conflicts if any. Conflicting plugins may not work as expected.');
131 -
132 -
                // remove conflicting munges
133 -
                const conflict_munge = mungeutil.decrement_munge(platform_config.config_munge, isConflictingInfo.conflictingMunge);
134 -
                for (const conflict_file in conflict_munge.files) {
135 -
                    this.apply_file_munge(conflict_file, conflict_munge.files[conflict_file], /* remove = */ true);
136 -
                }
137 -
138 -
                // force add new munges
139 -
                config_munge = this.generate_plugin_config_munge(pluginInfo, plugin_vars, edit_config_changes);
140 -
            } else if (isConflictingInfo.conflictFound) {
141 -
                throw new Error(`There was a conflict trying to modify attributes with <edit-config> in plugin ${pluginInfo.id}. The conflicting plugin, ${isConflictingInfo.conflictingPlugin}, already modified the same attributes. The conflict must be resolved before ${pluginInfo.id} can be added. You may use --force to add the plugin and overwrite the conflicting attributes.`);
142 -
            } else {
143 -
                // no conflicts, will handle edit-config
144 -
                config_munge = this.generate_plugin_config_munge(pluginInfo, plugin_vars, edit_config_changes);
145 -
            }
111 +
        if (plugin_force) {
112 +
            events.emit('warn', '--force is used. edit-config will overwrite conflicts if any. Conflicting plugins may not work as expected.');
113 +
114 +
            // remove conflicting munges, if any
115 +
            this._munge_helper(pluginConflicts, { remove: true });
116 +
        } else if (Object.keys(pluginConflicts.files).length > 0) {
117 +
            // plugin cannot overwrite other plugin changes without --force
118 +
            const witness = Object.values(Object.values(pluginConflicts.files)[0].parents)[0][0];
119 +
            const conflictingPlugin = witness.plugin;
120 +
            throw new Error(`There was a conflict trying to modify attributes with <edit-config> in plugin ${pluginInfo.id}. The conflicting plugin, ${conflictingPlugin}, already modified the same attributes. The conflict must be resolved before ${pluginInfo.id} can be added. You may use --force to add the plugin and overwrite the conflicting attributes.`);
146 121
        }
147 122
148 -
        this._munge_helper(should_increment, platform_config, config_munge);
123 +
        // get config munge, aka how should this plugin change various config files
124 +
        const config_munge = this.generate_plugin_config_munge(pluginInfo, plugin_vars, edit_config_changes);
125 +
        this._munge_helper(config_munge, { should_increment });
149 126
150 127
        // Move to installed/dependent_plugins
151 128
        this.platformJson.addPlugin(pluginInfo.id, plugin_vars || {}, is_top_level);
@@ -155,68 +132,45 @@
Loading
155 132
156 133
    // Handle edit-config changes from config.xml
157 134
    add_config_changes (config, should_increment) {
158 -
        const platform_config = this.platformJson.root;
159 -
        let changes = [];
160 -
161 -
        if (config.getEditConfigs) {
162 -
            const edit_config_changes = config.getEditConfigs(this.platform);
163 -
            if (edit_config_changes) {
164 -
                changes = changes.concat(edit_config_changes);
165 -
            }
135 +
        const changes = [
136 +
            ...this._getChanges(config, 'EditConfig'),
137 +
            ...this._getChanges(config, 'ConfigFile')
138 +
        ];
139 +
140 +
        const { configConflicts, pluginConflicts } = this._is_conflicting(changes);
141 +
        if (Object.keys(pluginConflicts.files).length !== 0) {
142 +
            events.emit('warn', 'Conflict found, edit-config changes from config.xml will overwrite plugin.xml changes');
166 143
        }
167 -
168 -
        if (config.getConfigFiles) {
169 -
            const config_files_changes = config.getConfigFiles(this.platform);
170 -
            if (config_files_changes) {
171 -
                changes = changes.concat(config_files_changes);
172 -
            }
173 -
        }
174 -
175 -
        if (changes && changes.length > 0) {
176 -
            const isConflictingInfo = this._is_conflicting(changes, platform_config.config_munge, true /* always force overwrite other edit-config */);
177 -
            if (isConflictingInfo.conflictFound) {
178 -
                if (Object.keys(isConflictingInfo.configxmlMunge.files).length !== 0) {
179 -
                    // silently remove conflicting config.xml munges so new munges can be added
180 -
                    const conflict_munge = mungeutil.decrement_munge(platform_config.config_munge, isConflictingInfo.configxmlMunge);
181 -
                    for (const conflict_file in conflict_munge.files) {
182 -
                        this.apply_file_munge(conflict_file, conflict_munge.files[conflict_file], /* remove = */ true);
183 -
                    }
184 -
                }
185 -
186 -
                if (Object.keys(isConflictingInfo.conflictingMunge.files).length !== 0) {
187 -
                    events.emit('warn', 'Conflict found, edit-config changes from config.xml will overwrite plugin.xml changes');
188 -
189 -
                    // remove conflicting plugin.xml munges
190 -
                    const conflict_munge = mungeutil.decrement_munge(platform_config.config_munge, isConflictingInfo.conflictingMunge);
191 -
                    for (const conflict_file in conflict_munge.files) {
192 -
                        this.apply_file_munge(conflict_file, conflict_munge.files[conflict_file], /* remove = */ true);
193 -
                    }
194 -
                }
195 -
            }
144 +
        // remove conflicting config.xml & plugin.xml munges, if any
145 +
        for (const conflict_munge of [configConflicts, pluginConflicts]) {
146 +
            this._munge_helper(conflict_munge, { remove: true });
196 147
        }
197 148
198 149
        // Add config.xml edit-config and config-file munges
199 150
        const config_munge = this.generate_config_xml_munge(config, changes, 'config.xml');
200 -
        this._munge_helper(should_increment, platform_config, config_munge);
151 +
        this._munge_helper(config_munge, { should_increment });
201 152
202 153
        // Move to installed/dependent_plugins
203 154
        return this;
204 155
    }
205 156
206 157
    /** @private */
207 -
    _munge_helper (should_increment, platform_config, config_munge) {
158 +
    _munge_helper (config_munge, { should_increment = true, remove = false } = {}) {
208 159
        // global munge looks at all changes to config files
209 160
        // TODO: The should_increment param is only used by cordova-cli and is going away soon.
210 161
        // If should_increment is set to false, avoid modifying the global_munge (use clone)
211 162
        // and apply the entire config_munge because it's already a proper subset of the global_munge.
212 163
164 +
        const platform_config = this.platformJson.root;
213 165
        const global_munge = platform_config.config_munge;
166 +
167 +
        const method = remove ? 'decrement_munge' : 'increment_munge';
214 168
        const munge = should_increment
215 -
            ? mungeutil.increment_munge(global_munge, config_munge)
169 +
            ? mungeutil[method](global_munge, config_munge)
216 170
            : config_munge;
217 171
218 172
        for (const file in munge.files) {
219 -
            this.apply_file_munge(file, munge.files[file]);
173 +
            this.apply_file_munge(file, munge.files[file], remove);
220 174
        }
221 175
222 176
        return this;
@@ -235,59 +189,45 @@
Loading
235 189
        return this;
236 190
    }
237 191
238 -
    // generate_config_xml_munge
239 192
    // Generate the munge object from config.xml
240 193
    generate_config_xml_munge (config, config_changes, type) {
241 -
        const munge = { files: {} };
242 -
243 -
        if (!config_changes) return munge;
244 -
245 -
        const id = type === 'config.xml' ? type : config.id;
246 -
247 -
        config_changes.forEach(change => {
248 -
            change.xmls.forEach(xml => {
249 -
                // 1. stringify each xml
250 -
                const stringified = (new et.ElementTree(xml)).write({ xml_declaration: false });
251 -
                // 2. add into munge
252 -
                if (change.mode) {
253 -
                    mungeutil.deep_add(munge, change.file, change.target, { xml: stringified, count: 1, mode: change.mode, id });
254 -
                } else {
255 -
                    mungeutil.deep_add(munge, change.target, change.parent, { xml: stringified, count: 1, after: change.after });
256 -
                }
257 -
            });
258 -
        });
259 -
260 -
        return munge;
194 +
        const originInfo = { id: type === 'config.xml' ? type : config.id };
195 +
        return this._generateMunge(config_changes, originInfo);
261 196
    }
262 197
263 -
    // generate_plugin_config_munge
264 198
    // Generate the munge object from plugin.xml + vars
265 199
    generate_plugin_config_munge (pluginInfo, vars, edit_config_changes) {
266 -
        vars = vars || {};
267 -
        const munge = { files: {} };
268 200
        const changes = pluginInfo.getConfigFiles(this.platform);
269 -
270 201
        if (edit_config_changes) {
271 202
            Array.prototype.push.apply(changes, edit_config_changes);
272 203
        }
204 +
        const filteredChanges = changes.filter(({ mode }) => mode !== 'remove');
205 +
206 +
        const originInfo = { plugin: pluginInfo.id };
207 +
        return this._generateMunge(filteredChanges, originInfo, vars || {});
208 +
    }
209 +
210 +
    /** @private */
211 +
    _generateMunge (changes, originInfo, vars = {}) {
212 +
        const munge = { files: {} };
273 213
274 214
        changes.forEach(change => {
215 +
            const [file, selector, rest] = change.mode
216 +
                ? [change.file, change.target, { mode: change.mode, ...originInfo }]
217 +
                : [change.target, change.parent, { after: change.after }];
218 +
275 219
            change.xmls.forEach(xml => {
276 220
                // 1. stringify each xml
277 221
                let stringified = (new et.ElementTree(xml)).write({ xml_declaration: false });
278 -
                // interp vars
222 +
223 +
                // interpolate vars, if any
279 224
                Object.keys(vars).forEach(key => {
280 225
                    const regExp = new RegExp(`\\$${key}`, 'g');
281 226
                    stringified = stringified.replace(regExp, vars[key]);
282 227
                });
228 +
283 229
                // 2. add into munge
284 -
                if (change.mode) {
285 -
                    if (change.mode !== 'remove') {
286 -
                        mungeutil.deep_add(munge, change.file, change.target, { xml: stringified, count: 1, mode: change.mode, plugin: pluginInfo.id });
287 -
                    }
288 -
                } else {
289 -
                    mungeutil.deep_add(munge, change.target, change.parent, { xml: stringified, count: 1, after: change.after });
290 -
                }
230 +
                mungeutil.deep_add(munge, file, selector, { xml: stringified, count: 1, ...rest });
291 231
            });
292 232
        });
293 233
@@ -295,77 +235,45 @@
Loading
295 235
    }
296 236
297 237
    /** @private */
298 -
    _is_conflicting (editchanges, config_munge, force) {
299 -
        const files = config_munge.files;
300 -
        let conflictFound = false;
301 -
        let conflictWithConfigxml = false;
302 -
        const conflictingMunge = { files: {} };
303 -
        const configxmlMunge = { files: {} };
304 -
        let conflictingParent;
305 -
        let conflictingPlugin;
306 -
307 -
        editchanges.forEach(editchange => {
308 -
            if (files[editchange.file]) {
309 -
                const parents = files[editchange.file].parents;
310 -
                let target = parents[editchange.target];
311 -
312 -
                // Check if the edit target will resolve to an existing target
313 -
                if (!target || target.length === 0) {
314 -
                    const file_xml = this.config_keeper.get(this.project_dir, this.platform, editchange.file).data;
315 -
                    const resolveEditTarget = xml_helpers.resolveParent(file_xml, editchange.target);
316 -
                    let resolveTarget;
317 -
318 -
                    if (resolveEditTarget) {
319 -
                        for (const parent in parents) {
320 -
                            resolveTarget = xml_helpers.resolveParent(file_xml, parent);
321 -
                            if (resolveEditTarget === resolveTarget) {
322 -
                                conflictingParent = parent;
323 -
                                target = parents[parent];
324 -
                                break;
325 -
                            }
326 -
                        }
327 -
                    }
328 -
                } else {
329 -
                    conflictingParent = editchange.target;
330 -
                }
238 +
    _getChanges (cfg, changeType) {
239 +
        const method = `get${changeType}s`;
240 +
        return (cfg[method] && cfg[method](this.platform)) || [];
241 +
    }
331 242
332 -
                if (target && target.length !== 0) {
333 -
                    // conflict has been found
334 -
                    conflictFound = true;
335 -
336 -
                    if (editchange.id === 'config.xml') {
337 -
                        if (target[0].id === 'config.xml') {
338 -
                            // Keep track of config.xml/config.xml edit-config conflicts
339 -
                            mungeutil.deep_add(configxmlMunge, editchange.file, conflictingParent, target[0]);
340 -
                        } else {
341 -
                            // Keep track of config.xml x plugin.xml edit-config conflicts
342 -
                            mungeutil.deep_add(conflictingMunge, editchange.file, conflictingParent, target[0]);
343 -
                        }
344 -
                    } else {
345 -
                        if (target[0].id === 'config.xml') {
346 -
                            // plugin.xml cannot overwrite config.xml changes even if --force is used
347 -
                            conflictWithConfigxml = true;
348 -
                            return;
349 -
                        }
350 -
351 -
                        if (force) {
352 -
                            // Need to find all conflicts when --force is used, track conflicting munges
353 -
                            mungeutil.deep_add(conflictingMunge, editchange.file, conflictingParent, target[0]);
354 -
                        } else {
355 -
                            // plugin cannot overwrite other plugin changes without --force
356 -
                            conflictingPlugin = target[0].plugin;
357 -
                        }
358 -
                    }
359 -
                }
360 -
            }
243 +
    /** @private */
244 +
    _is_conflicting (editchanges) {
245 +
        const platform_config = this.platformJson.root;
246 +
        const { files } = platform_config.config_munge;
247 +
248 +
        const configConflicts = { files: {} }; // config.xml edit-config conflicts
249 +
        const pluginConflicts = { files: {} }; // plugin.xml edit-config conflicts
250 +
251 +
        const registerConflict = (file, selector) => {
252 +
            const witness = files[file].parents[selector][0];
253 +
            const conflictMunge = witness.id === 'config.xml' ? configConflicts : pluginConflicts;
254 +
            mungeutil.deep_add(conflictMunge, file, selector, witness);
255 +
        };
256 +
257 +
        editchanges.forEach(({ file, target }) => {
258 +
            if (!files[file]) return null;
259 +
            const { parents: changesBySelector } = files[file];
260 +
261 +
            const conflicts = changesBySelector[target] || [];
262 +
            if (conflicts.length > 0) return registerConflict(file, target);
263 +
264 +
            // Check if the edit target will resolve to an existing target
265 +
            const file_xml = this.config_keeper.get(this.project_dir, this.platform, file).data;
266 +
            const resolveEditTarget = xml_helpers.resolveParent(file_xml, target);
267 +
            if (!resolveEditTarget) return;
268 +
            const selector = Object.keys(changesBySelector).find(parent =>
269 +
                resolveEditTarget === xml_helpers.resolveParent(file_xml, parent)
270 +
            );
271 +
            if (selector) return registerConflict(file, selector);
361 272
        });
362 273
363 274
        return {
364 -
            conflictFound,
365 -
            conflictingPlugin,
366 -
            conflictingMunge,
367 -
            configxmlMunge,
368 -
            conflictWithConfigxml
275 +
            configConflicts,
276 +
            pluginConflicts
369 277
        };
370 278
    }
371 279
Files Coverage
src 89.13%
cordova-common.js 0.00%
Project Totals (20 files) 87.79%
Notifications are pending CI completion. Periodically Codecov will check the CI state, when complete notifications will be submitted. Push notifications now.
Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading