Innovenergy_trunk/frontend/node_modules/testcafe-legacy-api/lib/compiler/legacy/compiler.js

427 lines
18 KiB
JavaScript
Raw Normal View History

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = __importDefault(require("path"));
const fs_1 = __importDefault(require("fs"));
const util_1 = __importDefault(require("util"));
const async_1 = __importDefault(require("async"));
const strip_bom_1 = __importDefault(require("strip-bom"));
const pinkie_1 = __importDefault(require("pinkie"));
const uglify_js_1 = require("../tools/uglify-js/uglify-js");
const uglify_js_2 = require("../tools/uglify-js/uglify-js");
const Common = __importStar(require("./common"));
const Ast = __importStar(require("./ast"));
const CallAnalyzer = __importStar(require("./analysis/call_analyzer"));
const steps_analyzer_1 = __importDefault(require("./analysis/steps_analyzer"));
const ErrCodes = __importStar(require("./err_codes"));
const promisify_1 = __importDefault(require("../../utils/promisify"));
var readFile = (0, promisify_1.default)(fs_1.default.readFile);
//Util
//NOTE: this is a version of splice which can operate with array of the injectable items
function multySplice(arr, index, deleteCount, itemsToInsert) {
var args = [index, deleteCount].concat(itemsToInsert);
arr.splice.apply(arr, args);
}
//Compiler
function Compiler(src, filename, modules, requireReader, sourceIndex, hammerheadProcessScript) {
this.walker = uglify_js_1.uglify.ast_walker();
this.hammerheadProcessScript = hammerheadProcessScript;
this.filename = filename;
this.src = src;
this.workingDir = path_1.default.dirname(this.filename);
this.modules = modules;
this.requireReader = requireReader;
this.sourceIndex = sourceIndex || [];
this.requires = [];
this.line = 0;
this.errs = [];
this.okFlag = true;
this.rawTestsStepData = {};
this.rawMixinsStepData = {};
this.out = {
fixture: '',
page: '',
authCredentials: null,
requireJs: '',
remainderJs: '',
testsStepData: {},
workingDir: this.workingDir,
testGroupMap: {}
};
this.testAnalyzer = new steps_analyzer_1.default(false, this.rawTestsStepData, this.errs, this.sourceIndex);
this.mixinAnalyzer = new steps_analyzer_1.default(true, this.rawMixinsStepData, this.errs, this.sourceIndex);
}
exports.default = Compiler;
;
Object.defineProperties(Compiler.prototype, {
ok: {
get: function () {
return !this.errs.length && this.okFlag;
},
set: function (value) {
this.okFlag = value;
}
}
});
Compiler.prototype._err = function (type, filename, line, additionalFields) {
this.errs.push(Common.createErrorObj(type, filename, line, additionalFields));
};
Compiler.prototype._fixtureErr = function (type, line, additionalFields) {
this._err(type, this.filename, line, additionalFields);
};
Compiler.prototype._addRequire = function (require) {
if (this.requires.indexOf(require) > -1)
this._fixtureErr(ErrCodes.REQUIRED_FILE_ALREADY_INCLUDED, this.line, { req: require });
else
this.requires.push(require);
};
Compiler.prototype._compileDirective = function (match) {
switch (match[1]) {
case Common.AUTH_DIRECTIVE_LVALUE:
if (this.out.authCredentials) {
this._fixtureErr(ErrCodes.AUTH_DIRECTIVE_REDEFINITION, this.line);
break;
}
var credentials = Common.AUTH_CREDENTIALS_REGEXP.exec(match[2]);
if (!credentials || credentials.length < 3) {
this._fixtureErr(ErrCodes.INVALID_NETWORK_AUTHENTICATION_CREDENTIALS_FORMAT, this.line);
break;
}
this.out.authCredentials = {
username: credentials[1],
password: credentials[2]
};
break;
case Common.FIXTURE_DIRECTIVE_LVALUE:
if (this.out.fixture) {
this._fixtureErr(ErrCodes.FIXTURE_DIRECTIVE_REDEFINITION, this.line);
break;
}
this.out.fixture = match[2];
break;
case Common.PAGE_DIRECTIVE_LVALUE:
if (this.out.page) {
this._fixtureErr(ErrCodes.PAGE_DIRECTIVE_REDEFINITION, this.line);
break;
}
this.out.page = match[2];
break;
case Common.REQUIRE_DIRECTIVE_LVALUE:
if (match[2].indexOf(Common.MODULE_PREFIX) === 0) {
var moduleName = match[2].slice(1), moduleFiles = this.modules && this.modules[moduleName];
if (!moduleFiles) {
this._fixtureErr(ErrCodes.MODULE_NOT_FOUND, this.line, { moduleName: moduleName });
break;
}
var compiler = this;
moduleFiles.forEach(function (moduleFile) {
compiler._addRequire(moduleFile);
});
break;
}
var require = path_1.default.join(this.workingDir, match[2]);
this._addRequire(require);
break;
default:
return false;
}
//NOTE: valid directive expression must match following AST path:
//'toplevel' -> 'stat' -> [string, DIRECTIVE_EXPRESSION].
//Also it's important to perform this test here, when we sure that this is a directive expression
//and not just a string that starts with @
if (!Ast.isPathMatch(Common.DIRECTIVE_EXPRESSION_AST_PATH, this.walker.stack()))
this._fixtureErr(ErrCodes.MISPLACED_DIRECTIVE, this.line);
return true;
};
Compiler.prototype._getRemainderCode = function (ast) {
var remainderAst = Ast.getRemainderAst(ast);
if (remainderAst) {
CallAnalyzer.run(remainderAst, this.filename, this.errs, true, this.sourceIndex, this.src);
if (this.ok) {
var remainderCode = uglify_js_1.uglify.gen_code(remainderAst, { beautify: true });
return this.hammerheadProcessScript(remainderCode, false);
}
}
return '';
};
Compiler.prototype._analyzeAst = function (ast) {
var compiler = this;
this.walker.with_walkers({
'string': function () {
var astPath = compiler.walker.stack(), topStatement = astPath[1][0];
compiler.line = Ast.getCurrentSrcLineNum(astPath);
var isMixinDeclaration = this[1] === Common.MIXIN_DECLARATION_MARKER, isTestDeclaration = this[1] === Common.TEST_DECLARATION_MARKER;
if (isMixinDeclaration || isTestDeclaration) {
topStatement.remove = true;
var analyzer = isMixinDeclaration ? compiler.mixinAnalyzer : compiler.testAnalyzer;
analyzer.run(compiler.walker.stack(), compiler.filename, compiler.src);
return;
}
// NOTE: Try to find directive expression.
// It's a string statement that match DIRECTIVE_EXPRESSION_PATTERN
var match = Common.DIRECTIVE_EXPRESSION_PATTERN.exec(this[1]);
if (match) {
//NOTE: do not unset 'remove' flag if it was already set by test facility parsing algorithm
var success = compiler._compileDirective(match);
topStatement.remove = topStatement.remove || success;
}
}
}, function () {
compiler.walker.walk(ast);
});
};
//Requires
Compiler.prototype._mergeRequireMixins = function (requireDescriptor) {
var compiler = this;
Object.keys(requireDescriptor.rawMixinsStepData).forEach(function (name) {
if (compiler.rawMixinsStepData[name]) {
compiler._fixtureErr(ErrCodes.DUPLICATE_MIXIN_NAME_IN_REQUIRE, null, {
name: name,
defFilename1: requireDescriptor.filename,
defFilename2: compiler.rawMixinsStepData[name].reqFilename || compiler.filename
});
}
else {
compiler.rawMixinsStepData[name] = requireDescriptor.rawMixinsStepData[name];
compiler.rawMixinsStepData[name].reqFilename = requireDescriptor.filename;
}
});
};
Compiler.prototype._analyzeRequires = function (callback) {
var requireReaderPromises = this.requires.map(require => {
return this.requireReader
.read(require, this.filename, this.sourceIndex)
.then(res => {
var descriptor = res.descriptor;
if (res.fromCache)
this.ok = this.ok && !descriptor.hasErrs;
else
this.errs = this.errs.concat(res.errs);
return descriptor;
});
});
pinkie_1.default.all(requireReaderPromises)
.then(descriptors => {
descriptors.forEach(descriptor => {
this._mergeRequireMixins(descriptor);
if (this.ok)
this.out.requireJs += descriptor.jsCode;
});
callback();
});
};
//Test steps data compilation
Compiler.prototype._insertMixins = function (testStepData) {
var stepCount = testStepData.names.length;
for (var i = 0; i < stepCount; i++) {
var ast = testStepData.asts[i];
if (ast.isMixinInsertionPoint) {
var mixinStepData = this.rawMixinsStepData[ast.mixinName];
if (!mixinStepData) {
this._fixtureErr(ErrCodes.UNDEFINED_MIXIN_USED, ast.line, { mixinName: ast.mixinName });
continue;
}
var mixinStepNames = [], stepName = testStepData.names[i];
for (var j = 0; j < mixinStepData.names.length; j++)
mixinStepNames.push(stepName + Common.TEST_MIXIN_STEP_NAME_SEPARATOR + mixinStepData.names[j]);
multySplice(testStepData.names, i, 1, mixinStepNames);
multySplice(testStepData.asts, i, 1, mixinStepData.asts);
i += mixinStepNames.length - 1;
stepCount = testStepData.names.length;
}
}
};
Compiler.prototype._populateTestCases = function (testName, testStepData) {
var compiler = this, cases = testStepData.testCasesDirectiveAst[1];
cases.forEach(function (testCase, index) {
var fields = testCase[1], caseName = null, initStats = [];
fields.forEach(function (field) {
var fieldName = field[0];
if (fieldName === Common.TEST_CASE_NAME_FIELD)
caseName = field[1][1];
else
initStats.push(['stat', ['assign', true, ['sub', ['name', 'this'], ['string', fieldName]], field[1]]]);
});
var initStepAst = ['function', null, [], initStats], genStepData = {
names: [Common.TEST_CASE_INIT_STEP_NAME].concat(testStepData.names),
asts: [initStepAst].concat(testStepData.asts)
}, genTestName = testName +
Common.TEST_CASE_NAME_SEPARATOR +
(caseName || util_1.default.format(Common.TEST_CASE_DEFAULT_NAME_PATTERN, index));
compiler.out.testGroupMap[genTestName] = testName;
compiler._addOutputTestStepData(genTestName, genStepData);
});
};
Compiler.prototype._addOutputTestStepData = function (testName, testStepData) {
var js = uglify_js_1.uglify.gen_code(['array', testStepData.asts], { beautify: true });
this.out.testsStepData[testName] = {
names: testStepData.names,
js: this.hammerheadProcessScript(js, false)
};
};
Compiler.prototype._compileTestsStepData = function () {
var compiler = this, testNames = Object.keys(this.rawTestsStepData);
testNames.forEach(function (testName) {
var testStepData = compiler.rawTestsStepData[testName];
compiler._insertMixins(testStepData);
if (compiler.ok) {
if (testStepData.testCasesDirectiveAst)
compiler._populateTestCases(testName, testStepData);
else
compiler._addOutputTestStepData(testName, testStepData);
}
});
};
//Test cases preparation
Compiler.prototype._parseExternalTestCases = function (data) {
var ast = null;
try {
ast = uglify_js_2.parser.parse(data.toString().trim(), false, true);
}
catch (parserErr) {
return null;
}
//NOTE: extract testCases array from first statement [toplevel]->[stat]->[array]
return ast && ast[1] && ast[1][0] && ast[1][0][1];
};
Compiler.prototype._validateTestCase = function (caseAst, filename, namesMap) {
if (caseAst[0].name !== 'object') {
this._err(ErrCodes.TEST_CASE_IS_NOT_AN_OBJECT, filename, Ast.getCurrentSrcLineNum(caseAst));
return;
}
var fields = caseAst[1];
if (!fields.length) {
this._err(ErrCodes.TEST_CASE_DOESNT_CONTAIN_ANY_FIELDS, filename, Ast.getCurrentSrcLineNum(caseAst));
return;
}
var nameField = fields.filter(function (field) {
return field[0] === Common.TEST_CASE_NAME_FIELD;
})[0];
if (nameField) {
var nameFieldValue = nameField[1];
if (nameFieldValue[0].name !== 'string') {
this._err(ErrCodes.TEST_CASE_NAME_IS_NOT_A_STRING, filename, Ast.getCurrentSrcLineNum(nameField));
return;
}
var testCaseName = nameFieldValue[1];
if (namesMap[testCaseName])
this._err(ErrCodes.DUPLICATE_TEST_CASE_NAME, filename, Ast.getCurrentSrcLineNum(nameField), { testCaseName: testCaseName });
else
namesMap[testCaseName] = true;
}
};
Compiler.prototype._validateTestCaseListAst = function (ast, filename) {
if (!ast || !ast[0] || ast[0].name !== 'array') {
this._err(ErrCodes.TEST_CASES_LIST_IS_NOT_ARRAY, filename, ast ? Ast.getCurrentSrcLineNum(ast) : 1);
return;
}
var cases = ast[1];
if (!cases.length) {
this._err(ErrCodes.TEST_CASES_LIST_IS_EMPTY, filename, Ast.getCurrentSrcLineNum(ast));
return;
}
var compiler = this, namesMap = [];
cases.forEach(function (caseAst) {
compiler._validateTestCase(caseAst, filename, namesMap);
});
};
Compiler.prototype._createExternalTestCasesAnalyzer = function (testCasesPath, refTestsStepData) {
var compiler = this;
return function (readerCallback) {
readFile(testCasesPath)
.then(data => {
data = (0, strip_bom_1.default)(data);
var ast = compiler._parseExternalTestCases(data);
compiler._validateTestCaseListAst(ast, testCasesPath);
refTestsStepData.forEach(function (testStepData) {
testStepData.testCasesDirectiveAst = ast;
});
readerCallback();
})
.catch(() => {
compiler._fixtureErr(ErrCodes.FAILED_TO_READ_EXTERNAL_TEST_CASES, 0, { testCasesPath: testCasesPath });
readerCallback();
});
};
};
Compiler.prototype._prepareTestCases = function (callback) {
var compiler = this, externalTestCases = {}, externalTestCasesAnalyzers = [];
Object.keys(this.rawTestsStepData).forEach(function (testName) {
var testStepData = compiler.rawTestsStepData[testName];
if (testStepData.testCasesDirectiveAst) {
//NOTE: we have a test cases from external file. Add it to the external test cases list, then read them,
//validate and attach to the appropriate testStepData's
if (testStepData.testCasesDirectiveAst[0].name === 'string') {
var testCasesPath = path_1.default.join(compiler.workingDir, testStepData.testCasesDirectiveAst[1]);
//NOTE: assign absolute path as a directive value
testStepData.testCasesDirectiveAst = testCasesPath;
//NOTE: create array which will contain all testStepData's which uses this external test case.
if (!externalTestCases[testCasesPath])
externalTestCases[testCasesPath] = [];
externalTestCases[testCasesPath].push(testStepData);
}
else
compiler._validateTestCaseListAst(testStepData.testCasesDirectiveAst, compiler.filename);
}
});
//NOTE: run external test cases analyzers
Object.keys(externalTestCases).forEach(function (testCasesPath) {
externalTestCasesAnalyzers.push(compiler._createExternalTestCasesAnalyzer(testCasesPath, externalTestCases[testCasesPath]));
});
async_1.default.parallel(externalTestCasesAnalyzers, callback);
};
//Compile
Compiler.prototype.compile = function (callback) {
var compiler = this;
var constructed = null;
try {
constructed = Ast.constructFromCode(this.src, this.filename);
}
catch (parserErr) {
callback([parserErr]);
return;
}
var ast = constructed.ast;
compiler.src = constructed.preprocessedCode;
compiler._analyzeAst(ast);
if (!compiler.out.fixture)
compiler._fixtureErr(ErrCodes.FIXTURE_DIRECTIVE_IS_UNDEFINED);
if (!compiler.out.page)
compiler._fixtureErr(ErrCodes.PAGE_DIRECTIVE_IS_UNDEFINED);
compiler.out.remainderJs = compiler._getRemainderCode(ast);
compiler._analyzeRequires(function () {
compiler._prepareTestCases(function () {
compiler._compileTestsStepData();
if (compiler.ok)
callback(null, compiler.out);
else
callback(compiler.errs);
});
});
};