ng-typeview
Advanced tools
Comparing version 0.0.4 to 0.0.5
@@ -66,14 +66,8 @@ /// <reference types="es6-promise" /> | ||
*/ | ||
export interface ScopeInfo { | ||
readonly contents: string; | ||
readonly fieldNames: string[]; | ||
} | ||
/** | ||
* @hidden | ||
*/ | ||
export interface ControllerScopeInfo { | ||
readonly tsModuleName: Maybe<string>; | ||
readonly scopeInfo: Maybe<ScopeInfo>; | ||
readonly scopeInfo: Maybe<string>; | ||
readonly typeAliases: string[]; | ||
readonly imports: string[]; | ||
readonly importNames: string[]; | ||
readonly nonExportedDeclarations: string[]; | ||
@@ -80,0 +74,0 @@ } |
@@ -5,21 +5,8 @@ "use strict"; | ||
var monet_1 = require("monet"); | ||
function maybeWhen(p, v) { | ||
return p ? monet_1.Maybe.Some(v) : monet_1.Maybe.None(); | ||
} | ||
function parseScopeInterface(iface) { | ||
var typeIsIScope = function (t) { | ||
return t.expression.kind === ts.SyntaxKind.PropertyAccessExpression && | ||
t.expression.name.text === "IScope"; | ||
}; | ||
var heritageClauseHasIScope = function (c) { | ||
return monet_1.Maybe.fromNull(c.types).filter(function (ts) { return ts.some(typeIsIScope); }).isSome(); | ||
}; | ||
return monet_1.Maybe.fromNull(iface.heritageClauses) | ||
.filter(function (clauses) { return clauses.some(heritageClauseHasIScope); }) | ||
.map(function (_) { return getScopeInfo(iface); }); | ||
return maybeWhen(iface.name.getText() === "Scope", iface.getText()); | ||
} | ||
function getScopeInfo(iface) { | ||
var fieldNames = monet_1.List.fromArray(iface.members) | ||
.map(function (m) { return maybeIdentifier(m.name).map(function (i) { return i.text; }); }) | ||
.flatMap(function (m) { return m.toList(); }) | ||
.toArray(); | ||
return { contents: iface.getText(), fieldNames: fieldNames }; | ||
} | ||
var maybeNodeType = function (sKind) { return function (input) { | ||
@@ -173,2 +160,3 @@ return (input && input.kind === sKind) ? monet_1.Maybe.Some(input) : monet_1.Maybe.None(); | ||
var imports = []; | ||
var importNames = []; | ||
var nonExportedDeclarations = []; | ||
@@ -202,2 +190,3 @@ function nodeExtractScopeInterface(node) { | ||
imports.push(node.getText()); | ||
importNames.push(node.name.getText()); | ||
} | ||
@@ -209,3 +198,3 @@ ts.forEachChild(node, nodeExtractScopeInterface); | ||
tsModuleName: monet_1.Maybe.fromNull(tsModuleName), | ||
scopeInfo: scopeInfo, typeAliases: typeAliases, imports: imports, nonExportedDeclarations: nonExportedDeclarations | ||
scopeInfo: scopeInfo, typeAliases: typeAliases, imports: imports, importNames: importNames, nonExportedDeclarations: nonExportedDeclarations | ||
}); | ||
@@ -212,0 +201,0 @@ }); |
@@ -1,12 +0,18 @@ | ||
import { CodegenHelpers } from "./view-ngexpression-parser"; | ||
import { CodegenHelper } from "./view-ngexpression-parser"; | ||
/** | ||
* When handling an angular directive, you can generate TS source code for | ||
* type-safety testing. You can generate two things: | ||
* 1. Field `source`: code inserted right now | ||
* 2. Field `closeSource`: code that'll be inserted when this tag gets closed. | ||
* type-safety testing. This is what your directive can return to ng-typeview. | ||
*/ | ||
export declare type DirectiveResponse = { | ||
export interface DirectiveResponse { | ||
/** | ||
* The code you want to insert in the generated typescript | ||
*/ | ||
source: string; | ||
/** | ||
* An optional function returning the code that'll be inserted | ||
* when this tag gets closed (typically you'll give nothing, | ||
* or `}` or `})` for instance). | ||
*/ | ||
closeSource?: () => string; | ||
}; | ||
} | ||
/** | ||
@@ -32,3 +38,3 @@ * Allows to handle a specific angular directive, which is tied to an attribute | ||
*/ | ||
handleAttribute(attrName: string, attrValue: string, codegenHelpers: CodegenHelpers): DirectiveResponse | undefined; | ||
handleAttribute(attrName: string, attrValue: string, codegenHelpers: CodegenHelper): DirectiveResponse | undefined; | ||
} | ||
@@ -59,3 +65,3 @@ /** | ||
[type: string]: string; | ||
}, codegenHelpers: CodegenHelpers): DirectiveResponse | undefined; | ||
}, codegenHelpers: CodegenHelper): DirectiveResponse | undefined; | ||
} | ||
@@ -62,0 +68,0 @@ /** |
"use strict"; | ||
var P = require("parsimmon"); | ||
var view_ngexpression_parser_1 = require("./view-ngexpression-parser"); | ||
; | ||
var boolAttrHandler = { | ||
forAttributes: ["ng-required", "ng-disabled"], | ||
handleAttribute: function (attrName, val, codegenHelpers) { | ||
return ({ source: codegenHelpers.registerVariable("boolean", val) }); | ||
return ({ source: codegenHelpers.declareVariable("boolean", val) }); | ||
} | ||
@@ -16,3 +17,3 @@ }; | ||
return ({ | ||
source: codegenHelpers.registerVariable("boolean", val) + | ||
source: codegenHelpers.declareVariable("boolean", val) + | ||
("if (" + codegenHelpers.addScopeAccessors(val) + ") {"), | ||
@@ -27,3 +28,3 @@ closeSource: function () { return "}"; } | ||
handleAttribute: function (attrName, val, codegenHelpers) { | ||
return ({ source: codegenHelpers.registerVariable("any", val) }); | ||
return ({ source: codegenHelpers.declareVariable("any", val) }); | ||
} | ||
@@ -34,3 +35,3 @@ }; | ||
handleAttribute: function (attrName, val, codegenHelpers) { | ||
return ({ source: codegenHelpers.registerVariable("string", val) }); | ||
return ({ source: codegenHelpers.declareVariable("string", val) }); | ||
} | ||
@@ -41,3 +42,3 @@ }; | ||
handleAttribute: function (attrName, val, codegenHelpers) { | ||
return ({ source: codegenHelpers.registerVariable("number", val) }); | ||
return ({ source: codegenHelpers.declareVariable("number", val) }); | ||
} | ||
@@ -75,8 +76,15 @@ }; | ||
var enumerable = view_ngexpression_parser_1.ngFilterExpressionToTypeScriptEmbedded(ngRepeatData.value.expression, codegenHelpers); | ||
var source = "angular.forEach(" + enumerable + ", " + ngRepeatData.value.variable + " => {" + | ||
"let $index = 0;let $first = true;let $middle = true;" + | ||
"let $last = true;let $even = true;let $odd = false;" + | ||
var source = "angular.forEach(" + enumerable + ", " + codegenHelpers.registerVariable(ngRepeatData.value.variable) + " => {" + | ||
("let " + codegenHelpers.registerVariable('$index') + " = 0;") + | ||
("let " + codegenHelpers.registerVariable('$first') + " = true;") + | ||
("let " + codegenHelpers.registerVariable('$middle') + " = true;") + | ||
("let " + codegenHelpers.registerVariable('$last') + " = true;") + | ||
("let " + codegenHelpers.registerVariable('$even') + " = true;") + | ||
("let " + codegenHelpers.registerVariable('$odd') + " = false;") + | ||
(ngRepeatData.value.trackingExpression ? | ||
"" + codegenHelpers.registerVariable('any', ngRepeatData.value.trackingExpression) : ""); | ||
return { source: source, closeSource: function () { return "});"; } }; | ||
"" + codegenHelpers.declareVariable('any', ngRepeatData.value.trackingExpression) : ""); | ||
return { | ||
source: source, | ||
closeSource: function () { return "});"; } | ||
}; | ||
} | ||
@@ -122,3 +130,3 @@ }; | ||
} | ||
var addVar = function (v) { return (v ? "" + codegenHelpers.registerVariable('any', v) : ""); }; | ||
var addVar = function (v) { return (v ? "" + codegenHelpers.declareVariable('any', v) : ""); }; | ||
var addNgVar = function (v) { | ||
@@ -128,3 +136,3 @@ return (v ? view_ngexpression_parser_1.ngFilterExpressionToTypeScriptStandalone(v, codegenHelpers) : ""); | ||
var enumerable = view_ngexpression_parser_1.ngFilterExpressionToTypeScriptEmbedded(ngOptionsData.value.array, codegenHelpers); | ||
var source = "angular.forEach(" + enumerable + ", " + ngOptionsData.value.value + " => {" + | ||
var source = "angular.forEach(" + enumerable + ", " + codegenHelpers.registerVariable(ngOptionsData.value.value) + " => {" + | ||
addNgVar(ngOptionsData.value.select) + | ||
@@ -180,9 +188,9 @@ addNgVar(ngOptionsData.value.label) + | ||
case "ng-model": | ||
source += "let $select = {search:'', selected: " + codegenHelpers.addScopeAccessors(attrValue) + "};"; | ||
source += "let " + codegenHelpers.registerVariable("$select") + " = {search:'', selected: " + codegenHelpers.addScopeAccessors(attrValue) + "};"; | ||
break; | ||
case "allow-clear": | ||
source += codegenHelpers.registerVariable("boolean", attrValue); | ||
source += codegenHelpers.declareVariable("boolean", attrValue); | ||
break; | ||
case "ui-lock-choice": | ||
source += codegenHelpers.registerVariable("any", attrValue); | ||
source += codegenHelpers.declareVariable("any", attrValue); | ||
break; | ||
@@ -219,4 +227,6 @@ } | ||
var enumerable = view_ngexpression_parser_1.ngFilterExpressionToTypeScriptEmbedded(selectData.value.expression, codegenHelpers); | ||
return { source: enumerable + ".forEach(" + selectData.value.variable + " => {", | ||
closeSource: function () { return "});"; } }; | ||
return { | ||
source: enumerable + ".forEach(" + codegenHelpers.registerVariable(selectData.value.variable) + " => {", | ||
closeSource: function () { return "});"; } | ||
}; | ||
} | ||
@@ -223,0 +233,0 @@ } |
@@ -12,2 +12,5 @@ /// <reference types="es6-promise" /> | ||
} | ||
interface Array<T> { | ||
find(p: (item: T) => boolean): T | undefined; | ||
} | ||
} | ||
@@ -73,2 +76,23 @@ /** | ||
attributeDirectives: AttributeDirectiveHandler[]; | ||
/** | ||
* When resolving the scope for variables in the view, we prefix "$scope." | ||
* for all variables except those defined in the view. For instance, a | ||
* `ng-repeat` will define local variables. For these, we do not prefix with | ||
* "$scope.". 99% of the time, that works great. | ||
* One issue that can come up though, is if you have static fields for | ||
* instance. If you read `MyClass.MY_STATIC_FIELD`... That'll work in javascript | ||
* and angular, due to the TS->JS transpilation. But in ng-typeview, we | ||
* can't declare on the scope a field of type [class of MyClass], so that | ||
* field.MY_STATIC_FIELD would work. | ||
* So a workaround is to specify in your controller: | ||
* `import MyClass = api.MyClass;` | ||
* In that case, if you enable this `resolveImportsAsNonScope` option | ||
* (disabled by default), ng-typeview will not resolve | ||
* `MyClass.MY_STATIC_FIELD` as `$scope.MyClass.MY_STATIC_FIELD` anymore, | ||
* but as `MyClass.MY_STATIC_FIELD`. And since we copy the imports in the | ||
* viewtest, it should work. | ||
* But it's pretty messy, so we rather encourage you to avoid statics if | ||
* at all possible. | ||
*/ | ||
resolveImportsAsNonScope?: boolean; | ||
} | ||
@@ -75,0 +99,0 @@ /** |
@@ -47,3 +47,2 @@ "use strict"; | ||
var controller_parser_1 = require("./controller-parser"); | ||
var view_ngexpression_parser_1 = require("./view-ngexpression-parser"); | ||
// we only repeat the imports, type synonyms and custom interfaces | ||
@@ -63,5 +62,5 @@ // if there is a module, because otherwise those are dumped in the | ||
} | ||
function processControllerView(controllerPath, viewPath, ngFilters, tagDirectives, attributeDirectives) { | ||
function processControllerView(prjSettings, controllerPath, viewPath, ngFilters, tagDirectives, attributeDirectives) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var scopeContents, addScope, viewExprs, pathInfo, viewPathInfo, outputFname, moduleWrap, filterParams; | ||
var scopeContents, viewExprs, pathInfo, viewPathInfo, outputFname, moduleWrap, filterParams; | ||
return __generator(this, function (_a) { | ||
@@ -76,4 +75,3 @@ switch (_a.label) { | ||
} | ||
addScope = function (js) { return view_ngexpression_parser_1.addScopeAccessors(js, scopeContents.scopeInfo.some()); }; | ||
return [4 /*yield*/, view_parser_1.parseView(viewPath, addScope, immutable_1.List(tagDirectives), immutable_1.List(attributeDirectives))]; | ||
return [4 /*yield*/, view_parser_1.parseView(prjSettings.resolveImportsAsNonScope || false, viewPath, scopeContents.importNames, immutable_1.List(tagDirectives), immutable_1.List(attributeDirectives))]; | ||
case 2: | ||
@@ -89,3 +87,3 @@ viewExprs = _a.sent(); | ||
filterParams = ngFilters.map(function (f) { return "f__" + f.name + ":" + f.type; }).join(",\n "); | ||
fs_1.writeFileSync(outputFname, moduleWrap(scopeContents.scopeInfo.some().contents + | ||
fs_1.writeFileSync(outputFname, moduleWrap(scopeContents.scopeInfo.some() + | ||
("\n\nfunction ___f($scope: Scope, " + filterParams + ") {\n") + | ||
@@ -153,3 +151,3 @@ viewExprs + | ||
}); | ||
return [2 /*return*/, Promise.all(viewFilenameToCtrlFilenames.map(function (ctrlNames, viewName) { return Promise.all(ctrlNames.map(function (ctrlName) { return processControllerView(ctrlName, viewName, prjSettings.ngFilters, prjSettings.tagDirectives, prjSettings.attributeDirectives); }).toArray()); }).toArray())]; | ||
return [2 /*return*/, Promise.all(viewFilenameToCtrlFilenames.map(function (ctrlNames, viewName) { return Promise.all(ctrlNames.map(function (ctrlName) { return processControllerView(prjSettings, ctrlName, viewName, prjSettings.ngFilters, prjSettings.tagDirectives, prjSettings.attributeDirectives); }).toArray()); }).toArray())]; | ||
} | ||
@@ -156,0 +154,0 @@ }); |
/// <reference types="parsimmon" /> | ||
import { Stack } from "immutable"; | ||
import * as P from "parsimmon"; | ||
import { ScopeInfo } from "./controller-parser"; | ||
import { NgScope } from "./view-parser"; | ||
/** | ||
* Helper functions to assist with code generation. | ||
* Scope info used by ng-typeview. Directive authors can | ||
* consider it an opaque type (type synonym on purpose | ||
* so typedoc doesn't document it). | ||
*/ | ||
export interface CodegenHelpers { | ||
export declare type NgScopeInfo = { | ||
readonly soFar: Stack<NgScope>; | ||
curScopeVars: string[]; | ||
}; | ||
/** | ||
* Companion object to assist typescript code generation. | ||
* It manages the scope behind the scenes so its state | ||
* changes as you call its methods. | ||
* If you do not let it know about variables you declare | ||
* in your typescript, there will be issues of '$scope.' | ||
* being prepended to the generated code when it shouldn't be. | ||
*/ | ||
export declare class CodegenHelper { | ||
readonly ngScopeInfo: NgScopeInfo; | ||
private getNewVarName; | ||
constructor(scope: Stack<NgScope>, getNewVarName: () => string); | ||
/** | ||
@@ -12,2 +30,5 @@ * Add scope accessors to a JS expression. For instance, | ||
* has a field named 'data' | ||
* NOTE using an instance function so this will be properly | ||
* bound when used as a callback => | ||
* https://github.com/Microsoft/TypeScript/wiki/'this'-in-TypeScript#use-instance-functions | ||
* @param js the javascript from the angular view | ||
@@ -21,3 +42,3 @@ * @returns new source with the scope accessors added | ||
*/ | ||
getNewVariableName: () => string; | ||
getNewVariableName(): string; | ||
/** | ||
@@ -31,3 +52,14 @@ * Generate a TS expression declaring a variable of | ||
*/ | ||
registerVariable: (type: string, val: string) => string; | ||
declareVariable(type: string, val: string): string; | ||
/** | ||
* You must register a variable name when you declare a variable | ||
* while generating code without going through [[generateVariable]] | ||
* or [[getNewVariableName]]. | ||
* Otherwise generation will add a `$scope.` accessor to it even though | ||
* it shouldn't. | ||
* Since `registerVariable` will return you the variable name you gave, | ||
* you can use this function as a pass-through, just wrap your var | ||
* name with this call. | ||
*/ | ||
registerVariable(name: string): string; | ||
} | ||
@@ -93,3 +125,3 @@ /** | ||
*/ | ||
export declare function filterExpressionToTypescript(expr: string, codegenHelpers: CodegenHelpers): string; | ||
export declare function filterExpressionToTypescript(expr: string, codegenHelpers: CodegenHelper): string; | ||
/** | ||
@@ -105,3 +137,3 @@ * Convert a parsed angular filter expression to typescript code. | ||
*/ | ||
export declare function ngFilterExpressionToTypeScriptStandalone(ngFilterExpr: NgFilterExpression, codegenHelpers: CodegenHelpers): string; | ||
export declare function ngFilterExpressionToTypeScriptStandalone(ngFilterExpr: NgFilterExpression, codegenHelpers: CodegenHelper): string; | ||
/** | ||
@@ -123,6 +155,6 @@ * Convert a parsed angular filter expression to typescript code. | ||
*/ | ||
export declare function ngFilterExpressionToTypeScriptEmbedded(ngFilterExpr: NgFilterExpression, codegenHelpers: CodegenHelpers): string; | ||
export declare function ngFilterExpressionToTypeScriptEmbedded(ngFilterExpr: NgFilterExpression, codegenHelpers: CodegenHelper): string; | ||
/** | ||
* @hidden | ||
*/ | ||
export declare function addScopeAccessors(input: string, scopeInfo: ScopeInfo): string; | ||
export declare function addScopeAccessors(scopes: Stack<NgScope>, input: string): string; |
@@ -6,2 +6,75 @@ "use strict"; | ||
/** | ||
* Companion object to assist typescript code generation. | ||
* It manages the scope behind the scenes so its state | ||
* changes as you call its methods. | ||
* If you do not let it know about variables you declare | ||
* in your typescript, there will be issues of '$scope.' | ||
* being prepended to the generated code when it shouldn't be. | ||
*/ | ||
var CodegenHelper = (function () { | ||
function CodegenHelper(scope, getNewVarName) { | ||
var _this = this; | ||
/** | ||
* Add scope accessors to a JS expression. For instance, | ||
* "data.name" will become "$scope.data.name" if the scope | ||
* has a field named 'data' | ||
* NOTE using an instance function so this will be properly | ||
* bound when used as a callback => | ||
* https://github.com/Microsoft/TypeScript/wiki/'this'-in-TypeScript#use-instance-functions | ||
* @param js the javascript from the angular view | ||
* @returns new source with the scope accessors added | ||
*/ | ||
this.addScopeAccessors = function (js) { | ||
return addScopeAccessors(_this.ngScopeInfo.soFar.unshift({ | ||
// hardcoding 1...I just need to let addScopeAccessors | ||
// know about these local variables. a bit of a hack. | ||
xpathDepth: 1, | ||
closeSource: function () { return ""; }, | ||
variables: _this.ngScopeInfo.curScopeVars | ||
}), js); | ||
}; | ||
this.ngScopeInfo = { soFar: scope, curScopeVars: [] }; | ||
this.getNewVarName = getNewVarName; | ||
} | ||
/** | ||
* Get a new unique variable name | ||
* @returns new unique variable name | ||
*/ | ||
CodegenHelper.prototype.getNewVariableName = function () { | ||
return this.registerVariable(this.getNewVarName()); | ||
}; | ||
/** | ||
* Generate a TS expression declaring a variable of | ||
* the type and value that you give. Will automatically call | ||
* `addScopeAccessors` on the value. | ||
* @param type typescript type for the variable | ||
* @param val value for the variable | ||
* @returns typescript expression that registers the variable, as string. | ||
*/ | ||
CodegenHelper.prototype.declareVariable = function (type, val) { | ||
if (val.length > 0) { | ||
return "const " + this.getNewVariableName() + ": " + type + " = " + this.addScopeAccessors(val) + ";"; | ||
} | ||
else { | ||
return ""; // angular tolerates empty attributes and ignores them, for instance ng-submit="" | ||
} | ||
}; | ||
/** | ||
* You must register a variable name when you declare a variable | ||
* while generating code without going through [[generateVariable]] | ||
* or [[getNewVariableName]]. | ||
* Otherwise generation will add a `$scope.` accessor to it even though | ||
* it shouldn't. | ||
* Since `registerVariable` will return you the variable name you gave, | ||
* you can use this function as a pass-through, just wrap your var | ||
* name with this call. | ||
*/ | ||
CodegenHelper.prototype.registerVariable = function (name) { | ||
this.ngScopeInfo.curScopeVars.push(name); | ||
return name; | ||
}; | ||
return CodegenHelper; | ||
}()); | ||
exports.CodegenHelper = CodegenHelper; | ||
/** | ||
* @hidden | ||
@@ -113,3 +186,3 @@ */ | ||
if (ngFilterExpr.filterCalls.length === 0) { | ||
return codegenHelpers.registerVariable("any", ngFilterExpr.expression); | ||
return codegenHelpers.declareVariable("any", ngFilterExpr.expression); | ||
} | ||
@@ -145,5 +218,5 @@ return ngFilterExpr.filterCalls.reduce(wrapFilterCall(codegenHelpers.addScopeAccessors), codegenHelpers.addScopeAccessors(ngFilterExpr.expression)) + ";"; | ||
*/ | ||
function addScopeAccessors(input, scopeInfo) { | ||
function addScopeAccessors(scopes, input) { | ||
var sourceFile = ts.createSourceFile("", input, ts.ScriptTarget.ES2016, /*setParentNodes */ true); | ||
return sourceFile.statements.map(stmtAddScopeAccessors(scopeInfo)).join(";\n"); | ||
return sourceFile.statements.map(stmtAddScopeAccessors(scopes)).join(";\n"); | ||
} | ||
@@ -156,28 +229,28 @@ exports.addScopeAccessors = addScopeAccessors; | ||
ts.SyntaxKind.FalseKeyword]); | ||
function stmtAddScopeAccessors(scopeInfo) { | ||
function stmtAddScopeAccessors(scopes) { | ||
return function (node) { | ||
if (node.kind === ts.SyntaxKind.ExpressionStatement) { | ||
return stmtAddScopeAccessors(scopeInfo)(node.expression); | ||
return stmtAddScopeAccessors(scopes)(node.expression); | ||
} | ||
else if (node.kind === ts.SyntaxKind.PropertyAccessExpression) { | ||
var prop = node; | ||
return stmtAddScopeAccessors(scopeInfo)(prop.expression) + "." + prop.name.getText(); | ||
return stmtAddScopeAccessors(scopes)(prop.expression) + "." + prop.name.getText(); | ||
} | ||
else if (node.kind === ts.SyntaxKind.Identifier) { | ||
return addScopePrefixIfNeeded(scopeInfo, node.getText()); | ||
return addScopePrefixIfNeeded(scopes, node.getText()); | ||
} | ||
else if (node.kind === ts.SyntaxKind.PrefixUnaryExpression) { | ||
var op = node; | ||
return ts.tokenToString(op.operator) + stmtAddScopeAccessors(scopeInfo)(op.operand); | ||
return ts.tokenToString(op.operator) + stmtAddScopeAccessors(scopes)(op.operand); | ||
} | ||
else if (node.kind === ts.SyntaxKind.CallExpression) { | ||
var expr = node; | ||
return addScopePrefixIfNeeded(scopeInfo, expr.expression.getText()) + "(" + | ||
expr.arguments.map(stmtAddScopeAccessors(scopeInfo)).join(", ") + ")"; | ||
return addScopePrefixIfNeeded(scopes, expr.expression.getText()) + "(" + | ||
expr.arguments.map(stmtAddScopeAccessors(scopes)).join(", ") + ")"; | ||
} | ||
else if (node.kind === ts.SyntaxKind.BinaryExpression) { | ||
var expr = node; | ||
return stmtAddScopeAccessors(scopeInfo)(expr.left) | ||
return stmtAddScopeAccessors(scopes)(expr.left) | ||
+ " " + expr.operatorToken.getText() + " " | ||
+ stmtAddScopeAccessors(scopeInfo)(expr.right); | ||
+ stmtAddScopeAccessors(scopes)(expr.right); | ||
} | ||
@@ -187,5 +260,5 @@ else if (node.kind === ts.SyntaxKind.ElementAccessExpression) { | ||
var argValue = acc.argumentExpression | ||
? stmtAddScopeAccessors(scopeInfo)(acc.argumentExpression) | ||
? stmtAddScopeAccessors(scopes)(acc.argumentExpression) | ||
: ""; | ||
return stmtAddScopeAccessors(scopeInfo)(acc.expression) + | ||
return stmtAddScopeAccessors(scopes)(acc.expression) + | ||
"[" + argValue + "]"; | ||
@@ -195,5 +268,5 @@ } | ||
var cond = node; | ||
return stmtAddScopeAccessors(scopeInfo)(cond.condition) + " ? " + | ||
stmtAddScopeAccessors(scopeInfo)(cond.whenTrue) + " : " + | ||
stmtAddScopeAccessors(scopeInfo)(cond.whenFalse); | ||
return stmtAddScopeAccessors(scopes)(cond.condition) + " ? " + | ||
stmtAddScopeAccessors(scopes)(cond.whenTrue) + " : " + | ||
stmtAddScopeAccessors(scopes)(cond.whenFalse); | ||
} | ||
@@ -203,10 +276,10 @@ else if (node.kind === ts.SyntaxKind.Block) { | ||
var block = node; | ||
return block.getChildren().map(stmtAddScopeAccessors(scopeInfo)).join(""); | ||
return block.getChildren().map(stmtAddScopeAccessors(scopes)).join(""); | ||
} | ||
else if (node.kind === ts.SyntaxKind.LabeledStatement) { | ||
var lStat = node; | ||
return lStat.label.text + ": " + stmtAddScopeAccessors(scopeInfo)(lStat.statement); | ||
return lStat.label.text + ": " + stmtAddScopeAccessors(scopes)(lStat.statement); | ||
} | ||
else if (node.kind === ts.SyntaxKind.SyntaxList) { | ||
return node.getChildren().map(stmtAddScopeAccessors(scopeInfo)).join(""); | ||
return node.getChildren().map(stmtAddScopeAccessors(scopes)).join(""); | ||
} | ||
@@ -223,3 +296,3 @@ else if (nodeKindPassthroughList.contains(node.kind)) { | ||
} | ||
function addScopePrefixIfNeeded(scopeInfo, expression) { | ||
function addScopePrefixIfNeeded(scopes, expression) { | ||
// extract the field name from the expression, which can be... | ||
@@ -230,13 +303,11 @@ // data.user.getName(), or getName() or things like that. | ||
var fieldName = expression.replace(/[\(\.].*$/, ""); | ||
// is the field name present in the scope declaration? | ||
if (scopeInfo.fieldNames.indexOf(fieldName) >= 0) { | ||
// is the field name present in any of the parent scopes? | ||
if (scopes.find(function (s) { return s.variables.indexOf(expression) >= 0; })) { | ||
// YES => read it from there. | ||
return "$scope." + expression; | ||
return expression; | ||
} | ||
else { | ||
// NO => the expression is accessed from elsewhere than the scope | ||
// (parent loop in the view, global namespace...) | ||
return expression; | ||
return "$scope." + expression; | ||
} | ||
} | ||
//# sourceMappingURL=view-ngexpression-parser.js.map |
@@ -5,2 +5,10 @@ /// <reference types="es6-promise" /> | ||
/** | ||
* @hidden | ||
*/ | ||
export interface NgScope { | ||
readonly xpathDepth: number; | ||
readonly closeSource: () => string; | ||
readonly variables: string[]; | ||
} | ||
/** | ||
* http://stackoverflow.com/a/16184477/516188 | ||
@@ -13,2 +21,2 @@ * @hidden | ||
*/ | ||
export declare function parseView(fileName: string, addScopeAccessors: (js: string) => string, tagDirectiveHandlers: List<TagDirectiveHandler>, attrDirectiveHandlers: List<AttributeDirectiveHandler>): Promise<string>; | ||
export declare function parseView(resolveImportsAsNonScope: boolean, fileName: string, importNames: string[], tagDirectiveHandlers: List<TagDirectiveHandler>, attrDirectiveHandlers: List<AttributeDirectiveHandler>): Promise<string>; |
@@ -39,20 +39,21 @@ "use strict"; | ||
exports.normalizeTagAttrName = normalizeTagAttrName; | ||
function getHandler(fileName, addScopeAccessors, tagDirectiveHandlers, attrDirectiveHandlers, f) { | ||
function handleDirectiveResponses(xpath, codegenHelpers, resps) { | ||
return resps | ||
.filter(function (x) { return x.closeSource !== undefined || | ||
codegenHelpers.ngScopeInfo.curScopeVars.length > 0; }) | ||
.map(function (r) { return ({ | ||
xpathDepth: xpath.size, | ||
closeSource: r.closeSource || (function () { return ""; }), | ||
variables: codegenHelpers.ngScopeInfo.curScopeVars | ||
}); }); | ||
} | ||
function getHandler(fileName, defaultScope, tagDirectiveHandlers, attrDirectiveHandlers, f) { | ||
var expressions = ""; | ||
var xpath = immutable_1.Stack(); | ||
var activeLoops = immutable_1.Stack(); | ||
var activeScopes = immutable_1.Stack([{ | ||
xpathDepth: 0, | ||
closeSource: function () { return ""; }, | ||
variables: defaultScope | ||
}]); | ||
var getNewVariableName = function () { return "___x" + v++; }; | ||
var registerVariable = function (type, val) { | ||
if (val.length > 0) { | ||
return "const " + getNewVariableName() + ": " + type + " = " + addScopeAccessors(val) + ";"; | ||
} | ||
else { | ||
return ""; // angular tolerates empty attributes and ignores them, for instance ng-submit="" | ||
} | ||
}; | ||
var codegenHelpers = { | ||
registerVariable: registerVariable, | ||
addScopeAccessors: addScopeAccessors, | ||
getNewVariableName: getNewVariableName | ||
}; | ||
return { | ||
@@ -67,26 +68,17 @@ onopentag: function (_name, _attribs) { | ||
// work on tag handlers | ||
var codegenHelpersTag = new view_ngexpression_parser_1.CodegenHelper(activeScopes, getNewVariableName); | ||
var relevantTagHandlers = tagDirectiveHandlers | ||
.filter(function (d) { return d.forTags.length === 0 || d.forTags.indexOf(name) >= 0; }); | ||
var tagDirectiveResps = listKeepDefined(relevantTagHandlers | ||
.map(function (handler) { return handler.handleTag(name, attribs, codegenHelpers); })); | ||
expressions += tagDirectiveResps | ||
.map(function (x) { return x.source; }).join(""); | ||
tagDirectiveResps | ||
.filter(function (x) { return x.closeSource !== undefined; }) | ||
.forEach(function (r) { return activeLoops = activeLoops.unshift({ | ||
xpathDepth: xpath.size, | ||
closeSource: requireDefined(r.closeSource) | ||
}); }); | ||
var tagDirectiveResps = listKeepDefined(relevantTagHandlers.map(function (handler) { return handler.handleTag(name, attribs, codegenHelpersTag); })); | ||
expressions += tagDirectiveResps.map(function (x) { return x.source; }).join(""); | ||
activeScopes = activeScopes.unshiftAll(handleDirectiveResponses(xpath, codegenHelpersTag, tagDirectiveResps)); | ||
var _loop_1 = function (attrName) { | ||
var codegenHelpersAttr = new view_ngexpression_parser_1.CodegenHelper(activeScopes, getNewVariableName); | ||
var attrValue = attribs[attrName]; | ||
var attrDirectiveResps = listKeepDefined(attrDirectiveHandlers | ||
.filter(function (d) { return d.forAttributes.indexOf(attrName) >= 0; }) | ||
.map(function (handler) { return handler.handleAttribute(attrName, attrValue, codegenHelpers); })); | ||
.map(function (handler) { return handler.handleAttribute(attrName, attrValue, codegenHelpersAttr); })); | ||
expressions += attrDirectiveResps.map(function (x) { return x.source; }).join(""); | ||
listKeepDefined(attrDirectiveResps | ||
.map(function (x) { return x.closeSource; })) | ||
.forEach(function (closeSrc) { | ||
activeLoops = activeLoops.unshift({ xpathDepth: xpath.size, closeSource: closeSrc }); | ||
}); | ||
expressions += extractInlineExpressions(attrValue, codegenHelpers); | ||
activeScopes = activeScopes.unshiftAll(handleDirectiveResponses(xpath, codegenHelpersAttr, attrDirectiveResps)); | ||
expressions += extractInlineExpressions(attrValue, codegenHelpersAttr); | ||
}; | ||
@@ -103,8 +95,9 @@ // work on attribute handlers | ||
xpath = xpath.shift(); | ||
while (activeLoops.first() && activeLoops.first().xpathDepth > xpath.size) { | ||
expressions += activeLoops.first().closeSource(); | ||
activeLoops = activeLoops.shift(); | ||
while (activeScopes.first() && activeScopes.first().xpathDepth > xpath.size) { | ||
expressions += activeScopes.first().closeSource(); | ||
activeScopes = activeScopes.shift(); | ||
} | ||
}, | ||
ontext: function (text) { | ||
var codegenHelpers = new view_ngexpression_parser_1.CodegenHelper(activeScopes, getNewVariableName); | ||
expressions = expressions.concat(extractInlineExpressions(text, codegenHelpers)); | ||
@@ -174,5 +167,6 @@ }, | ||
*/ | ||
function parseView(fileName, addScopeAccessors, tagDirectiveHandlers, attrDirectiveHandlers) { | ||
function parseView(resolveImportsAsNonScope, fileName, importNames, tagDirectiveHandlers, attrDirectiveHandlers) { | ||
var defaultScope = resolveImportsAsNonScope ? importNames : []; | ||
return new Promise(function (resolve, reject) { | ||
var parser = new htmlparser2_1.Parser(getHandler(fileName, addScopeAccessors, tagDirectiveHandlers, attrDirectiveHandlers, resolve)); | ||
var parser = new htmlparser2_1.Parser(getHandler(fileName, defaultScope, tagDirectiveHandlers, attrDirectiveHandlers, resolve)); | ||
parser.write(fs_1.readFileSync(fileName).toString()); | ||
@@ -179,0 +173,0 @@ parser.done(); |
@@ -116,4 +116,3 @@ "use strict"; | ||
" maxlength: number;\n" + | ||
" }", scopeInfo.scopeInfo.some().contents); | ||
assert.deepEqual(["showDiv", "showText", "data", "triggerAction", "user", "maxlength"], scopeInfo.scopeInfo.some().fieldNames); | ||
" }", scopeInfo.scopeInfo.some()); | ||
assert.deepEqual(["type STR = string;", "type INT = number;"], scopeInfo.typeAliases); | ||
@@ -120,0 +119,0 @@ assert.deepEqual(["import Aa = api.Aa;", "import Bb = api.Bb;"], scopeInfo.imports); |
"use strict"; | ||
var assert = require("assert"); | ||
var immutable_1 = require("immutable"); | ||
var view_ngexpression_parser_1 = require("../src/view-ngexpression-parser"); | ||
describe("addScopeAccessors", function () { | ||
it("should add $scope properly", function () { | ||
var fakeScopeInfo = { | ||
contents: "", | ||
fieldNames: ["data", "wasProvidedWorkbook", "info", "movieInfo", | ||
"selectedScreen", "getSelectedImage", "fType", "idx"] | ||
}; | ||
var assertScopeAcc = function (expected, input) { return assert.equal(expected, view_ngexpression_parser_1.addScopeAccessors(input, fakeScopeInfo)); }; | ||
var fakeScopeInfo = immutable_1.Stack([ | ||
{ | ||
xpathDepth: 1, | ||
closeSource: function () { return ""; }, | ||
variables: [] | ||
}, | ||
{ | ||
xpathDepth: 2, | ||
closeSource: function () { return ""; }, | ||
variables: [] | ||
} | ||
]); | ||
var assertScopeAcc = function (expected, input) { return assert.equal(expected, view_ngexpression_parser_1.addScopeAccessors(fakeScopeInfo, input)); }; | ||
assertScopeAcc("$scope.data.value", "data.value"); | ||
@@ -13,0 +21,0 @@ assertScopeAcc("!$scope.wasProvidedWorkbook()", "!wasProvidedWorkbook()"); |
{ | ||
"name": "ng-typeview", | ||
"version": "0.0.4", | ||
"version": "0.0.5", | ||
"description": "library to enable type-checking of angular views when using typescript", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
@@ -35,2 +35,4 @@ # ng-typeview | ||
(ng-typeview searches for an interface named `Scope` in the controller) | ||
In the matching view, ng-typeview searches for expressions like `{{title}}`, | ||
@@ -106,6 +108,4 @@ or `ng-if='showTitle'`, and similar. | ||
* the API is still changing very often | ||
* was tested only against two projects from a single company for now | ||
* won't detect scopes with inheritance (the `Scope` in the controller must inherit | ||
from `ng.IScope`, if you use inheritance with your scopes, ng-typeview won't work, | ||
for now) | ||
* incomplete mapping of standard directives & filters (ng-typeview does not support | ||
@@ -112,0 +112,0 @@ all of the syntaxes of `ng-repeat` for instance.. Pull requests welcome :-) ) |
@@ -70,6 +70,3 @@ import * as assert from 'assert' | ||
" maxlength: number;\n" + | ||
" }", scopeInfo.scopeInfo.some().contents); | ||
assert.deepEqual( | ||
["showDiv", "showText", "data", "triggerAction", "user", "maxlength"], | ||
scopeInfo.scopeInfo.some().fieldNames); | ||
" }", scopeInfo.scopeInfo.some()); | ||
assert.deepEqual(["type STR = string;", "type INT = number;"], scopeInfo.typeAliases); | ||
@@ -76,0 +73,0 @@ assert.deepEqual(["import Aa = api.Aa;", "import Bb = api.Bb;"], scopeInfo.imports); |
import * as assert from 'assert' | ||
import {Stack} from "immutable"; | ||
import {addScopeAccessors} from '../src/view-ngexpression-parser' | ||
import {ScopeInfo} from "../src/controller-parser" | ||
import {NgScope} from "../src/view-parser" | ||
describe("addScopeAccessors", () => { | ||
it ("should add $scope properly", () => { | ||
const fakeScopeInfo: ScopeInfo = { | ||
contents: "", | ||
fieldNames: ["data", "wasProvidedWorkbook", "info", "movieInfo", | ||
"selectedScreen", "getSelectedImage", "fType", "idx"] | ||
}; | ||
const assertScopeAcc = (expected:string,input:string) => assert.equal(expected, addScopeAccessors(input, fakeScopeInfo)); | ||
const fakeScopeInfo: Stack<NgScope> = Stack([ | ||
{ | ||
xpathDepth: 1, | ||
closeSource: ()=>"", | ||
variables: [] | ||
}, | ||
{ | ||
xpathDepth: 2, | ||
closeSource: ()=>"", | ||
variables: [] | ||
} | ||
]); | ||
const assertScopeAcc = (expected:string,input:string) => assert.equal( | ||
expected, addScopeAccessors(fakeScopeInfo, input)); | ||
assertScopeAcc("$scope.data.value", "data.value"); | ||
@@ -14,0 +23,0 @@ assertScopeAcc("!$scope.wasProvidedWorkbook()", "!wasProvidedWorkbook()"); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
163346
2240