Skip to content

Commit

Permalink
refactor: removed dependent-usage-analyzer from eslintignore
Browse files Browse the repository at this point in the history
  • Loading branch information
PKulkoRaccoonGang committed Dec 29, 2023
1 parent 86fa2a9 commit 9cc3611
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 184 deletions.
1 change: 0 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ dist/
node_modules/
www/
icons/
dependent-usage-analyzer/
build-scss.js
component-generator/
example/
2 changes: 1 addition & 1 deletion dependent-usage-analyzer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ program
const analysis = {
lastModified: Date.now(),
projectUsages: analyzedProjects,
}
};
fs.writeFileSync(outputFilePath, JSON.stringify(analysis, null, 2));
console.log(`Analyzed ${projectDirectories.length} projects:`);
console.log(analysis);
Expand Down
22 changes: 11 additions & 11 deletions dependent-usage-analyzer/tools/analyzeProject.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,19 @@ const { getPackageInfo, getProjectFiles, getComponentUsagesInFiles } = require('
* including package details, component usages, and Paragon version associated with each usage.
*/
function analyzeProject(dir, options = {}) {
const packageInfo = getPackageInfo(dir, options);
const files = getProjectFiles(dir);
const usages = getComponentUsagesInFiles(files, dir);
const packageInfo = getPackageInfo(dir, options);
const files = getProjectFiles(dir);
const usages = getComponentUsagesInFiles(files, dir);

// Add Paragon version to each component usage
Object.keys(usages).forEach(componentName => {
usages[componentName].usages = usages[componentName].map(usage => ({
...usage,
version: packageInfo.version,
}));
});
// Add Paragon version to each component usage
Object.keys(usages).forEach(componentName => {
usages[componentName].usages = usages[componentName].map(usage => ({
...usage,
version: packageInfo.version,
}));
});

return { ...packageInfo, usages };
return { ...packageInfo, usages };
}

module.exports = { analyzeProject };
27 changes: 13 additions & 14 deletions dependent-usage-analyzer/tools/findProjectsToAnalyze.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,25 @@ const glob = require('glob');
* @returns {Array.<string>} - An array of directory paths containing projects with Paragon as a dependency.
*/
function findProjectsToAnalyze(dir) {
// Find all directories containing a package.json file.
const packageJSONFiles = glob.sync(`${dir}/**/package.json`, { ignore: [`${dir}/**/node_modules/**`] });
// Find all directories containing a package.json file.
const packageJSONFiles = glob.sync(`${dir}/**/package.json`, { ignore: [`${dir}/**/node_modules/**`] });

// If paragon isn't included in the package.json file then skip analyzing it
const packageJSONFilesWithParagon = packageJSONFiles.filter(packageJSONFile => {
const { dependencies, peerDependencies } = JSON.parse(fs.readFileSync(packageJSONFile, { encoding: 'utf-8' }));
// If paragon isn't included in the package.json file then skip analyzing it
const packageJSONFilesWithParagon = packageJSONFiles.filter(packageJSONFile => {
const { dependencies, peerDependencies } = JSON.parse(fs.readFileSync(packageJSONFile, { encoding: 'utf-8' }));

const hasDependency = (depsObject, orgs) => {
return orgs.some(org => depsObject && depsObject[`${org}/paragon`] !== undefined);
};
const hasDependency = (depsObject, orgs) => orgs.some(org => depsObject && depsObject[`${org}/paragon`] !== undefined);

const hasDirectDependency = hasDependency(dependencies, ['@edx', '@openedx']);
const hasPeerDependency = hasDependency(peerDependencies, ['@edx', '@openedx']);
const hasDirectDependency = hasDependency(dependencies, ['@edx', '@openedx']);
const hasPeerDependency = hasDependency(peerDependencies, ['@edx', '@openedx']);

return hasDirectDependency || hasPeerDependency;
});
return hasDirectDependency || hasPeerDependency;
});

console.log(packageJSONFilesWithParagon)
// eslint-disable-next-line no-console
console.log(packageJSONFilesWithParagon);

return packageJSONFilesWithParagon.map(packageJSONFile => path.dirname(packageJSONFile));
return packageJSONFilesWithParagon.map(packageJSONFile => path.dirname(packageJSONFile));
}

module.exports = { findProjectsToAnalyze };
184 changes: 93 additions & 91 deletions dependent-usage-analyzer/utils/getComponentUsagesInFiles.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable no-prototype-builtins */
const fs = require('fs');
const walk = require('babel-walk');
const parser = require('@babel/parser');
Expand All @@ -9,107 +10,108 @@ const parser = require('@babel/parser');
* @returns {Object} - An object containing component usage information, organized by component name.
*/
function getComponentUsagesInFiles(files, rootDir) {
// Save the file and line location of components for all files
return files.reduce((usagesAccumulator, filePath) => {
const sourceCode = fs.readFileSync(filePath, { encoding: 'utf-8' });
let ast;
// Save the file and line location of components for all files
return files.reduce((usagesAccumulator, filePath) => {
const sourceCode = fs.readFileSync(filePath, { encoding: 'utf-8' });
let ast;

try {
ast = parser.parse(sourceCode, { sourceType: 'module', plugins: ['jsx', 'classProperties'] });
} catch (e) {
console.error(`There was an error parsing a file into an abstract syntax tree. Skipping file: ${filePath}`);
return usagesAccumulator;
}
try {
ast = parser.parse(sourceCode, { sourceType: 'module', plugins: ['jsx', 'classProperties'] });
} catch (e) {
// eslint-disable-next-line no-console
console.error(`There was an error parsing a file into an abstract syntax tree. Skipping file: ${filePath}`);
return usagesAccumulator;
}

// Track the local names of imported paragon components
const paragonImportsInFile = {};
const addParagonImport = (specifierNode) => {
const { local, imported } = specifierNode;
paragonImportsInFile[local.name] = imported ? imported.name : local.name;
};
// Track the local names of imported paragon components
const paragonImportsInFile = {};
const addParagonImport = (specifierNode) => {
const { local, imported } = specifierNode;
paragonImportsInFile[local.name] = imported ? imported.name : local.name;
};

const addComponentUsage = (fullComponentName, startLocation) => {
if (!usagesAccumulator[fullComponentName]) {
usagesAccumulator[fullComponentName] = [];
}
usagesAccumulator[fullComponentName].push({
filePath: filePath.substring(rootDir.length + 1),
...startLocation,
});
};
const addComponentUsage = (fullComponentName, startLocation) => {
if (!usagesAccumulator[fullComponentName]) {
usagesAccumulator[fullComponentName] = [];
}
usagesAccumulator[fullComponentName].push({
filePath: filePath.substring(rootDir.length + 1),
...startLocation,
});
};

// Walk the abstract syntax tree of the file looking for paragon imports and component usages
walk.simple({
// ImportDeclaration nodes contains data about imports in the files
ImportDeclaration(node) {
const allowedPackages = ['@edx/paragon', '@edx/paragon/icons', '@openedx/paragon', '@openedx/paragon/icons'];
// Ignore direct imports for now
if (allowedPackages.includes(node.source.value)) {
node.specifiers.forEach(addParagonImport);
}
},
// JSXOpeningElement nodes contains data about each JSX element in the file.
// where Paragon component can be found through node.name.object and node.name.property.name for subcomponents
// Example: `<Alert variant="danger">Some alert</Alert>`
JSXOpeningElement(node) {
const componentName = node.name.object ? node.name.object.name : node.name.name;
const isParagonComponent = componentName in paragonImportsInFile;
// Walk the abstract syntax tree of the file looking for paragon imports and component usages
walk.simple({
// ImportDeclaration nodes contains data about imports in the files
ImportDeclaration(node) {
const allowedPackages = ['@edx/paragon', '@edx/paragon/icons', '@openedx/paragon', '@openedx/paragon/icons'];
// Ignore direct imports for now
if (allowedPackages.includes(node.source.value)) {
node.specifiers.forEach(addParagonImport);
}
},
// JSXOpeningElement nodes contains data about each JSX element in the file.
// where Paragon component can be found through node.name.object and node.name.property.name for subcomponents
// Example: `<Alert variant="danger">Some alert</Alert>`
JSXOpeningElement(node) {
const componentName = node.name.object ? node.name.object.name : node.name.name;
const isParagonComponent = componentName in paragonImportsInFile;

if (isParagonComponent) {
const paragonName = paragonImportsInFile[componentName];
const subComponentName = node.name.object ? node.name.property.name : null;
const fullComponentName = subComponentName ? `${paragonName}.${subComponentName}` : paragonName;
addComponentUsage(fullComponentName, node.loc.start);
}
},
// JSXExpressionContainer nodes contains data about each JSX props expressions in the file.
// where Paragon component can be found through node.expression.name
// Example: `<Icon src={Add} />`
JSXExpressionContainer(node) {
const componentName = node.expression.name;
const isParagonComponent = paragonImportsInFile.hasOwnProperty(componentName);
if (isParagonComponent) {
const paragonName = paragonImportsInFile[componentName];
const subComponentName = node.name.object ? node.name.property.name : null;
const fullComponentName = subComponentName ? `${paragonName}.${subComponentName}` : paragonName;
addComponentUsage(fullComponentName, node.loc.start);
}
},
// JSXExpressionContainer nodes contains data about each JSX props expressions in the file.
// where Paragon component can be found through node.expression.name
// Example: `<Icon src={Add} />`
JSXExpressionContainer(node) {
const componentName = node.expression.name;
const isParagonComponent = paragonImportsInFile.hasOwnProperty(componentName);

if (isParagonComponent) {
addComponentUsage(componentName, node.expression.loc.start);
}
},
// AssignmentExpression contains data about each assignment in the file,
// where Paragon components, hooks and utils can be found through node.name.object
// Example: `const alert = Alert;` will go here
AssignmentExpression(node) {
const componentName = node.right.name;
const isParagonComponent = paragonImportsInFile.hasOwnProperty(componentName);
if (isParagonComponent) {
addComponentUsage(componentName, node.expression.loc.start);
}
},
// AssignmentExpression contains data about each assignment in the file,
// where Paragon components, hooks and utils can be found through node.name.object
// Example: `const alert = Alert;` will go here
AssignmentExpression(node) {
const componentName = node.right.name;
const isParagonComponent = paragonImportsInFile.hasOwnProperty(componentName);

if (isParagonComponent) {
addComponentUsage(componentName, node.loc.start);
}
},
// CallExpression contains data about each function call in the file,
// where Paragon hooks and functions can be found usage through node.callee.
// Example: `const myVar = useWindowSize();` will go here
CallExpression(node) {
const componentName = node.callee.name;
const isParagonComponent = paragonImportsInFile.hasOwnProperty(componentName);
if (isParagonComponent) {
addComponentUsage(componentName, node.loc.start);
}
},
// CallExpression contains data about each function call in the file,
// where Paragon hooks and functions can be found usage through node.callee.
// Example: `const myVar = useWindowSize();` will go here
CallExpression(node) {
const componentName = node.callee.name;
const isParagonComponent = paragonImportsInFile.hasOwnProperty(componentName);

if (isParagonComponent) {
addComponentUsage(componentName, node.loc.start);
}
},
// MemberExpression contains data about complex expressions,
// where Paragon components, hooks and utils can be found node.object.
// Example: `const myVar = isVertical ? Button : ActionRow;` will go here
MemberExpression(node) {
const componentName = node.object.name;
const isParagonComponent = paragonImportsInFile.hasOwnProperty(componentName);
if (isParagonComponent) {
addComponentUsage(componentName, node.loc.start);
}
},
// MemberExpression contains data about complex expressions,
// where Paragon components, hooks and utils can be found node.object.
// Example: `const myVar = isVertical ? Button : ActionRow;` will go here
MemberExpression(node) {
const componentName = node.object.name;
const isParagonComponent = paragonImportsInFile.hasOwnProperty(componentName);

if (isParagonComponent) {
addComponentUsage(componentName, node.loc.start);
}
}
})(ast);
if (isParagonComponent) {
addComponentUsage(componentName, node.loc.start);
}
},
})(ast);

return usagesAccumulator;
}, {});
return usagesAccumulator;
}, {});
}

module.exports = { getComponentUsagesInFiles };
87 changes: 44 additions & 43 deletions dependent-usage-analyzer/utils/getDependencyVersion.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,57 +14,58 @@ const fs = require('fs');
* @returns String representing direct or peer Paragon dependency version
*/
function getDependencyVersion(dir, options = {}) {
// package-lock.json contains the actual Paragon version
// rather than a range in package.json.
const packageFilename = 'package-lock.json';
const { projectsDir } = options;
// package-lock.json contains the actual Paragon version
// rather than a range in package.json.
const packageFilename = 'package-lock.json';
const { projectsDir } = options;

if (dir === projectsDir) {
// At the top-level directory containing all projects; Paragon version not found.
return "";
}
if (dir === projectsDir) {
// At the top-level directory containing all projects; Paragon version not found.
return '';
}

const parentDir = dir.split('/').slice(0, -1).join('/');
const parentDir = dir.split('/').slice(0, -1).join('/');

if (!fs.existsSync(`${dir}/${packageFilename}`)) {
// No package-lock.json file exists, so try traversing up the tree until
// reaching the top-level ``projectsDir``.
return getDependencyVersion(parentDir, options);
}
if (!fs.existsSync(`${dir}/${packageFilename}`)) {
// No package-lock.json file exists, so try traversing up the tree until
// reaching the top-level ``projectsDir``.
return getDependencyVersion(parentDir, options);
}

const {
packages,
dependencies,
peerDependencies
} = JSON.parse(fs.readFileSync(`${dir}/${packageFilename}`, { encoding: 'utf-8' }));
const {
packages,
dependencies,
peerDependencies,
} = JSON.parse(fs.readFileSync(`${dir}/${packageFilename}`, { encoding: 'utf-8' }));

const getVersion = (depsObjectName, org = '@edx') => {
switch (depsObjectName) {
case 'packages':
return packages && packages[`node_modules/${org}/paragon`]?.version;
case 'dependencies':
return dependencies && dependencies[`${org}/paragon`]?.version;
case 'peerDependencies':
return peerDependencies && peerDependencies[`${org}/paragon`]?.version;
default:
console.error(`Unexpected organization: ${org} or dependence object name: ${depsObjectName}`);
return undefined;
}
};
const getVersion = (depsObjectName, org = '@edx') => {
switch (depsObjectName) {
case 'packages':
return packages && packages[`node_modules/${org}/paragon`]?.version;
case 'dependencies':
return dependencies && dependencies[`${org}/paragon`]?.version;
case 'peerDependencies':
return peerDependencies && peerDependencies[`${org}/paragon`]?.version;
default:
// eslint-disable-next-line no-console
console.error(`Unexpected organization: ${org} or dependence object name: ${depsObjectName}`);
return undefined;
}
};

// first handle lockfileVersion 3 that contains all dependencies data in 'packages' key
const packagesDependencyVersion = getVersion('packages') || getVersion('packages', '@openedx');
const directDependencyVersion = getVersion('dependencies') || getVersion('dependencies', '@openedx');
const peerDependencyVersion = getVersion('peerDependencies') || getVersion('peerDependencies', '@openedx');
const resolvedVersion = packagesDependencyVersion || directDependencyVersion || peerDependencyVersion;
// first handle lockfileVersion 3 that contains all dependencies data in 'packages' key
const packagesDependencyVersion = getVersion('packages') || getVersion('packages', '@openedx');
const directDependencyVersion = getVersion('dependencies') || getVersion('dependencies', '@openedx');
const peerDependencyVersion = getVersion('peerDependencies') || getVersion('peerDependencies', '@openedx');
const resolvedVersion = packagesDependencyVersion || directDependencyVersion || peerDependencyVersion;

if (resolvedVersion) {
return resolvedVersion;
}
if (resolvedVersion) {
return resolvedVersion;
}

// No Paragon dependency exists, so try traversing up the tree until
// reaching the top-level ``projectsDir``.
return getDependencyVersion(parentDir, options)
// No Paragon dependency exists, so try traversing up the tree until
// reaching the top-level ``projectsDir``.
return getDependencyVersion(parentDir, options);
}

module.exports = { getDependencyVersion };
Loading

0 comments on commit 9cc3611

Please sign in to comment.