Skip to content

Commit

Permalink
feat: add package.json parser #20
Browse files Browse the repository at this point in the history
  • Loading branch information
regevbr committed May 8, 2020
1 parent 56bedde commit b660419
Show file tree
Hide file tree
Showing 20 changed files with 650 additions and 7 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
"@types/cross-spawn": "^6.0.1",
"@types/jest": "^25.2.1",
"@types/jest-when": "^2.7.1",
"@types/lodash": "^4.14.150",
"@types/node": "^13.13.5",
"@types/node-fetch": "^2.5.7",
"@types/pacote": "^11.1.0",
Expand Down Expand Up @@ -129,6 +130,7 @@
"cross-spawn": "~7.0.2",
"figures": "~3.2.0",
"inversify": "~5.0.1",
"lodash": "^4.17.15",
"moment": "~2.25.3",
"pacote": "~11.1.9",
"reflect-metadata": "~0.1.13",
Expand Down
2 changes: 2 additions & 0 deletions src/container/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { packageInfoModulesBinder } from '../utils/packageInfo';
import { nodeModulesBinder } from './nodeModulesContainer';
import { enginesResolveModulesBinder } from '../resolvers/enginesResolver';
import { loggerModuleBinder } from '../utils/logger';
import { manifestParserModulesBinder } from '../utils/manifestParser';

