Skip to content
This repository was archived by the owner on Mar 17, 2025. It is now read-only.

Type imports #208

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 35 additions & 4 deletions bin/firebase-bolt
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ function main() {
if (args.output !== undefined) {
writeTranslation(util.ensureExtension(args.output, 'json'), data);
} else {
// Note: Not sure but this will more than likely break some form of
// compatability
console.log(translateRules(data));
}
});
Expand All @@ -106,12 +108,15 @@ function main() {
log("Could not read file: " + inFile);
process.exit(1);
}
writeTranslation(outFile, data);
writeTranslationWithImports(outFile, data, inFile);
});
}

/**
* Depricated: Use writeTranslation still needs to be maintained as we have the
* ability to write the translation from STDIN not just the file system.
* ??? Global modules should still be supported from std in.
*/
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would think that if you read from STDIN then you would just resolve relative to the current directory instead of the file location. Thoughts? I'm not sure what the best thing for global imports would be... Maybe they should look in global node packages only?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I didn't really think of std in as a use case. Not sure if this is required though. See comments below about global modules.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At least we should gracefully handle errors if people try and do this, but having these two code paths isn't great either.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think throwing a deprecation warning is appropriate but I wouldn't remove it unless there is a major version bump (backward compatability must be maintained). So I would add this as an open issue to remove old behaviour in the next major version bump.

So 2 suggestions, I will add the deprecation warning and can add the deprecation issue for the next major milestone. (you might have to create the next major milestone tag).

