Skip to content

Commit

Permalink
feat: support tsconfig path mappings (#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
mattem authored Apr 7, 2020
1 parent 03fcbcd commit 0e9b2a2
Show file tree
Hide file tree
Showing 11 changed files with 156 additions and 24 deletions.
2 changes: 1 addition & 1 deletion .bzlgenrc
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
--ng_module_bundle_load=//tools/rules_bazel/defs.bzl

# label mappings
--label_mapping=rxjs/operators=@npm//rxjs
--label_mapping=rxjs/*=@npm//rxjs
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"shelljs": "0.8.3",
"signale": "1.4.0",
"typescript": "3.8.3",
"tsconfig-paths": "3.9.0",
"yargs": "14.2.0"
},
"devDependencies": {
Expand Down
1 change: 1 addition & 0 deletions src/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ RUNTIME_NPM_DEPS = [
"@npm//@phenomnomnominal/tsquery",
"@npm//typescript",
"@npm//minimatch",
"@npm//tsconfig-paths",
]

ts_library(
Expand Down
15 changes: 13 additions & 2 deletions src/generators/generator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
import { GeneratorType } from '../flags';
import { Flags, GeneratorType } from '../flags';
import { Workspace } from '../workspace';
import { Buildozer } from '../buildozer';

export abstract class BuildFileGenerator {
private readonly flags: Flags;

protected readonly buildozer: Buildozer;

protected constructor(protected readonly workspace: Workspace) {
this.buildozer = workspace.getBuildozer();
this.flags = workspace.getFlags();
}

/**
* Run any validation rules here on the current flags or workspace
* If an error is thrown then the message is printed to the user and the generator exits with code 1,
Expand All @@ -24,4 +35,4 @@ export abstract class BuildFileGenerator {
* Return if this generator supports directories
*/
public abstract supportsDirectories(): boolean;
}
}
8 changes: 2 additions & 6 deletions src/generators/sass/sass.generator.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
import * as gonzales from 'gonzales-pe';
import { ParsedPath } from 'path';

import { Buildozer } from '../../buildozer';
import { GeneratorType } from '../../flags';
import { log } from '../../logger';
import { Workspace } from '../../workspace';
import { BuildFileGenerator } from '../generator';

