@extjs/sencha-cmd-linux-32
Version:
Productivity and performance optimization tool for building applications with Sencha Ext JS and Sencha Touch.
557 lines (493 loc) • 16.8 kB
JavaScript
"use strict";
var Fashion = require('./export/Base.js'),
Base = Fashion.Base;
var Visitor = require('./Visitor.js');
var regexes = {
replaces: /^\s*?\/\/# fashion replaces\s+?(.*?)$/,
};
class SassVariableAttributes {
}
Fashion.apply(SassVariableAttributes.prototype, {
global: false,
"default": false,
dynamic: false
});
class SassVariable extends Base {
constructor(cfg) {
super(cfg);
this.references = this.references || [];
}
elevateDynamics(variables, elevator) {
var me = this,
dynamicWas = me.dynamic;
me.dynamic = true;
if (!dynamicWas) {
if (this.enableElevationWarning) {
Fashion.warn("Elevating variable '" + me.name + "' to dynamic", me.node);
Fashion.warn("\tcaused by", elevator.node);
}
me.elevationCause = elevator;
variables.push(me);
}
me.references.forEach((ref) => {
var variable = me.map[ref];
if (variable && !variable.dynamic) {
variable.elevateDynamics(variables, me);
}
});
}
verify() {
if (this.dynamic) {
if (this.previous) {
if (!this.previous.dynamic) {
Fashion.error([
'Cannot redefine ',
this.name,
' as dynamic'
].join(''));
Fashion.error('\tfrom ', this.previous.getNode());
Fashion.error('\t at ', this.getNode());
this.preprocessor.errors += 1;
return false;
}
}
}
return true;
}
elevated() {
return this.dynamic && !this.attributes.dynamic;
}
getNode() {
var node = this.node;
return node;
}
}
Fashion.apply(SassVariable.prototype, {
name: null,
previous: null,
node: null,
dynamic: false,
references: null,
attributes: null,
isGlobal: false,
map: null,
elevationCause: null,
processed: false,
preprocessor: null,
enableElevationWarning: false,
scope: undefined
});
class Preprocessor extends Visitor {
constructor(cfg) {
super(cfg);
this.reset();
}
reset() {
this.replacedVariable = null;
this.variables = {};
this.functions = [];
this.currentVariable = null;
this.functionDeclarations = {};
this.mixinDeclarations = {};
this.registeredDeclarations = null;
this.replacesMap = {};
this.currentScope = {
variables: {},
isGlobal: true
};
}
handleFunc(node, collection) {
var func = node.func || node.name,
name = Fashion.getJsName(func.id || func.value),
parameters = Preprocessor.getFunctionCallArgs(func);
collection[name] = {
parameters: parameters,
ast: node,
scope: this.currentScope
};
for (var i = 0; i < parameters.length; i++) {
var param = parameters[i];
name = Fashion.getJsName(param.name);
this.currentScope.variables[name] = new SassVariable({
name: name,
node: param.arg,
isGlobal: false,
dynamic: false,
map: this.currentScope.variables,
preprocessor: this,
enableElevationWarning: this.enableElevationWarning,
scope: this.currentScope
})
}
}
Mixin(node) {
var scopeWas = this.currentScope;
this.currentScope = {
prev: scopeWas,
variables: {}
};
this.handleFunc(node, this.mixinDeclarations);
node.descend(this);
this.currentScope = scopeWas;
}
Function(node) {
var isGlobal = this.nodeStack.length == 1;
var scopeWas = this.currentScope;
if (isGlobal) {
this.functions.push(node);
}
this.currentScope = {
prev: scopeWas,
variables: {}
};
this.handleFunc(node, this.functionDeclarations);
node.descend(this);
this.currentScope = scopeWas;
}
getVariable (name) {
var scope = this.currentScope,
variable;
while (scope) {
variable = scope.variables[name];
if (variable) {
break;
}
scope = scope.prev;
}
return variable;
}
Variable(node) {
if (this.currentVariable) {
var refName = Fashion.getJsName(node.name),
variable = this.getVariable(refName);
if (!variable || variable.scope.isGlobal) {
this.currentVariable.references.push(refName);
}
}
}
FunctionCall (node) {
if (this.currentVariable) {
node.descend(this);
var id = Fashion.getJsName(node.id),
entry = this.functionDeclarations[id];
if (entry) {
// re-visit the function body for this node, as that call will
// possibly contain references to other global variables
var scopeWas = this.currentScope;
this.currentScope = entry.scope;
this.visit(entry.ast.statements);
this.currentScope = scopeWas;
}
}
}
Comment(comment) {
var replaces = regexes.replaces,
match = replaces.exec(comment);
if (comment === '//# fashion warn -elevation') {
this.enableElevationWarning = false;
}
else if (comment === '//# fashion warn +elevation') {
this.enableElevationWarning = true;
}
else if (match) {
this.replacedVariable = match[1];
}
}
VariableAssignment(node) {
var replaces = this.replacedVariable,
replacesEntry = this.replacesMap[Fashion.getJsName(node.name)],
origName = node.name,
nodeName = (replacesEntry && replacesEntry.name) || origName,
name = Fashion.getJsName(nodeName),
currVariable = this.variables[name],
varWas = this.currentVariable,
bangGlobal = !!node.global,
bangDynamic = !!node.dynamic,
bangDefault = !!node.default,
isGlobalVar = this.nodeStack.length === 1,
variable, value, funcName;
if (replacesEntry) {
Fashion.warn("Using new variable " + replacesEntry.name + " for deprecated variable " + node.name, node);
}
node.name = nodeName;
value = node.value;
if (value.type === 'FunctionCall') {
funcName = value.id || value.value;
if (funcName === 'dynamic') {
bangDynamic = true;
value.visitTarget = value.args;
if (value.args.items && value.args.items.length === 1) {
value.visitTarget = value.args.items[0];
}
}
}
variable = new SassVariable({
name: name,
nodeName: origName,
node: node,
previous: currVariable,
attributes: {
global: bangGlobal,
"default": bangDefault,
dynamic: bangDynamic
},
isGlobal: isGlobalVar,
dynamic: (currVariable && currVariable.dynamic) || bangDynamic,
map: this.variables,
preprocessor: this,
enableElevationWarning: this.enableElevationWarning,
scope: this.currentScope,
replaces: replaces || (currVariable && currVariable.replaces)
});
if (variable.replaces) {
var replacesName = Fashion.getJsName(variable.replaces);
var current = this.replacesMap[replacesName];
if (current && variable.name != current.name) {
Fashion.warn("Variable " + current.name + " already replaces variable " + variable.replaces, node);
}
this.replacesMap[replacesName] = variable;
}
this.replacedVariable = null;
this.currentScope.variables[name] = variable;
if (isGlobalVar || bangGlobal) {
if (!!node.dynamic) {
Fashion.warn("Use of !dynamic has been deprecated", node);
Fashion.warn("Use dynamic() function instead.");
}
variable.verify();
this.variables[name] = variable;
}
else {
node.descend(this);
return false;
}
this.currentVariable = variable;
node.descend(this);
this.currentVariable = varWas;
}
getRuntime() {
return this.runtime;
}
getRegisteredFunctions() {
if (!this.registeredFunctions) {
this.registeredFunctions =
(this.runtime && this.runtime.getRegisteredFunctions()) || {};
}
return this.registeredFunctions;
}
loadRegisteredFunctionArgs() {
if (!this.registeredDeclarations) {
var registered = this.getRegisteredFunctions(),
funcArgsRx = this.funcArgsRx,
paramsMap = {},
name, func, src, params, match, args, i, argName;
for (name in registered) {
func = registered[name];
if (Fashion.isFunction(func)) {
src = func + '';
params = [];
match = funcArgsRx.exec(src);
if (match && match[2]) {
args = (match[2] && match[2].split(/,/g)) || [];
for (i = 0; i < args.length; i++) {
argName = args[i].trim();
params.push({
name: argName,
position: i
});
}
}
paramsMap[name] = params;
}
}
this.registeredDeclarations = paramsMap;
}
}
preprocess(node, skipRegistrations) {
this.reset();
this.visit(node);
if (!skipRegistrations) {
this.loadRegisteredFunctionArgs();
}
if (this.errors) {
Fashion.raise([
'Encountered ',
this.errors,
' error(s) during preprocessing.'
].join(''));
}
}
getVariables() {
return this.variables;
}
generateCycleError(stack, variables) {
var referenceTrace = [],
r, trace;
for (r = 0; r < stack.length; r++) {
trace = [
stack[r],
" => ",
variables[stack[r]].node.file,
":",
variables[stack[r]].node.lineNumber
].join('');
referenceTrace.push(trace);
}
var msg = [
"Variable Cycle detected in variable : ",
referenceTrace.join('\n')
].join('\n');
Fashion.error(msg);
Fashion.raise(msg);
}
topoSort(variable, variables, sorted, processed, stack) {
processed = processed || {};
sorted = sorted || [];
stack = stack || [];
var me = this,
name = variable.name,
refs, ref, r, refVariable;
function processRef (ref) {
var refVariable = variables[ref];
if (refVariable) {
if (refVariable.dynamic || me.sortAllVariables) {
me.topoSort(refVariable, variables, sorted, processed, stack);
return true;
}
}
return false;
}
if (processed[name] !== true) {
stack.push(name);
if (processed[name] === 'processing') {
this.generateCycleError(stack, variables);
}
processed[name] = 'processing';
refs = variable.references;
for (r = 0; r < refs.length; r++) {
ref = refs[r];
if (!processRef(ref)) {
if (this.replacesMap[ref]) {
var entry = this.replacesMap[ref],
ref = Fashion.getJsName(entry.name);
processRef(ref);
}
}
}
sorted.push(variable);
processed[name] = true;
stack.pop();
}
}
getDynamics() {
var variables = this.sortAllVariables
? this.currentScope.variables
: this.variables,
variableNames = Object.keys(variables),
dynamics = [],
sorted = [],
variable, name, dynamic, d, n;
// push the dynamic flag to all variables referenced
for (n = 0; n < variableNames.length; n++) {
name = variableNames[n];
variable = variables[name];
if (variable.dynamic) {
variable.elevateDynamics(dynamics);
dynamics.push(variable);
}
}
for (d = 0; d < dynamics.length; d++) {
dynamic = dynamics[d];
this.topoSort(dynamic, variables, sorted);
}
return sorted;
}
getDynamicsMap() {
var dynamicVariables = this.getDynamics(),
map = {},
i, variable;
for (i = 0; i < dynamicVariables.length; i++) {
variable = dynamicVariables[i];
map[variable.name] = variable;
}
return map;
}
getSortedDynamicAstNodes() {
var sortedVariables = this.getDynamics(),
sortedAst = [], i;
for (i = 0; i < sortedVariables.length; i++) {
sortedAst.push(sortedVariables[i].getNode());
}
return sortedAst;
}
loadPreprocessorCache(preprocessor) {
this.functionDeclarations = preprocessor.functionDeclarations;
this.mixinDeclarations = preprocessor.mixinDeclarations;
this.registeredDeclarations = preprocessor.registeredDeclarations;
this.registeredFunctions = preprocessor.registeredFunctions;
this.currentScope = preprocessor.currentScope;
}
static loadArgsArray(args) {
if (args && (args.type === 'SelectorList' || args.type === 'List')) {
args = args.items;
}
if (!Array.isArray(args)) {
args = [args];
}
return args;
}
static getFunctionCallArgs(func) {
var args = this.loadArgsArray(func.args),
parameters = [],
arg, a, param;
for (a = 0; a < args.length; a++) {
arg = args[a];
if (arg) {
param = {
name: arg.variable || arg.name,
value: arg,
position: a,
varArgs: arg.varArgs
};
parameters.push(param);
if (arg.variable) {
if (arg.token) {
var scanner = arg.token.scanner,
style = scanner && scanner.style,
start = arg.token.startIdx,
end = arg.token.idx;
param.default = style.substring(start, end).trim();
}
else if (arg.type == 'List') {
var first = arg.items[0],
last = arg.items[arg.items.length - 1],
startTok = first && first.token,
endTok = last && last.token;
if (startTok && endTok) {
var style = startTok.scanner.style,
start = startTok.startIdx,
end = endTok.idx;
param.default = style.substring(start, end).trim();
}
}
}
}
}
return parameters;
}
}
Fashion.apply(Preprocessor.prototype, {
functionDeclarations: null,
mixinDeclarations: null,
registeredDeclarations: null,
registeredFunctions: null,
runtime: null,
currentVariable: null,
functions: null,
variables: null,
errors: 0,
enableElevationWarning: true,
funcArgsRx: /function\s*?(.*?)\((.*?)\)\s*?\{/,
sortAllVariables: false
});
module.exports = Preprocessor;