function writeTranslation(outFile, data) {
log("Generating " + outFile + "...");
fs.writeFile(outFile, translateRules(data) + '\n', 'utf8', function(err2) {
if (err2) {
log("Could not write file: " + outFile);
Expand All @@ -120,6 +125,15 @@ function writeTranslation(outFile, data) {
});
}

function writeTranslationWithImports(outFile, data, inFile) {
fs.writeFile(outFile, translateRulesWithImports(inFile) + '\n', 'utf8', function(err2) {
if (err2) {
log("Could not write file: " + outFile);
process.exit(1);
}
});
}

function usage(code) {
var cmdName = process.argv[1].split('/').slice(-1);
console.error("Translate Firebase Bolt file into JSON rules format.\n");
Expand Down Expand Up @@ -160,18 +174,35 @@ function readFile(f, callback) {

function translateRules(input) {
var symbols;
var rules;

try {
symbols = bolt.parse(input);
return continueTranslateRules(symbols);
} catch (e) {
if (DEBUG) {
log(e.stack);
}
log(e.message, e.line, e.column);
process.exit(1);
}
}

function translateRulesWithImports(inFile) {
var symbols;

try {
symbols = bolt.parseWithImports(inFile);
return continueTranslateRules(symbols);
} catch (e) {
if (DEBUG) {
log(e.stack);
}
log(e.message, e.line, e.column);
process.exit(1);
}
}
function continueTranslateRules(symbols) {
var rules;
try {
var gen = new bolt.Generator(symbols);
rules = gen.generateRules();
Expand Down
2 changes: 1 addition & 1 deletion gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ gulp.task('ts-compile', ['build-peg'], function() {
.pipe(gulp.dest(LIB_DIR));
});

gulp.task('build', ['ts-compile', 'browserify-bolt']);
gulp.task('build', ['build-peg', 'ts-compile', 'browserify-bolt']);

gulp.task('build-peg', function() {
return gulp.src('src/rules-parser.pegjs')
Expand Down
25 changes: 25 additions & 0 deletions samples/subModule/typeOne.bolt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {TypeTwo} from './typeTwo'


type SampleOne extends TypeTwo {
validate() { true }
}
type TypeOne extends String {
validate() { true }
}
type Alpha extends String {
validate() { this.test(/^[a-zA-Z]*$/) }
}

type Alphanumeric {
validate() { this.test(/^[a-zA-Z0-9]*$/) }
alphaValue: String
}

type Ascii extends String {
validate() { this.test(/^[\x00-\x7F]+$/) }
}

type Secondary extends String {
validate() { true }
}
8 changes: 8 additions & 0 deletions samples/subModule/typeTwo.bolt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

type TypeTwo extends String {
validate() { this.test(/^[a-z]*$/) }
}

type Fourth extends String {
validate() { this.test(/^4$/)}
}
40 changes: 40 additions & 0 deletions samples/type-imports.bolt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Lvl 1 - simply import everything.
import * from './subModule/typeOne'
// Lvl 2 - import single item only
import {Alpha, Alphanumeric} from './subModule/typeOne'
// Lvl 3 - import as an aliased type
import {Alpha, Alphanumeric} as types from './subModule/typeOne'
// Lvl 4 - import recursive types
import { SampleOne } from './subModule/typeOne'

// Lvl 5 - import with an adjusted path
// import from './subModule/typeOne' at '/somewhere' // duplicate import test
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: This will be done in a seperate module.



type Test extends Another {
validate() { this.test(/^[0-9]*$/) }
}

type Another {
validate() { this.message.length < 14 }
something : String
}
path / is SampleOne {
validate() { true }
}

path /test is Alphanumeric {
read() {true}
}

path /alpha is Alpha {
read() {true}
}

path /alphanumeric is types.Alphanumeric {
read() { true }
}

path /ascii is Ascii {
read() { true }
}
31 changes: 31 additions & 0 deletions src/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export interface TypeParams { [name: string]: ExpType; };
// Simple Type (reference)
export interface ExpSimpleType extends Exp {
name: string;
namespace?: string;
}

// Union Type: Type1 | Type2 | ...
Expand All @@ -97,6 +98,13 @@ export interface Method {
body: Exp;
}

export interface Import {
filename: string;
alias: string;
identifiers: string[];
symbols?: Symbols;
}

export class PathPart {
label: string;
variable: string;
Expand Down Expand Up @@ -501,6 +509,14 @@ export function method(params: string[], body: Exp): Method {
};
}

export function typeTypeNamespaced(typeName: string, namespace: string) {
if (namespace) {
return { type: "type", valueType: "type", name: typeName, namespace: namespace};
} else {
return { type: "type", valueType: "type", name: typeName}; // backwards compatability for tests
}
}

export function typeType(typeName: string): ExpSimpleType {
return { type: "type", valueType: "type", name: typeName };
}
Expand All @@ -509,6 +525,9 @@ export function unionType(types: ExpType[]): ExpUnionType {
return { type: "union", valueType: "type", types: types };
}

export function genericTypeNamespaced(typeName: string, params: ExpType[], namespace: string) {
return { type: "generic", valueType: "type", name: typeName, params: params, namespace: namespace };
}
export function genericType(typeName: string, params: ExpType[]): ExpGenericType {
return { type: "generic", valueType: "type", name: typeName, params: params };
}
Expand All @@ -517,11 +536,13 @@ export class Symbols {
functions: { [name: string]: Method };
paths: Path[];
schema: { [name: string]: Schema };
imports: Import[] ;

constructor() {
this.functions = {};
this.paths = [];
this.schema = {};
this.imports = [];
}

register<T>(map: {[name: string]: T}, typeName: string, name: string, object: T): T {
Expand All @@ -538,6 +559,16 @@ export class Symbols {
method(params, body));
}

registerImport(identifiers: string[], alias: string, filePath: string): Import {
var i: Import = {
filename : filePath,
alias: alias,
identifiers: identifiers
};
this.imports.push(i);
return i;
}

registerPath(template: PathTemplate, isType: ExpType | void, methods: { [name: string]: Method; } = {}): Path {
isType = isType || typeType('Any');
var p: Path = {
Expand Down
6 changes: 4 additions & 2 deletions src/bolt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@ if (typeof Promise === 'undefined') {
require('es6-promise').polyfill();
}

let parser = require('./rules-parser');
let parser = require('./imports-parser');
let rulesParser = require('./rules-parser');
import * as generator from './rules-generator';
import * as astImport from './ast';

export let FILE_EXTENSION = 'bolt';

export let ast = astImport;
export let parse = parser.parse;
export let parseWithImports = parser.parseWithImports;
export let parse = rulesParser.parse;
export let Generator = generator.Generator;
export let decodeExpression = ast.decodeExpression;
export let generate = generator.generate;
55 changes: 55 additions & 0 deletions src/imports-parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
let rulesParser = require('./rules-parser');
let fs = require('fs');
/*
Imports file parser for split file systems
Note: Using a modified ES6 syntax to include imports
*/
export function parseWithImports(filename: string) {
// creating a stream through which each file will pass
let contents = fs.readFileSync(filename, "utf8");
return parserWrapper(contents, filename);
}

/*
*************************** Function Section ****************************
*/

/* ******** wrapper for recursive parsing ******* */
function parserWrapper(data: string, filename: string) {
var sym = rulesParser.parse(data);

// Process any imports in symbol list
for (let i = 0; i < sym.imports.length; i++) {
let next = sym.imports[i];
var nextFilename = getNextFilenameFromContextAndImport(filename, next.filename);
let contents = fs.readFileSync(nextFilename, 'utf8');
let nextSymbols = parserWrapper(contents, nextFilename);
sym.imports[i].symbols = nextSymbols; // add it to the next part of the tree
}
return sym;
}; // end function

// Convert absolute filenames to relative
// Convert relative filenames to include original path
function getNextFilenameFromContextAndImport(current: string, nextImport: any) {
current = current.replace('.bolt', '');
nextImport = nextImport.replace('.bolt', '');
var currentFn = current.split('/');
var nextFn = nextImport.split('/');
let result = '';
if (nextFn[0] !== '.' && nextFn[0] !== '..') { // global reference
result = './node_modules/' + nextImport + '/index.bolt';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make the assumption that this is always ran from the root of the project directory?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, interesting point. The difference here is between bower & npm (or npm dedupe).

My preference would be not to have nesting (bower) but I didn't think of the nesting module issue. I can go back to the drawing board and look at the module resolution process to see if we can support an npm hierarchy module resultion.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need to support nesting, but maybe I'm not familiar enough is ./node_modules going to exist? Is it possible to run this from a ./src/ directory so that we would need to look in ../node_modules?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

node_modules are a standard npm spec so if they use NPM this would exist in the project root directory (not ./src/node_modules).

It's a valid test to load it from the ./src/myfile.bolt directory but generally the root directory is still considered ./

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will have a look at using the following package to do the resolution https://www.npmjs.com/package/node-modules-resolve

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or this one is probably better https://www.npmjs.com/package/resolve

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. It looks like that first package just recursively searches parent directories until it gets to a ./node_modules and uses that one.

If the root directory is considered to be the current working directory we should check for it when we start. This makes an assumption which isn't enforced anywhere as far as I can tell.

} else {
// import {./something} -> ['.','something'] -> ''
// import {./something/anotherthing} -> ['.','something','anotherthing'] -> something
currentFn.pop(); // remove trailing file name and leave only the directory
nextFn = currentFn.concat(nextFn);
// if file.bolt exists then we have it otherwise return
if (fs.existsSync(nextFn.join('/') + '.bolt')) {
result = nextFn.join('/') + '.bolt';
} else {
result = nextFn.join('/') + '/index.bolt';
}
}
return result;
}
Loading