Skip to content

Commit

Permalink
- Added Kotlin support
Browse files Browse the repository at this point in the history
- Improved performances
  • Loading branch information
Davide Cirillo committed Dec 26, 2017
1 parent 84688eb commit ed27732
Show file tree
Hide file tree
Showing 110 changed files with 4,170 additions and 119 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
@@ -0,0 +1,3 @@
{
"java.configuration.updateBuildConfiguration": "disabled"
}
12 changes: 6 additions & 6 deletions README.md
Expand Up @@ -30,13 +30,13 @@ Those are the features that are currently supported and used to build the graph,

| Feature | Java | Kotlin |
| ----------------------|:-------------:|:-------:|
| @Module || 🚧 |
| @Component || 🚧 |
| @SubComponent || 🚧 |
| @Provides || 🚧 |
| Field @Inject || 🚧 |
| @Module || |
| @Component || |
| @SubComponent || |
| @Provides || |
| Field @Inject || |
| Constructor @Inject |||
| @Named() | | |
| @Named() | | |
| @Binds |||
| Component dependencies|||
| Extended modules |||
Expand Down
15 changes: 4 additions & 11 deletions cli.js
Expand Up @@ -10,19 +10,12 @@ updateNotifier({ pkg }).notify();

const cli = meow(`
Usage
$ daggraph <command> <params>
$ daggraph sample <param> # Uses the <PARAM>
$ daggraph other <param> # Other the <PARAM>
$ daggraph another <param> # Another the <PARAM>
Examples
$ daggraph <path> # Uses the <path>
Examples
$ daggraph sample TEST # Uses the TEST
$ daggraph sample YOLO # Uses the YOLO
$ daggraph other YOLO # Uses the YOLO
$ daggraph another YOLO # Uses the YOLO
$ daggraph /Path/to/android/project # Uses the TEST
`,
{
alias: {
Expand Down
5 changes: 0 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Expand Up @@ -9,7 +9,7 @@
},
"scripts": {
"start": "node cli.js",
"test": "node test.js"
"test": "ava --timeout=2m"
},
"bin": {
"daggraph": "cli.js"
Expand All @@ -25,11 +25,11 @@
"node-time-log": "^1.0.3",
"open": "0.0.5",
"opn": "^5.1.0",
"regularity": "^0.5.1",
"string-search": "^1.2.0",
"update-notifier": "^2.3.0"
},
"devDependencies": {
"ava": "^0.24.0",
"eslint": "^4.12.0",
"xo": "^0.18.2"
},
Expand Down
99 changes: 68 additions & 31 deletions src/dagger/DaggerAnalyzer.js
Expand Up @@ -11,85 +11,113 @@ const DComponent = require('./models/DComponent.js');
* @param {*Path of the android project} projectRootPath
*/
function findComponents(projectRootPath){
return new Promise((resolve, reject) => {
console.log('Analyzing dagger components and modules..');

const searchCriteria = FILE_HOUND.create()
.paths(projectRootPath)
.discard("build")
.depth(20)
.ignoreHiddenDirectories()
.ignoreHiddenFiles()
.ext('java');

searchModules(searchCriteria)
.then(modules => findAndAddInjections(modules, searchCriteria))
.then(modules => searchComponents(modules, searchCriteria))
.then(components => resolve(components))
.catch(e => reject(e));
});
console.log('Analyzing dagger components and modules..');

const searchCriteria = FILE_HOUND.create()
.paths(projectRootPath)
.discard("build")
.depth(20)
.ignoreHiddenDirectories()
.ignoreHiddenFiles()
.ext('.java', '.kt');

return searchModules(searchCriteria).then(modules => searchComponents(modules, searchCriteria));
}

function searchModules(searchCriteria){
return new Promise((resolve, reject) => {
const daggerModules = [];
const analyzed = [];
const fileSniffer = FILE_SNIFFER.create(searchCriteria);

fileSniffer.on("match", (path) => {
if (analyzed.includes(path)) return;
analyzed.push(path);

var module = new DModule();
module.init(path);
daggerModules.push(module);
});
fileSniffer.on("end", (files) => resolve(daggerModules));
fileSniffer.on("error", reject);
fileSniffer.on("end", (files) => {
resolve(findAndAddInjections(daggerModules, searchCriteria));
});
fileSniffer.on("error", (e) => {
reject("Error while searching for modules "+e);
});
fileSniffer.find("@Module");
});
}

function searchComponents(modules, searchCriteria){
return new Promise((resolve, reject) => {
const daggerComponents = [];

const daggerComponents = [];
const analyzed = [];
const fileSniffer = FILE_SNIFFER.create(searchCriteria);

fileSniffer.on('match', (path) => {
if (analyzed.includes(path)) return;
analyzed.push(path);

const component = new DComponent();
component.init(path, modules);
daggerComponents.push(component);
});
fileSniffer.on("end", (files) => resolve(daggerComponents));
fileSniffer.on("error", reject);
fileSniffer.on("end", (files) => {
resolve(daggerComponents);
});
fileSniffer.on("error", (e) => {
reject("Error while searching for components " + e);
});
fileSniffer.find(/@Component|@Subcomponent/);
});
}

function findAndAddInjections(modules, searchCriteria){
return new Promise((resolve, reject) => {

const injectionPathMap = [];
const injectRegex = /@Inject(?:\n|.)*?\s+(?:protected|public)*\s*(\w*)/g;

// Find all the field injections for kotline and java (group 1 java only, group 2 kotlin only)
const injectRegex = /(?:(?:@Inject(?:\n|.)*?\s+(?:protected|public|lateinit|(\w+(?:\.\w+)*))?\s+(?:var(?:\n|.)*?:\s*)?)|(?:@field\s*:\s*\[(?:\n|.)*?Inject(?:\n|.)*?\]\s*(?:protected|public|lateinit)?\s*var\s*.+?\s*:\s*))(\w+(?:\.\w+)*)/g;
const namedRegex = /@*Named\(\"(\w*)\"\)/;
const fileSniffer = FILE_SNIFFER.create(searchCriteria);

fileSniffer.on('match', (path) => {
// Open file
const file = FS.readFileSync(path, 'utf8');
// Find injections
while ((fullMatch = injectRegex.exec(file)) !== null) {
// For each injections found, save the path in the array corresponding to the right dependency
var depName;
var depIdentifier;

// Name Could be at 1 or 3
if (fullMatch[1] !== undefined && fullMatch[1] !== null) depName = fullMatch[1];
else depName = fullMatch[2];

// Look for @Named in the full matcher and add it to the dep identifier
const namedMatch = namedRegex.exec(fullMatch[0]);
if(namedMatch !== null){
depIdentifier = createDependencyIdentifier(depName, namedMatch[1]);
}else{
depIdentifier = depName;
}

// If the array of paths for that dep is not initialised, init
if (injectionPathMap[fullMatch[1]] === undefined) injectionPathMap[fullMatch[1]] = [];
if (injectionPathMap[depIdentifier] === undefined) injectionPathMap[depIdentifier] = [];

// If the path is not already in the list, add it
if (!injectionPathMap[fullMatch[1]].includes(path)){
injectionPathMap[fullMatch[1]].push(path);
if (!injectionPathMap[depIdentifier].includes(path)){
injectionPathMap[depIdentifier].push(path);
}
}
});
fileSniffer.on("end", (files) => {
addInjectionsToModules(injectionPathMap, modules);
resolve(modules);
});
fileSniffer.on("error", reject);
fileSniffer.on("error", (e) => {
reject("Error while searching for injections " + e);
});
fileSniffer.find(/@Inject/i);

});
Expand All @@ -98,14 +126,23 @@ function searchModules(searchCriteria){
function addInjectionsToModules(injectionPathMap, modules){
modules.forEach(module => {
module.dependencies.forEach(dep => {
// Define the identifier base on the name and the named parameter if present
var depIndentifier = createDependencyIdentifier(dep.name, dep.named);

// If i have some injections for that dependency in the map, add them
if(injectionPathMap[dep.name] !== undefined){
injectionPathMap[dep.name].forEach(path => {
if(injectionPathMap[depIndentifier] !== undefined){
injectionPathMap[depIndentifier].forEach(path => {
dep.addInjectionPath(path);
});
}
});
});
}

function createDependencyIdentifier(depName, depNamed){
var depIndentifier = depName;
if (depNamed !== undefined && depNamed !== null) depIndentifier = depIndentifier + "**" + depNamed;
return depIndentifier;
}

exports.findComponents = findComponents;
37 changes: 20 additions & 17 deletions src/dagger/models/DComponent.js
@@ -1,6 +1,6 @@
const FS = require('fs');
const Regularity = require('regularity');
const Utils = require('./../../utils/utils');
const DModule = require('./DModule');

function DComponent(){
this.modules = [];
Expand All @@ -16,9 +16,8 @@ DComponent.prototype.init = function(path, allModules){
};

function getModules(file, allModules){
// Load modules
var regularity = new Regularity();
var modulesRegex = /(\w+)\.class,*/g;
// Find the modules in the components for java or kotlin
var modulesRegex = /(\w+)(?:.|::)class,*/g;

// For each module specified in the component, try to find it in the loaded modules
var moduleMatches = file.match(modulesRegex);
Expand All @@ -29,11 +28,20 @@ function getModules(file, allModules){
while ((array = modulesRegex.exec(element)) !== null) {

// If the model name in the component matches one of the modules that we have loaded, then add it to the component
allModules.forEach(module => {
if (array[1] === module.name) {
result.push(module);
var loadedModule;
allModules.forEach(m => {
if (array[1] === m.name) {
loadedModule = m;
// TODO break here
}
});
// If we don't have the module loaded, fallback creating a new one with just that name
if (loadedModule === undefined){
loadedModule = new DModule();
loadedModule.name = array[1];
}

result.push(loadedModule);
}
});
}
Expand All @@ -43,20 +51,15 @@ function getModules(file, allModules){
function getInjections(file){
var result = [];

// Load injections
var regularity = new Regularity();
var injectionsRegex = regularity
.then("inject(")
.oneOrMore("alphanumeric")
.global()
.multiline()
.done();
// Find all the injections in this component for java or kotlin
var injectionsRegex = /(?:void|fun)\s*inject\s*\((?:\w+:)?(?:\s*)?(\w*)/g;

var matches = file.match(injectionsRegex);
if (matches != null) {
matches.forEach(element => {
var injection = element.split('(')[1];
result.push(injection);
while ((array = injectionsRegex.exec(element)) !== null) {
result.push(array[1]);
}
});
}
return result;
Expand Down
4 changes: 4 additions & 0 deletions src/dagger/models/DDependency.js
Expand Up @@ -12,4 +12,8 @@ DDependency.prototype.addInjectionPath = function (injectionPath) {
this.injectionPaths.push(injectionPath);
}

DDependency.prototype.addNamed = function (n) {
this.named = n;
}

module.exports = DDependency;
44 changes: 33 additions & 11 deletions src/dagger/models/DModule.js
@@ -1,5 +1,4 @@
const FS = require('fs');
const Regularity = require('regularity');
const DDependency = require('./DDependency.js');
const Utils = require('./../../utils/utils');

Expand All @@ -14,25 +13,48 @@ DModule.prototype.init = function(filePath){
this.dependencies = getProvidedDependencies(filePath);
};

function getProvidedDependencies(path){
function getProvidedDependencies(path){
let file = FS.readFileSync(path, 'utf8');

// Match all the dependencies of the module using a regex
const fullDependencyRegex = /@\w+\s*(?:protected|public)?\s*(\w+(?:\.\w+)*)\s*provide\w+\s*\(([^\)]*)\)/;
const paramRegex = /\s*(\w+)\s*\w+\s*,?\s*/;

// Group 1: params set if kotlin
// Group 2: dep name set if kotlin
// Group 3: dep name set if java
// Group 4: params set if java
const fullDependencyRegex = /(?:@Named\s*\("[^"]*"\)\s*)?@Provides(?:(?:\n|.)*?\s+fun\s+.+?\(\s*((?:\n|.)*?)\)\s*:\s*(\w+(?:\.\w+)*)(?:\s+|=)|(?:\n|.)*?\s+(?:static)?\s*(?:protected|public)?\s+(\w*)\s+\w+\s*\(((?:\n|.)*?)\))/;

// Match the dependencies inside the params of each provided dcependency
// Group 1: name set if java
// Group 2: name set if kotlin
const paramRegex = /(?:\s*(\w+)\s+\w+\s*,?\s*|\s*\w+\s*:\s*(\w+)\s*,?\s*)/;
const namedRegex = /@Named\(\"([a-zA-Z0-9_ ]*)\"\)/;

const deps = [];
while ((fullMatch = fullDependencyRegex.exec(file)) !== null) {

var dep = (fullMatch[3] !== undefined) ? fullMatch[3] : fullMatch[2];

// Get dependency name
file = file.replace(fullDependencyRegex, "");
const moduleDep = new DDependency(fullMatch[1]);
let params = fullMatch[2];

const moduleDep = new DDependency(dep);

// Get sub-depepndencies
while ((paramMatch = paramRegex.exec(params)) !== null) {
params = params.replace(paramRegex, "");
moduleDep.addDependency(new DDependency(paramMatch[1]));
let params = (fullMatch[4] !== undefined) ? fullMatch[4] : fullMatch[1];
if (params !== undefined) {
while ((paramMatch = paramRegex.exec(params)) !== null) {
params = params.replace(paramRegex, "");

var depName = (paramMatch[1] !== undefined) ? paramMatch[1] : paramMatch[2];
moduleDep.addDependency(new DDependency(depName));
}
}

// Look for @Named in the full matcher and add it to the dependency if found
const namedMatch = namedRegex.exec(fullMatch[0]);
if(namedMatch !== null && namedMatch[1] !== undefined && namedMatch[1] !== null){
moduleDep.addNamed(namedMatch[1]);
}

deps.push(moduleDep);
}

Expand Down

0 comments on commit ed27732

Please sign in to comment.