New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

ember-unused-components

Package Overview
Dependencies
Maintainers
1
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ember-unused-components - npm Package Compare versions

Comparing version

to
1.2.0

lib/object-info.js

21

CHANGELOG.md

@@ -5,4 +5,23 @@ # ember-unused-components CHANGELOG

Nothing new yet
- nothing yet
## 1.2.0 (January 28, 2020)
**FEATURES:**
- [#52](https://github.com/vastec/ember-unused-components/pull/52) [EXPERIMENTAL] - Search for unused compnents from addons :fire: Maybe you don't need that addon anymore?
**REFACTOR:**
- [#52](https://github.com/vastec/ember-unused-components/pull/52) Improvements to components detection + internal restructuring that sets things in the right direction for future development
Special thanks to @jkeen for his huge work on the refactor and the experimental feature!
**MAINTENANCE:**
- ([#39](https://github.com/vastec/ember-unused-components/pull/39)) Bump ava from `2.3.0` to `2.4.0`
- ([#42](https://github.com/vastec/ember-unused-components/pull/42)) Bump colors from `1.3.3` to `1.4.0`
- ([#43](https://github.com/vastec/ember-unused-components/pull/43), [#61](https://github.com/vastec/ember-unused-components/pull/61)) Bump eslint-plugin-prettier from `3.1.0` to `3.1.2`
- ([#45](https://github.com/vastec/ember-unused-components/pull/45), [#65](https://github.com/vastec/ember-unused-components/pull/65)) Bump yargs from `14.0.0` to `15.1.0`
- ([#48](https://github.com/vastec/ember-unused-components/pull/48), [#62](https://github.com/vastec/ember-unused-components/pull/62)) Bump eslint from `6.3.0` to `6.8.0`
- ([#49](https://github.com/vastec/ember-unused-components/pull/49), [#64](https://github.com/vastec/ember-unused-components/pull/64)) Bump eslint-config-prettier from `6.2.0` to `6.9.0`
- ([#63](https://github.com/vastec/ember-unused-components/pull/63)) Bump eslint-plugin-node from `10.0.0` to `11.0.0`
## 1.1.0 (September 10, 2019)

@@ -9,0 +28,0 @@

@@ -10,3 +10,2 @@ #!/usr/bin/env node

const utils = require('./lib/utils');
/**

@@ -25,2 +24,3 @@ * MAIN FUNCTION

} catch (e) {
console.log(e);
console.log(

@@ -38,6 +38,19 @@ colors.red("Can't find Ember config. Are you sure you are running this in root directory?")

console.log(colors.dim('[1/3]'), '🗺️ Mapping the project...');
analyser.mapComponents(config);
if (commandOptions.debug) {
console.log(colors.blue('indexed components:'));
console.log(analyser.components);
}
console.log(colors.dim('[2/3]'), '🔍 Looking for components usage...');
analyser.scanProject(config);
if (commandOptions.debug) {
console.log(colors.blue('scanned for occurrences in:'));
console.log(config.sourcePaths);
}
analyser.respectWhitelist(config.whitelist);

@@ -44,0 +57,0 @@

224

lib/analyser.js

@@ -5,11 +5,8 @@ 'use strict';

const fs = require('fs-extra');
const lookup = require('./lookup');
const stats = require('./stats');
const objectInfo = require('./object-info');
module.exports = {
components: [],
unusedComponents: [],
stats: {},
occurrences: {},
components: {},
example: true,

@@ -26,3 +23,3 @@

this.components.forEach(component => {
Object.values(this.components).forEach(component => {
let lookupResult = null;

@@ -37,28 +34,18 @@

if (lookupResult) {
let unusedIndex = this.unusedComponents.indexOf(component);
lookupResult.key = component.key;
if (unusedIndex !== -1) {
this.unusedComponents.splice(unusedIndex, 1);
if (lookupResult.fileType === 'js') {
component.stats.js += lookupResult.lines.length;
} else if (lookupResult.fileType === 'hbs') {
component.stats[lookupResult.type] += lookupResult.lines.length;
}
if (this.stats[component]) {
let occurrencesCount = lookupResult.lines.length;
this.stats[component].count += occurrencesCount;
if (lookupResult.fileType === 'js') {
this.stats[component].js += occurrencesCount;
} else if (lookupResult.fileType === 'hbs') {
this.stats[component][lookupResult.type] += occurrencesCount;
}
this.occurrences[component].push(
Object.assign(
{
file: filename,
},
lookupResult
)
);
}
component.stats.count += lookupResult.lines.length;
component.occurrences.push(
Object.assign(
{
file: filename,
},
lookupResult
)
);
}

@@ -107,16 +94,16 @@ });

let unusedComponents = Object.values(this.components).filter(c => c.stats.count === 0);
if (hasWildcard) {
let whitelisted = [];
componentOnWhitelist = componentOnWhitelist.replace('*', '');
this.unusedComponents.forEach(unusedComponent => {
if (unusedComponent.indexOf(componentOnWhitelist) !== -1) {
whitelisted.push(unusedComponent);
unusedComponents.forEach(unusedComponent => {
if (unusedComponent.name.indexOf(componentOnWhitelist) !== -1) {
unusedComponent.whitelisted = true;
}
});
return whitelisted;
} else {
return this.unusedComponents.includes(componentOnWhitelist);
return Object.values(this.unusedComponents).find(
component => component.name === componentOnWhitelist
);
}

@@ -126,2 +113,15 @@ },

/**
* Get unused components
*
* @returns {array} components
*/
unusedComponents() {
let values = Object.values(this.components).filter(c => {
return c.stats.count === 0 && !c.whitelisted;
});
return values;
},
/**
* Outputs the results

@@ -134,15 +134,17 @@ *

logResults(showStats, showOccurrences, whitelist) {
let percentage = (this.unusedComponents.length / this.components.length) * 100;
let unusedComponents = this.unusedComponents();
let unusedComponentCount = unusedComponents.length;
let componentCount = Object.keys(this.components).length;
let percentage = (unusedComponentCount / componentCount) * 100;
console.log('\n No. of components:', this.components.length);
console.log('\n No. of components:', componentCount);
console.log(
' No. of unused components:',
this.unusedComponents.length,
unusedComponentCount,
colors.dim(isNaN(percentage) ? '' : `(${percentage.toFixed(2)}%)`)
);
if (this.unusedComponents.length > 0) {
if (unusedComponentCount > 0) {
console.log(colors.cyan('\n Unused components:'));
this.unusedComponents.forEach(component => console.log(` - ${component}`));
unusedComponents.forEach(component => console.log(` - ${component.key}`));
} else {

@@ -153,3 +155,3 @@ console.log(colors.green('\n Congratulations! No unused components found in your project.'));

if (showStats) {
let mostUsedComponent = stats.getTheMostCommon(this.stats);
let mostUsedComponent = stats.getTheMostCommon(this.components);

@@ -159,5 +161,7 @@ if (mostUsedComponent) {

colors.cyan('\n The most used component:'),
mostUsedComponent.name,
mostUsedComponent.key,
colors.dim(
`(${mostUsedComponent.count} occurrence${mostUsedComponent.count > 1 ? 's' : ''})`
`(${mostUsedComponent.stats.count} occurrence${
mostUsedComponent.stats.count > 1 ? 's' : ''
})`
)

@@ -167,5 +171,5 @@ );

let countUsedJustOnce = stats.countUsedJustOnce(this.stats);
let countUsedJustOnce = stats.countUsedJustOnce(this.components);
if (countUsedJustOnce > 0) {
let percentageJustOnce = (countUsedJustOnce / this.components.length) * 100;
let percentageJustOnce = (countUsedJustOnce / componentCount) * 100;
console.log(

@@ -178,3 +182,3 @@ colors.cyan(' The number of components used just once:'),

let curlyVsAngle = stats.curlyVsAngle(this.stats);
let curlyVsAngle = stats.curlyVsAngle(this.components);
console.log(

@@ -193,3 +197,3 @@ colors.cyan(' Usage of {{curly-braces}} vs <AngleBrackets> syntax:'),

let componentHelpersCount = stats.countComponentHelpers(this.stats);
let componentHelpersCount = stats.countComponentHelpers(this.components);
console.log(

@@ -200,3 +204,3 @@ colors.cyan(' Usage of (component "component-name") helper in templates:'),

let countJsUsage = stats.countJsUsage(this.stats);
let countJsUsage = stats.countJsUsage(this.components);
if (countUsedJustOnce > 0) {

@@ -213,4 +217,7 @@ console.log(

for (const key of Object.keys(this.occurrences)) {
if (!this.unusedComponents.includes(key)) {
Object.keys(this.components).forEach(key => {
let component = this.components[key];
// start with the parent components
if (!component.isSubComponent && component.stats.count > 0) {
console.log(colors.cyan(`\n ${key}:`));

@@ -222,8 +229,20 @@

this.occurrences[key].forEach(o => {
component.occurrences.forEach(o => {
console.log(`\n > ${o.file}`);
o.lines.forEach(line => console.log(colors.gray(` - ${line}`)));
});
Object.keys(component.subComponentKeys).forEach(subComponentKey => {
if (this.components[subComponentKey].stats.count > 0) {
console.log(colors.cyan(`\n ${subComponentKey}:`));
let subcomponent = this.components[subComponentKey];
subcomponent.occurrences.forEach(p => {
console.log(`\n > ${p.file}`);
p.lines.forEach(line => console.log(colors.gray(` - ${line}`)));
});
}
});
}
}
});
}

@@ -235,2 +254,13 @@

/**
* Map components based on config
*
* @param {object} config
* @private
*/
mapComponents(config) {
config.componentPaths.forEach(path => this.mapComponentPath(config, './' + path));
},
/**
* Recursively search for components in given directory

@@ -242,6 +272,4 @@ *

*/
mapComponents(config, pathToCheck) {
pathToCheck = pathToCheck || `./${config.componentsPath}`;
let componentsPath = `./${config.componentsPath}/`;
mapComponentPath(config, pathToCheck) {
let files = fs.readdirSync(pathToCheck);

@@ -254,18 +282,11 @@

if (stat.isDirectory()) {
this.mapComponents(config, filename);
this.mapComponentPath(config, filename);
} else {
let recognizer = config.usePods || config.useModuleUnification ? '/component.js' : '.js';
let component = objectInfo.get(config, filename);
if (component.type == 'component') {
if (!this.components[component.key]) {
this.components[component.key] = component;
}
if (filename.includes(recognizer)) {
let componentName = filename.replace(recognizer, '').replace(componentsPath, '');
this.components.push(componentName);
this.stats[componentName] = {
name: componentName,
count: 0,
curly: 0,
angle: 0,
componentHelper: 0,
js: 0,
};
this.occurrences[componentName] = [];
this.components[component.key].filePaths.push(filename);
}

@@ -275,7 +296,15 @@ }

this.unusedComponents = this.components.slice();
// Add all the subcomponent keys to the parents
Object.values(this.components).forEach(component => {
if (component.isSubComponent) {
let parentComponent = this.components[component.parentKey];
if (parentComponent) {
parentComponent.subComponentKeys[component.key] = true;
}
}
});
},
/**
* Removes whitelisted components from unused components list
* Marks components as whitelisted for unused components list
*

@@ -287,12 +316,20 @@ * @param {array} whitelist

if (Array.isArray(whitelist)) {
whitelist.forEach(component => {
let whitelisted = this.isWhitelisted(component);
whitelist.forEach(componentOnWhitelist => {
let hasWildcard = componentOnWhitelist.indexOf('*') !== -1;
let unusedComponents = Object.values(this.components).filter(c => c.stats.count === 0);
if (whitelisted) {
let toRemove = Array.isArray(whitelisted) ? whitelisted : [component];
if (hasWildcard) {
componentOnWhitelist = componentOnWhitelist.replace('*', '');
toRemove.forEach(item => {
let unusedIndex = this.unusedComponents.indexOf(item);
this.unusedComponents.splice(unusedIndex, 1);
unusedComponents.forEach(unusedComponent => {
if (unusedComponent.key.indexOf(componentOnWhitelist) !== -1) {
unusedComponent.whitelisted = true;
}
});
} else {
let selectedComponents = Object.values(this.components).filter(
component => component.key === componentOnWhitelist
);
selectedComponents.forEach(c => (c.whitelisted = true));
}

@@ -310,7 +347,22 @@ });

*/
scanProject(config, pathToCheck) {
pathToCheck = pathToCheck || `./${config.appPath}`;
let files = fs.readdirSync(pathToCheck);
scanProject(config) {
config.sourcePaths.forEach(path => this.scanProjectPath(config, './' + path));
},
/**
* Recursively search for any file in project that could contain reference to component
*
* @param {object} config
* @param {string} [pathToCheck]
* @private
*/
scanProjectPath(config, pathToCheck) {
let files;
try {
files = fs.readdirSync(pathToCheck);
} catch (e) {
console.log(e);
}
files.forEach((item, index) => {

@@ -322,3 +374,3 @@ let filename = `${pathToCheck}/${files[index]}`;

if (stat.isDirectory()) {
this.scanProject(config, filename);
this.scanProjectPath(config, filename);
} else {

@@ -325,0 +377,0 @@ if (

@@ -57,2 +57,6 @@ 'use strict';

})
.option('include-addons', {
alias: 'a',
describe: 'include addons matching wildcard or boolean',
})
.locale('en').argv;

@@ -59,0 +63,0 @@

'use strict';
const APP_PATH = 'app/';
const APP_PATH_FOR_ADDONS = 'addon/';
const SRC_PATH = 'src/';

@@ -9,5 +10,7 @@ const EMBER_CONFIG_REL_PATH = '/config/environment.js';

const EUC_CONFIG_REL_PATH = '.eucrc.js';
const NODE_MODULE_PATH = 'node_modules';
module.exports = {
APP_PATH,
APP_PATH_FOR_ADDONS,
DEFAULT_COMPONENTS_DIR_NAME,

@@ -17,3 +20,4 @@ DEFAULT_MU_COMPONENTS_DIR_NAME,

EUC_CONFIG_REL_PATH,
NODE_MODULE_PATH,
SRC_PATH,
};

@@ -5,40 +5,2 @@ 'use strict';

/**
* Checks if file has any angle brackets invocation of given component
*
* Angle brackets components:
* - <Alert>
* - <XButton/>
* - <MiniButton></MiniButton>
* - <User::UserCard/>
*
* @param {string} data - text file
* @param {string} component - the name of component
* @returns {{regex: string, match: undefined|array }}
*/
angleBrackets(data, component) {
component = _convertToAngleBracketsName(component);
let regex = `<${component}($|\\s|\\r|/>|>)`;
return _prepareResult(data, regex);
},
/**
* Checks if file has any usage of a component via `component` helper
*
* Examples:
* -
* {{yield (hash
* generic-form=(component "contact-form")
* )}}
*
* @param {string} data - text file
* @param {string} component - the name of component
* @returns {{regex: string, match: undefined|array }}
*/
componentHelper(data, component) {
let regex = `component\\s('|")${component}('|")`;
return _prepareResult(data, regex);
},
/**
* Looks for component's occurrences in HBS file

@@ -128,3 +90,3 @@ *

curlyBraces(data, component) {
let regex = `({{|{{#)${component}($|\\s|\\r|/|}}|'|"|\`)`;
let regex = `({{|{{#)${component.name}($|\\s|\\r|/|}}|'|"|\`)`;
return _prepareResult(data, regex);

@@ -134,2 +96,40 @@ },

/**
* Checks if file has any angle brackets invocation of given component
*
* Angle brackets components:
* - <Alert>
* - <XButton/>
* - <MiniButton></MiniButton>
* - <User::UserCard/>
*
* @param {string} data - text file
* @param {string} component - the name of component
* @returns {{regex: string, match: undefined|array }}
*/
angleBrackets(data, component) {
let componentName = _convertToAngleBracketsName(component.name);
let regex = `<${componentName}($|\\s|\\r|/>|>)`;
return _prepareResult(data, regex);
},
/**
* Checks if file has any usage of a component via `component` helper
*
* Examples:
* -
* {{yield (hash
* generic-form=(component "contact-form")
* )}}
*
* @param {string} data - text file
* @param {string} component - the name of component
* @returns {{regex: string, match: undefined|array }}
*/
componentHelper(data, component) {
let regex = `component\\s('|")${component.name}('|")`;
return _prepareResult(data, regex);
},
/**
* Returns lines of component's occurrence

@@ -164,4 +164,20 @@ *

importedInJs(data, component) {
let regex = `/${component}(/component)?('|"|\`)`;
return _prepareResult(data, regex);
let ignoredResults = ['import layout', 'export {'];
let regex = `^.+${component.name}(/component)?('|"|\`)`;
let result = _prepareResult(data, regex);
if (result.match) {
let resultLine = result.match[0];
let ignoreResult = false;
ignoredResults.forEach(match => {
ignoreResult = ignoreResult || resultLine.indexOf(match) > -1;
});
if (ignoreResult) {
return { regex: regex }; // Don't return match result
}
}
return result;
},

@@ -185,3 +201,3 @@

usedAsCellComponent(data, component) {
let regex = `(cellComponent|component):\\s?('|"|\`)${component}('|"|\`)`;
let regex = `(cellComponent|component):\\s?('|"|\`)${component.name}('|"|\`)`;
return _prepareResult(data, regex);

@@ -203,4 +219,4 @@ },

*/
function _convertToAngleBracketsName(component) {
let nestedParts = component.split('/');
function _convertToAngleBracketsName(componentName) {
let nestedParts = componentName.split('/');

@@ -234,9 +250,10 @@ nestedParts = nestedParts.map(nestedPart => {

let re = new RegExp(regex, 'gi');
let match = data.match(re);
let matches = data.match(re);
let result = { regex };
if (match && match.length > 0) {
result.match = match;
if (matches && matches.length > 0) {
result.match = matches;
}
return result;

@@ -243,0 +260,0 @@ }

@@ -7,10 +7,10 @@ 'use strict';

*
* @param {object} stats
* @param {object} components
* @returns {number}
*/
countComponentHelpers(stats) {
countComponentHelpers(components) {
let count = 0;
for (const key of Object.keys(stats)) {
count += stats[key].componentHelper;
for (const key of Object.keys(components)) {
count += components[key].stats.componentHelper;
}

@@ -25,10 +25,10 @@

*
* @param {object} stats
* @param {object} components
* @returns {number}
*/
countJsUsage(stats) {
countJsUsage(components) {
let count = 0;
for (const key of Object.keys(stats)) {
count += stats[key].js;
for (const key of Object.keys(components)) {
count += components[key].stats.js;
}

@@ -42,10 +42,10 @@

*
* @param {object} stats
* @param {object} components
* @returns {number}
*/
countUsedJustOnce(stats) {
countUsedJustOnce(components) {
let count = 0;
for (const key of Object.keys(stats)) {
if (stats[key].count === 1) {
for (const key of Object.keys(components)) {
if (components[key].stats.count === 1) {
count++;

@@ -61,6 +61,6 @@ }

*
* @param {object} stats
* @param {object} components
* @returns {{curly: number, angle: number, curlyPercentage: number, anglePercentage: number}}
*/
curlyVsAngle(stats) {
curlyVsAngle(components) {
let curly = 0;

@@ -70,5 +70,5 @@ let angle = 0;

for (const key of Object.keys(stats)) {
curly += stats[key].curly;
angle += stats[key].angle;
for (const key of Object.keys(components)) {
curly += components[key].stats.curly;
angle += components[key].stats.angle;
}

@@ -91,11 +91,11 @@

*
* @param {object} stats
* @param {object} components
* @returns {object|null}
*/
getTheMostCommon(stats) {
getTheMostCommon(components) {
let max = null;
for (const key of Object.keys(stats)) {
if (!max || max.count < stats[key].count) {
max = stats[key];
for (const key of Object.keys(components)) {
if (!max || max.stats.count < components[key].stats.count) {
max = components[key];
}

@@ -102,0 +102,0 @@ }

@@ -5,2 +5,4 @@ 'use strict';

const fs = require('fs-extra');
const glob = require('glob');
const path = require('path');

@@ -26,46 +28,42 @@ module.exports = {

let emberConfigFile;
emberConfigFile = _getEmberConfigFile(commandOptions);
let appPath = commandOptions.path + constants.APP_PATH;
this.config = {
appPath: appPath,
sourcePaths: [],
projectRoot: commandOptions.path,
failOnUnused: !!commandOptions.failOnUnused,
ignore: [],
usePods:
commandOptions.pods ||
typeof emberConfigFile.podModulePrefix === 'string' ||
_podsGuess(appPath),
useModuleUnification: !!(
emberConfigFile.EmberENV &&
emberConfigFile.EmberENV.FEATURES &&
emberConfigFile.EmberENV.FEATURES.EMBER_MODULE_UNIFICATION
),
includeAddons: !!commandOptions.includeAddons,
whitelist: [],
isAddon: _isAddon(commandOptions.path),
componentPaths: [],
};
// Initial value, more will be added below for PODs and classical structure
this.config.componentsPath = this.config.appPath;
// todo get /app /addon or /src for searching
this.config.sourcePaths = this.getSourcePaths(this.config.projectRoot, commandOptions);
// Specify `componentsPath` based on config
if (this.config.useModuleUnification) {
this.config.appPath = commandOptions.path + constants.SRC_PATH;
this.config.componentsPath = this.config.appPath + constants.DEFAULT_MU_COMPONENTS_DIR_NAME;
} else if (this.config.usePods) {
if (commandOptions.podsDir) {
this.config.componentsPath += commandOptions.podsDir;
} else {
let podModule =
typeof emberConfigFile.podModulePrefix === 'string'
? emberConfigFile.podModulePrefix.replace(emberConfigFile.modulePrefix + '/', '') + '/'
: '';
let componentPaths = [];
componentPaths.push(...this.getComponentPaths(this.config.projectRoot, commandOptions));
// componentPaths.push(...this.config.sourcePaths);
this.config.componentsPath += podModule + constants.DEFAULT_COMPONENTS_DIR_NAME;
if (this.config.includeAddons) {
this.config.filterAddonsBy = '*';
if (typeof commandOptions.includeAddons == 'string') {
this.config.filterAddonsBy = commandOptions.includeAddons;
}
} else {
this.config.componentsPath += constants.DEFAULT_COMPONENTS_DIR_NAME;
let addonPaths = this.getAddonPaths(
this.config.projectRoot,
commandOptions,
this.config.filterAddonsBy
);
this.config.addonPaths = addonPaths;
for (let addonPath of addonPaths) {
componentPaths.push(
...this.getComponentPaths(path.join(this.config.projectRoot, addonPath), commandOptions)
);
}
}
this.config.componentPaths = componentPaths;
// Look for script's config

@@ -93,4 +91,57 @@ let eucConfig = _getEUCConfigFile(commandOptions);

if (commandOptions.debug) {
console.log(this.config);
}
return this.config;
},
getSourcePaths: function(rootPath) {
let componentPaths = [];
if (_isAddon(rootPath)) {
componentPaths = ['src', 'addon'];
} else {
componentPaths = ['src', 'app'];
}
let paths = componentPaths.map(s => path.join(rootPath, s));
return paths.filter(p => fs.existsSync(path.join(process.cwd(), p)));
},
getComponentPaths: function(rootPath, commandOptions) {
let podModulePrefix = _getPodModulePrefix(this.config.projectRoot, commandOptions, this.config);
let componentPathsuffixes = [
'app/components',
'app/templates/components',
'src/ui/components',
'addon/components',
'addon/templates/components',
];
if (podModulePrefix) {
componentPathsuffixes.push(`app/${podModulePrefix}/${constants.DEFAULT_COMPONENTS_DIR_NAME}`);
}
let paths = componentPathsuffixes.map(s => path.join(rootPath, s));
return paths.filter(p => fs.existsSync(path.join(process.cwd(), p)));
},
getAddonPaths: function(rootPath, commandOptions, filterAddonsBy) {
let pathPrefix = path.join(process.cwd(), rootPath);
let searchString = path.join(
pathPrefix,
`{node_modules,packages,lib}/${filterAddonsBy}/{addon,ember-cli-build.js}`
);
let files = glob.sync(searchString, {
root: commandOptions.projectRoot,
});
let addonPaths = files
.map(f => path.normalize(f))
.map(f => path.dirname(f.replace(pathPrefix, '/')));
return [...new Set(addonPaths)]; // return unique paths;
},
};

@@ -106,4 +157,4 @@

*/
function _getEmberConfigFile(commandOptions) {
const emberConfigRelPath = commandOptions.path + constants.EMBER_CONFIG_REL_PATH;
function _getEmberConfigFile(projectPath) {
const emberConfigRelPath = path.join(projectPath, constants.EMBER_CONFIG_REL_PATH);
let emberConfigPath = process.cwd() + emberConfigRelPath;

@@ -138,60 +189,35 @@ let emberConfigFn = require(emberConfigPath);

/**
* Tries to guess if POD structure is in use.
*
* When we read `environment.js` and see `podModulePrefix` property then we are sure that
* POD structure is in use. There is no other way to guess that by reading ember files.
* User (developer) can use `--pods` argument when calling `ember-unused-components` script which
* informs us about structure but for dev's ergonomics we try our best to guess.
*
* Guessing algorithm:
* - go to `app/components`
* - search through all directories there
* - check if all directories have `component.js` or `template.hbs` file
* - if at least one doesn't have aforementioned files then it's not PODs
*
* If so, then we guess this is a POD structure.
*
* @param {string} appPath - path to application
* @returns {boolean}
* @private
*/
function _podsGuess(appPath) {
let path = './' + appPath + constants.DEFAULT_COMPONENTS_DIR_NAME + '/';
let hasAllComponentsAsPODs = true;
Searches for ember addons based on config flag
try {
let files = fs.readdirSync(path);
* @param {path} path to project directory,
* @param {object} commandOptions - arguments passed when script was executed
* @param {object} config - config object
* @returns {string|null}
* @private
*/
files.forEach((item, index) => {
let filename = `${path}/${files[index]}`;
filename = filename.replace(/\/\//gi, '/');
let stat = fs.lstatSync(filename);
function _getPodModulePrefix(path, commandOptions, config) {
if (commandOptions.podsDir && path === config.projectRoot) {
// podDir option only valid for root project
return commandOptions.podsDir;
}
// This could be an addon config file, or the root project config
let configFile = _getEmberConfigFile(path);
if (stat.isDirectory()) {
/*
One level deep check if all of directories of `app/components` have `component.js` or `template.hbs`
*/
let dirPath = filename;
let dirPathFiles = fs.readdirSync(dirPath);
if (typeof configFile.podModulePrefix === 'string') {
return configFile.podModulePrefix.replace(configFile.modulePrefix + '/', '');
}
}
dirPathFiles.forEach((dirItem, dirIndex) => {
let dirPathFilename = `${dirPath}/${dirPathFiles[dirIndex]}`;
dirPathFilename = dirPathFilename.replace(/\/\//gi, '/');
let dirStat = fs.lstatSync(dirPathFilename);
/**
Determines whether root project is an addon. If so, there are slightly different
paths to check
if (
!dirStat.isDirectory() &&
!dirPathFilename.includes('template.hbs') &&
!dirPathFilename.includes('component.js')
) {
hasAllComponentsAsPODs = false;
}
});
}
});
return hasAllComponentsAsPODs;
} catch (e) {
return false;
}
* @param {object} commandOptions - arguments passed when script was executed
* @param {boolean} [commandOptions.path=''] - path to root directory of a project
* @returns {object|null}
* @private
*/
function _isAddon(rootPath) {
return fs.existsSync(path.join(process.cwd(), rootPath, 'addon'));
}
{
"name": "ember-unused-components",
"version": "1.1.0",
"version": "1.2.0",
"description": "Search for unused components in your Ember project",

@@ -29,5 +29,6 @@ "keywords": [

"dependencies": {
"colors": "1.3.3",
"colors": "1.4.0",
"fs-extra": "8.1.0",
"yargs": "^14.0.0"
"glob": "^7.1.5",
"yargs": "^15.1.0"
},

@@ -38,3 +39,3 @@ "devDependencies": {

"eslint-config-prettier": "^6.1.0",
"eslint-plugin-node": "^10.0.0",
"eslint-plugin-node": "^11.0.0",
"eslint-plugin-prettier": "^3.0.1",

@@ -41,0 +42,0 @@ "prettier": "^1.18.2"

@@ -16,4 +16,6 @@ [![npm version](https://badge.fury.io/js/ember-unused-components.svg)](https://badge.fury.io/js/ember-unused-components)

- ignoring files,
- and whitelisting components unused temporary.
- whitelisting components unused temporary,
- addons,
- and components being used in addons with `--includeAddons` option.
It also has a very interesting statistics module.

@@ -110,2 +112,23 @@

#### [EXPERIMENTAL] Searching components contained in other packages
You can also print all occurrences of components that were found in included addons. Use `--includeAddons` to include all found addons, or `includeAddons=company-*` to only include addons that match `company-*`
```bash
$ npx ember-unused-components --occurrences --includeAddons=company-*
// simplified
[company-buttons] button-a:
> ./app/templates/components/user-card.hbs
- <ButtonA>Button Text</ButtonA>
welcome-page:
> ./app/templates/application.hbs
- {{welcome-page}}
```
### Advanced usage

@@ -125,3 +148,3 @@

The script will use the default directory of POD components: `app/components`. **Please let me know** if you had to force using POD. I made a simple guessing algorithm that should handle PODs out-of-the-box.
The script will use the default directory of POD components: `app/components`. **Please let me know** if you had to force using POD. I made a simple guessing algorithm that should handle PODs out-of-the-box.

@@ -128,0 +151,0 @@ #### Forcing POD with the custom directory