export const container = new Container({
skipBaseClassChecks: true,
Expand All @@ -34,6 +35,7 @@ const binders: Binder[] = [
packageInfoModulesBinder,
enginesResolveModulesBinder,
loggerModuleBinder,
manifestParserModulesBinder,
];

container.load(
Expand Down
6 changes: 1 addition & 5 deletions src/dependencyChecker/interfaces/IDependencyChecker.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
export enum DependencyType {
PROD = `production`,
DEV = `development`,
PEER = `peer`,
}
import { DependencyType } from '../../utils/manifestParser';

export interface IDependencyCheckerRunOptions {
pkg: {
Expand Down
44 changes: 44 additions & 0 deletions src/utils/manifestParser/impl/dependencyTypeHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { DependencyType, IDependency } from '../types';
import { IPredicate } from '../../predicateBuilder/predicateBuilder';
import { ILogger } from '../../logger/interfaces/ILogger';

interface DependencyTypeHandlerOptions {
predicate: IPredicate;
included: boolean;
dependencies: undefined | Record<string, string>;
type: DependencyType;
logger: ILogger;
}

export const dependencyTypeHandler = ({
included,
dependencies,
type,
predicate,
logger,
}: DependencyTypeHandlerOptions): Set<IDependency> => {
const results = new Set<IDependency>();
if (included) {
if (dependencies) {
let count = 0;
for (const [name, semver] of Object.entries(dependencies)) {
if (predicate(name)) {
count++;
results.add({
name,
semver,
dependencyType: type,
});
} else {
logger.info(`Skipping ${name}`);
}
}
logger.info(`Found ${count} ${type} dependencies`);
} else {
logger.info(`No ${type} dependencies defined`);
}
} else {
logger.info(`Skipping ${type} dependencies`);
}
return results;
};
53 changes: 53 additions & 0 deletions src/utils/manifestParser/impl/manifestParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { injectable } from 'inversify';
import { ILoggerFactory } from '../../logger';
import { ILogger } from '../../logger/interfaces/ILogger';
import { IManifestParser, IManifestParserOptions } from '../interfaces/IManifestParser';
import { DependencyType, IDependency, IPackageData } from '../types';
import { dependencyTypeHandler } from './dependencyTypeHandler';

@injectable()
export class ManifestParser extends IManifestParser {
private readonly logger: ILogger;

constructor(loggerFactory: ILoggerFactory) {
super();
this.logger = loggerFactory.getLogger(`Manifest Parser`);
}

public async parse({ manifest, include, predicate }: IManifestParserOptions): Promise<IPackageData> {
this.logger.info(`Reading manifest information`);
const options = {
predicate,
logger: this.logger,
};
const dev = dependencyTypeHandler({
...options,
type: DependencyType.DEV,
included: include.dev,
dependencies: manifest.devDependencies,
});
const prod = dependencyTypeHandler({
...options,
type: DependencyType.PROD,
included: include.prod,
dependencies: manifest.dependencies,
});
const peer = dependencyTypeHandler({
...options,
type: DependencyType.PEER,
included: include.peer,
dependencies: manifest.peerDependencies,
});
const optional = dependencyTypeHandler({
...options,
type: DependencyType.OPTIONAL,
included: include.optional,
dependencies: manifest.optionalDependencies,
});
const dependencies = new Set<IDependency>([...dev, ...prod, ...peer, ...optional]);
this.logger.info(`Going to check ${dependencies.size} dependencies`);
return {
dependencies,
};
}
}
34 changes: 34 additions & 0 deletions src/utils/manifestParser/impl/npmGlobalManifestParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { injectable } from 'inversify';
import { ILoggerFactory } from '../../logger';
import { ILogger } from '../../logger/interfaces/ILogger';
import { INpmGlobalManifestParser, INpmGlobalManifestParserOptions } from '../interfaces/INpmGlobalManifestParser';
import { DependencyType, IDependency, IPackageData } from '../types';
import { dependencyTypeHandler } from './dependencyTypeHandler';
import * as _ from 'lodash';

@injectable()
export class NpmGlobalManifestParser extends INpmGlobalManifestParser {
private readonly logger: ILogger;

constructor(loggerFactory: ILoggerFactory) {
super();
this.logger = loggerFactory.getLogger(`Npm Global Manifest Parser`);
}

public async parse({ manifest, predicate }: INpmGlobalManifestParserOptions): Promise<IPackageData> {
this.logger.info(`Reading npm global manifest information`);
const deps = _.mapValues(manifest.dependencies, (val) => val.version);
const global = dependencyTypeHandler({
predicate,
logger: this.logger,
type: DependencyType.GLOBAL,
included: true,
dependencies: deps,
});
const dependencies = new Set<IDependency>([...global]);
this.logger.info(`Going to check ${dependencies.size} dependencies`);
return {
dependencies,
};
}
}
15 changes: 15 additions & 0 deletions src/utils/manifestParser/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { interfaces } from 'inversify';
import Bind = interfaces.Bind;
import { IManifestParser } from './interfaces/IManifestParser';
import { ManifestParser } from './impl/manifestParser';
import { INpmGlobalManifestParser } from './interfaces/INpmGlobalManifestParser';
import { NpmGlobalManifestParser } from './impl/npmGlobalManifestParser';

export const manifestParserModulesBinder = (bind: Bind): void => {
bind<IManifestParser>(IManifestParser).to(ManifestParser).inSingletonScope();
bind<INpmGlobalManifestParser>(INpmGlobalManifestParser).to(NpmGlobalManifestParser).inSingletonScope();
};

export * from './interfaces/IManifestParser';
export * from './interfaces/INpmGlobalManifestParser';
export * from './types';
18 changes: 18 additions & 0 deletions src/utils/manifestParser/interfaces/IManifestParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { IPredicate } from '../../predicateBuilder/predicateBuilder';
import { Manifest } from 'pacote';
import { IPackageData } from '../types';

export interface IManifestParserOptions {
manifest: Manifest;
include: {
prod: boolean;
dev: boolean;
optional: boolean;
peer: boolean;
};
predicate: IPredicate;
}

export abstract class IManifestParser {
public abstract async parse(options: IManifestParserOptions): Promise<IPackageData>;
}
15 changes: 15 additions & 0 deletions src/utils/manifestParser/interfaces/INpmGlobalManifestParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { IPackageData } from '../types';
import { IPredicate } from '../../predicateBuilder/predicateBuilder';

export interface INpmGlobalManifest {
dependencies?: Record<string, { version: string }>;
}

export interface INpmGlobalManifestParserOptions {
manifest: INpmGlobalManifest;
predicate: IPredicate;
}

export abstract class INpmGlobalManifestParser {
public abstract async parse(options: INpmGlobalManifestParserOptions): Promise<IPackageData>;
}
17 changes: 17 additions & 0 deletions src/utils/manifestParser/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export enum DependencyType {
PROD = `production`,
DEV = `development`,
PEER = `peer`,
OPTIONAL = `optional`,
GLOBAL = `global`,
}

export interface IDependency {
semver: string;
name: string;
dependencyType: DependencyType;
}

export interface IPackageData {
dependencies: Set<IDependency>;
}
43 changes: 43 additions & 0 deletions src/utils/predicateBuilder/predicateBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { isString } from 'ts-type-guards';

const fromPrimitive = (input: string | RegExp): IPredicate => {
if (isString(input)) {
return (predicate: string): boolean => predicate === input;
}
return (predicate: string): boolean => input.test(predicate);
};

const truthy: IPredicate = (): boolean => true;

const not = (predicate: IPredicate): IPredicate => (str: string): boolean => !predicate(str);

const or = (predicate1: IPredicate, predicate2: IPredicate): IPredicate => (str: string): boolean =>
predicate1(str) || predicate2(str);

const and = (predicate1: IPredicate, predicate2: IPredicate): IPredicate => (str: string): boolean =>
predicate1(str) && predicate2(str);

const orReducer = (prev: IPredicate, current: IPredicate): IPredicate => {
return or(prev, current);
};

const andReducer = (prev: IPredicate, current: IPredicate): IPredicate => {
return and(prev, current);
};

export type IPredicate = (str: string) => boolean;
export type Filter = (string | RegExp)[];

export const buildPredicate = (include: Filter, exclude: Filter): IPredicate => {
const includePredicateArr = include.map(fromPrimitive);
if (includePredicateArr.length === 0) {
includePredicateArr.push(truthy);
}
const includePredicate = includePredicateArr.reduce(orReducer);
const excludePredicateArr = exclude.map(fromPrimitive).map(not);
if (excludePredicateArr.length === 0) {
excludePredicateArr.push(truthy);
}
const excludePredicate = excludePredicateArr.reduce(andReducer);
return and(includePredicate, excludePredicate);
};
82 changes: 82 additions & 0 deletions src/utils/predicateBuilder/stringSplitter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
const quotes = new Set<string>([`"`, `'`, `\``]);
const separators = new Set<string>([` `, `,`]);
const regexPlaceholder = `/`;
const regexMatcher = /^\/(.+)\/(i)?$/i;

class Splitter {
private idx = -1;
private currentSequence = ``;
private results: string[] = [];

constructor(private readonly input: string) {}

public split(): string[] {
const iterationLimit = this.input.length - 1;
while (this.idx < iterationLimit) {
const currentChar = this.consumeNextChar();
if (currentChar === regexPlaceholder && !this.currentSequence) {
const closingIdx = this.getClosingIndex(currentChar, this.idx + 1);
if (closingIdx > -1) {
this.append(this.input.slice(this.idx, closingIdx + 1));
this.idx = closingIdx;
} else {
this.append(currentChar);
}
} else if (quotes.has(currentChar)) {
const pos = this.idx + 1;
const closingIdx = this.getClosingIndex(currentChar, pos);
if (closingIdx > -1) {
this.append(this.input.slice(pos, closingIdx));
this.idx = closingIdx;
} else {
this.append(currentChar);
}
} else if (separators.has(currentChar)) {
this.break();
} else {
this.append(currentChar);
}
}
this.break();
return this.results;
}

private break(): void {
if (this.currentSequence) {
this.results.push(this.currentSequence);
this.currentSequence = ``;
}
}

private append(sequence: string): void {
this.currentSequence += sequence;
}

private getClosingIndex(quote: string, startIdx: number): number {
let idx = this.input.indexOf(quote, startIdx);
if (idx > -1 && this.input[idx - 1] === `\\`) {
idx = this.getClosingIndex(quote, idx + 1);
}
return idx;
}

private consumeNextChar(): string {
return this.input[++this.idx];
}
}

const regexParser = (input: string): string | RegExp => {
const match = regexMatcher.exec(input);
if (!match) {
return input;
}
return new RegExp(match[1], match[2]);
};

export const split = (str: string | undefined): (string | RegExp)[] => {
if (!str) {
return [];
}
const splitter = new Splitter(str);
return splitter.split().filter(Boolean).map(regexParser);
};
Loading

0 comments on commit b660419

Please sign in to comment.