export class SassGenerator extends BuildFileGenerator {
private readonly buildozer: Buildozer;

constructor(private readonly workspace: Workspace) {
super();
this.buildozer = workspace.getBuildozer();
constructor(workspace: Workspace) {
super(workspace);
}

async generate(): Promise<void> {
Expand Down
10 changes: 10 additions & 0 deletions src/generators/ts/ts.generator.flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ export function setupGeneratorCommand(y) {
description: 'The label used for any tsconfig attrs',
requiresArg: true,
group: 'TS Generator'
}).option('ts_config', {
type: 'string',
description: 'Path to a tsconfig.json file that is used to attempt to resolve path mappings',
requiresArg: true,
group: 'TS Generator'
});
}

Expand All @@ -23,4 +28,9 @@ export interface TsGeneratorFlags {
* The label used for any tsconfig attrs
*/
ts_config_label: string;

/**
* Path (relative to base_dir) to a tsconfig.json file that is used to attempt to resolve path mappings
*/
ts_config: string;
}
63 changes: 57 additions & 6 deletions src/generators/ts/ts.generator.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import { parse, posix } from 'path';
import { tsquery } from '@phenomnomnominal/tsquery';
import { ExportDeclaration, Expression, ImportDeclaration, SourceFile } from 'typescript';
import { Buildozer } from '../../buildozer';
import {
ExportDeclaration,
Expression,
ImportDeclaration,
ParseConfigHost,
parseJsonSourceFileConfigFileContent,
readJsonConfigFile,
SourceFile,
sys
} from 'typescript';
import { createMatchPath, MatchPath } from 'tsconfig-paths';

import { GeneratorType } from '../../flags';
import { Label } from '../../label';
import { fatal, log } from '../../logger';
Expand All @@ -12,11 +22,12 @@ const IMPORTS_QUERY = `ImportDeclaration:has(StringLiteral)`;
const EXPORTS_QUERY = `ExportDeclaration:has(StringLiteral)`;

export class TsGenerator extends BuildFileGenerator {
protected readonly buildozer: Buildozer;
protected readonly tsPathsMatcher: MatchPath;

constructor(workspace: Workspace) {
super(workspace);

constructor(protected readonly workspace: Workspace) {
super();
this.buildozer = workspace.getBuildozer();
this.tsPathsMatcher = this.createPathMatcherForTsPaths();
}

async generate(): Promise<void> {
Expand Down Expand Up @@ -102,6 +113,9 @@ export class TsGenerator extends BuildFileGenerator {
}

private calculateTsDependencyLabel(imp: string, npmWorkspace: string): Label | undefined {
// see if there is a tsconfig.json, and if we need to adjust the import
imp = this.tryResolveFromTsPaths(imp);

let label = this.workspace.tryResolveLabelFromStaticMapping(imp, undefined, '.');
if (label) { return label; }

Expand Down Expand Up @@ -130,4 +144,41 @@ export class TsGenerator extends BuildFileGenerator {
}
}

private tryResolveFromTsPaths(imp: string): string {
if (!this.tsPathsMatcher) {
return imp;
}

const path = this.tsPathsMatcher(imp, undefined, undefined, ['.ts']);
return path ? path.replace(this.workspace.getFlags().base_dir + '/', '') : imp;
}

private createPathMatcherForTsPaths(): MatchPath | undefined {
if (!this.workspace.getFlags().ts_config) {
return;
}

const tsconfigPath = this.workspace.resolveRelativeToWorkspace(this.workspace.getFlags().ts_config);
const tsConfigSourceFile = readJsonConfigFile(tsconfigPath, path => this.workspace.readFile(path));

if (!tsConfigSourceFile) {
throw new Error(`--ts_config flag set, but failed to load tsconfig.json from ${tsconfigPath}`);
}

const parseConfigHost: ParseConfigHost = {
fileExists: sys.fileExists,
readFile: path => this.workspace.readFile(path),
readDirectory: sys.readDirectory,
useCaseSensitiveFileNames: true
};

const parsed = parseJsonSourceFileConfigFileContent(tsConfigSourceFile, parseConfigHost, this.workspace.getPath());

// we _could_ throw parse errors here, but it may be not worth it if we can access the paths attr
if (!parsed.options.paths) {
return;
}

return createMatchPath(this.workspace.getAbsolutePath(), parsed.options.paths);
}
}
1 change: 1 addition & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export async function run() {
}

const isValid = generator.validate();

if (isValid) {
await wrap('generate', async () => await generator.generate());

Expand Down
14 changes: 7 additions & 7 deletions src/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { debug, fatal, isDebugEnabled, lb, log, warn } from './logger';
export class Workspace {
private readonly buildozer: Buildozer;

private readonly staticLabels: Map<RegExp, string>;
private readonly staticLabels: Map<string, RegExp>;
private readonly resolvedStaticLabelsCache: Map<string, Label> = new Map<string, Label>();

private readonly fileQueryResultCache: Map<string, Label> = new Map<string, Label>();
Expand All @@ -23,8 +23,8 @@ export class Workspace {
constructor(private readonly flags: Flags) {
this.buildozer = new Buildozer(this.flags.load_mapping);

const regexLabels: Array<[RegExp, string]> = Array.from(this.flags.label_mapping.entries())
.map(pair => [minimatch.makeRe(pair[0]), pair[1]]);
const regexLabels: Array<[string, RegExp]> = Array.from(this.flags.label_mapping.entries())
.map(pair => [pair[1], minimatch.makeRe(pair[0])]);

this.staticLabels = new Map(regexLabels);

Expand Down Expand Up @@ -313,17 +313,17 @@ export class Workspace {
return this.resolvedStaticLabelsCache.get(imp);
}

// find returns the first found truthy match, so there _may_ be a more speciffic glob in the map
// find returns the first found truthy match, so there _may_ be a more specific glob in the map
// but we won't find it - room for improvement, but the consumer can move them up the list
const result = Array.from(this.staticLabels.entries())
.find(([key, _]) => !!key.exec(imp));
.find(([_, value]) => !!value.exec(imp));

if (!result) {
return defaultValue ? Label.parseAbsolute(defaultValue) : undefined;
}

const staticMaped = result[1];
const label = Label.parseAbsolute(staticMaped);
const staticMapped = result[0];
const label = Label.parseAbsolute(staticMapped);

this.resolvedStaticLabelsCache.set(imp, label);

Expand Down
38 changes: 36 additions & 2 deletions test/generators/ts.generator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as mockfs from 'mock-fs';
import { setupAndParseArgs } from '../../src/flags';
import { TsGenerator } from '../../src/generators/ts/ts.generator';
import { Workspace } from '../../src/workspace';
import { TsGeneratorFlags } from '../../src/generators/ts/ts.generator.flags';

describe('ng generator', () => {
const TS_ONE =
Expand Down Expand Up @@ -36,9 +37,10 @@ export class Some {}

workspace = new Workspace(setupAndParseArgs(argv, true, 0));
gen = new TsGenerator(workspace);
})
});

afterEach(() => mockfs.restore());

it('can generate ts_library with deps', async () => {
mockfs({
'/home/workspace/src/some': {
Expand Down Expand Up @@ -67,6 +69,38 @@ export class Some {}
expect(commands.join('\n')).toEqual(expected);
});

it('can use tsconfig paths', async () => {
mockfs({
'/home/workspace': {
src: {
some: {
nested: {
'main.ts': ''
},
'one.ts': `import { BAR } from '@foo/main'`,
'tsconfig.json': `{"compilerOptions":{"paths":{"@foo/*":["nested/*"]}}}`
}
}
},
});

(workspace.getFlags() as TsGeneratorFlags).ts_config = 'tsconfig.json';
gen = new TsGenerator(workspace);

await gen.generate();

const commands = workspace.getBuildozer().toCommands();

const expected =
'new_load @npm_bazel_typescript//:index.bzl ts_library|//src/some:__pkg__\n' +
'new ts_library some|//src/some:__pkg__\n' +
'add srcs one.ts|//src/some:some\n' +
'add deps //src/some/nested:main|//src/some:some\n' +
'set tsconfig "//:tsconfig"|//src/some:some';

expect(commands.join('\n')).toEqual(expected);
});

it('can strip all deep imports', async () => {
mockfs({
'/home/workspace/src/some': {
Expand All @@ -82,7 +116,7 @@ export class Some {}
new ts_library some|//src/some:__pkg__
add srcs one.ts|//src/some:some
add deps @npm//package:package @npm//@scope/package:package|//src/some:some
set tsconfig "//:tsconfig"|//src/some:some`
set tsconfig "//:tsconfig"|//src/some:some`;

expect(commands.join('\n')).toEqual(expected);
});
Expand Down
27 changes: 27 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@
resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-3.5.0.tgz#2ad2006c8a937d20df20a8fee86071d0f730ef99"
integrity sha512-kGCRI9oiCxFS6soGKlyzhMzDydfcPix9PpTkr7h11huxOxhWwP37Tg7DYBaQ18eQTNreZEuLkhpbGSqVNZPnnw==

"@types/json5@^0.0.29":
version "0.0.29"
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=

"@types/long@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.0.tgz#719551d2352d301ac8b81db732acb6bdc28dbdef"
Expand Down Expand Up @@ -529,6 +534,13 @@ json-parse-better-errors@^1.0.1:
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==

json5@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==
dependencies:
minimist "^1.2.0"

lcid@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf"
Expand Down Expand Up @@ -618,6 +630,11 @@ minimist@1.1.x:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.1.3.tgz#3bedfd91a92d39016fcfaa1c681e8faa1a1efda8"
integrity sha1-O+39kaktOQFvz6ocaB6Pqhoe/ag=

minimist@^1.2.0:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==

minimist@~0.0.1:
version "0.0.10"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
Expand Down Expand Up @@ -1076,6 +1093,16 @@ test-exclude@^5.2.2:
read-pkg-up "^4.0.0"
require-main-filename "^2.0.0"

tsconfig-paths@3.9.0:
version "3.9.0"
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b"
integrity sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==
dependencies:
"@types/json5" "^0.0.29"
json5 "^1.0.1"
minimist "^1.2.0"
strip-bom "^3.0.0"

tslib@^1.8.1:
version "1.10.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
Expand Down

0 comments on commit 0e9b2a2

Please sign in to comment.