Skip to content

Instantly share code, notes, and snippets.

@pumbaEO
Last active August 29, 2015 13:57
Show Gist options
  • Save pumbaEO/9748617 to your computer and use it in GitHub Desktop.
Save pumbaEO/9748617 to your computer and use it in GitHub Desktop.
var thisNamespace = "";
exports.handlers = {
beforeParse:function(e){
//Заменим все переводы строк на православно верный.
var logger = require("jsdoc/util/logger");
var sourceText = e.source.replace("\t\n", "\n", "gm");
var syntax = SyntaxAnalysis.AnalyseModule(sourceText);
var Lines = sourceText.split("\n");
for (var i = syntax.moduleMethodComments.length - 1; i >= 0; i--) {
var source = ""
for (var j = syntax.moduleMethodComments[i].startLine; j<=syntax.moduleMethodComments[i].endLine; j++){
source = source + Lines[j] + "\n";
}
syntax.moduleMethodComments[i].parseComment(source);
newComment = syntax.moduleMethodComments[i].compliteComment();
logger.debug("New comment "+newComment);
Lines[j] = Lines[j]+"\n"+newComment;
};
//В начало модуля добавим определения типа класса и подсистемы если возможно будет определить...
sourceText = Lines.join("\n");
e.source = SyntaxAnalysis.ParseComments(sourceText);
logger.debug(e.source);
}
}
var logger = require("jsdoc/util/logger");
////////////////////////////////////////////////////////////////////////////////////////
////{ Cкрипт-библиотека SyntaxAnalysis (SyntaxAnalysis.js) для проекта "Снегопат"
////
//// Описание: Реализует функционал по cинтаксическому анализу исходного кода на
//// внутреннем языке 1С:Предприятия 8.
////
//// Основана на исходном коде скриптлета SyntaxAnalysis.wsc для проекта OpenConf.
////
//// Автор SyntaxAnalysis.wsc: Алексей Диркс <[email protected]>
//// Автор порта: Александр Кунташов <[email protected]>, http://compaud.ru/blog
////}
////////////////////////////////////////////////////////////////////////////////////////
SyntaxAnalysis = {};
SyntaxAnalysis.AnalyseTextDocument = function (text) {
return new _1CModule(text)
}
SyntaxAnalysis.Create1CModuleContextDescription = function(initValueTable) {
return new _1CModuleContextDescription(initValueTable);
}
SyntaxAnalysis.Create1CMethodDescription = function(parentModule) {
return new _1CMethodDescription(parentModule);
}
////////////////////////////////////////////////////////////////////////////////////////
////{ Регулярные выражения для поиска конструкций встроенного языка 1С.
////TODO:
//// - Удалить из регулярок определения метода Далее - не имеет смысла для 8.х
//// - Описать индексы и назначения группировок, подобно тому, как сделано для RE_VAR.
SyntaxAnalysis.RE_COMMENT = new RegExp('^\\s*((?:(?:(?:"[^"]")*)|(?:[^/]*)|(?:[^/]+/))*)(//.*)?\\s*$', "");
SyntaxAnalysis.RE_COMMENTMODULE = new RegExp('^\\s*\/\/.*');
/* Группировки: 1: Объявление метода (процедура/функция), 2: Имя метода, 3: Список параметров метода строкой, 4: "Далее" - имеет смысл только для 7.7. */
//SyntaxAnalysis.RE_PROC = new RegExp('^\\s*((?:procedure)|(?:function)|(?:процедура)|(?:функция))\\s+([\\wА-яёЁ\\d]+)\\s*\\(([\\wА-яёЁ\\d\\s,.="\']*)\\)\\s*((?:forward)|(?:далее))?(.*)$', "i");
SyntaxAnalysis.RE_PROC = new RegExp('^\\s*((?:procedure)|(?:function)|(?:процедура)|(?:функция))\\s+([\\wА-яёЁ\\d]+)\\s*\\(', 'i');
SyntaxAnalysis.RE_PARAM = new RegExp('(?:(?:Val)|(?:Знач)\\s+)?([\\wА-яёЁ\\d]+)(\\s*=\\s*(?:(?:"[^"]")|(?:[^,)]*))*)?', "ig");
SyntaxAnalysis.RE_PARAM_END = new RegExp('([\\wА-яёЁ\\d\\s,.="\']*)\\)(.*)', 'i');
SyntaxAnalysis.RE_PROC_END = new RegExp('((?:EndProcedure)|(?:EndFunction)|(?:КонецПроцедуры)|(?:КонецФункции))', "i");
SyntaxAnalysis.RE_VARS_DEF = new RegExp('^\\s*(?:(?:Var)|(?:Перем))\\s*([\\wА-яёЁ\\d,=\\[\\]\\s]*)(\\s+экспорт\\s*)?([\\s;]*)(.*?)$', "i");
/* Группировки: 1: Имя переменной, 2: Определение размерности массива, 3: Экспорт, 4: Конечный символ ("," или пусто - конец строки). */
SyntaxAnalysis.RE_VAR = new RegExp('([\\wА-яёЁ\\d]+)\\s*(\\[[\\d\\s,]*\\])?(\\s+экспорт\\s*)?(?:\\s*(?:,|;|$))', "ig");
SyntaxAnalysis.RE_VAR_ASSIGN = new RegExp('([\\wА-яёЁ\\d.]+)\\s*=\\s*(([^;]*);)?', "g");
SyntaxAnalysis.RE_CALL = new RegExp('([\\wА-яёЁ\\d.]+)\\s*\\(', "g");
SyntaxAnalysis.RE_SPACE = new RegExp('\\s+', "g");
//FIXME:RE_PROC_TORMOZIT пока не используеться, т.к. нет определения НаКлиенте, НаСервере и т.д. для тонкого клиента.
SyntaxAnalysis.RE_PROC_TORMOZIT = new RegExp('((Процедура|Функция)(?://[^\\n]*\\n|\\s|^|$)*([А-Яа-я_A-Za-z][А-Яа-я_A-Za-z\\d]*)(?://[^\\n]*\\n|\\s|^|$)*\\(([^\\)]*)\\)((?://[^\\n]*\\n|\\s|^|$)*Экспорт)?)((?:(?:"(?:(?:"")|[^"\\n$])*(?:(?://[^\\n]*\\n|\\s|^|$)*\\|(?:(?:"")|[^"\\n$])*)*(?:"|$)|\\.Конец(?:Процедуры|Функции)|\\r|\\n|.)*?))[^А-Яа-я_A-Za-z0-9\\."]Конец(?:Процедуры|Функции)[^А-Яа-я_A-Za-z0-9]|#[^\\n]*\\n|(?://[^\\n]*\\n|\\s|^|$)', 'igm')
SyntaxAnalysis.RE_PARAM_TORMOZIT = new RegExp('(?://[^\\n]*\\n|\\s|^|$)*(Знач\\s)?(?://[^\\n]*\\n|\\s|^|$)*([А-Яа-я_A-Za-z][А-Яа-я_A-Za-z0-9]+)(?://[^\\n]*\\n|\\s|^|$)*=?((?:(?://[^\\n]*\\n|\\s|^|$)*|("(?:(?:"")|[^"\\n$])*(?:(?://[^\\n]*\\n|\\s|^|$)*\\|(?:(?:"")|[^"\\n$])*)*(?:"|$))|(?:[^,\\n$]*))+)(?:,|$)','img');
SyntaxAnalysis.RE_CONTEXT = new RegExp('^\\s*&\\s*(AtClientAtServerNoContext|AtServerNoContext|AtClientAtServer|AtServer|AtClient|НаСервереБезКонтекста|НаКлиентеНаСервереБезКонтекста|НаКлиентеНаСервере|НаКлиенте|НаСервере)\\s*', 'i')
SyntaxAnalysis.CONTEXT = { "atclientatservernocontext" :"AtClientAtServerNoContext",
"atservernocontext" :"AtServerNoContext",
"atclientatserver" :"AtClientAtServer",
"atserver" :"AtServer",
"atclient" :"AtClient",
"насерверебезконтекста" :"НаСервереБезКонтекста",
"наклиентенасерверебезконтекста":"НаКлиентеНаСервереБезКонтекста",
"наклиентенасервере" :"НаКлиентеНаСервере",
"наклиенте" :"НаКлиенте",
"насервере" :"НаСервере"
}
//SyntaxAnalysis
//SyntaxAnalysis.RE_CRLF = new RegExp('[\\n]+', "");
////} Регулярные выражения для поиска конструкций встроенного языка 1С.
SyntaxAnalysis.AnalyseParams = function(sourceCode, Meth) {
var Matches;
while( (Matches = SyntaxAnalysis.RE_PARAM_TORMOZIT.exec(sourceCode)) != null ) {
Meth.Params.push(Matches[2]);
}
}
SyntaxAnalysis.AnalyseComments = function(sourceCode){
var result = sourceCode;
var Matches = SyntaxAnalysis.RE_COMMENT.exec(sourceCode);
if( Matches != null )
result = Matches[1];
return result
}
SyntaxAnalysis.ParseComments = function(text){
var re = new RegExp('^\\s*\/\/.*');
var recomment = new RegExp('^\\s*(\\/\\*\\*|\\*|\\/\\/)');
var Lines = text.split('\n');
for (var i = Lines.length - 1; i >= 0; i--) {
var line = Lines[i];
var Mathes = re.exec(line);
if (Mathes !=null){
} else {
var Mathes = recomment.exec(line);
if (Mathes!=null){
} else {
Lines[i] = "//"+line;
}
}
};
return Lines.join('\n');
}
SyntaxAnalysis.AnalyseModule = function (sourceCode, initValueTable) {
var Meth;
var stStart = 0, stInProc = 1, stInModule = 2, stInVarsDef, stStartComment = 0;
var state = stStart, PrevState;
var stateComment;
var Match;
var Context = "";
var commentContext = null;
var moduleContext = SyntaxAnalysis.Create1CModuleContextDescription(initValueTable);
var proc_count = 0;
var Lines = sourceCode.split("\n");
var n = Lines.length;
var i = 0;
var nextPart = '';
while (i < n)
{
var str = '';
if (nextPart)
{
str = nextPart;
}
else
{
if(state != stInModule && state!=stStart) {
str = this.AnalyseComments(Lines[i]);
} else {
str = Lines[i];
}
}
//logger.debug("Строка:"+str);
switch( state )
{
case stStart:
case stInModule:
//logger.debug("stInModule");
var Matches = SyntaxAnalysis.RE_CONTEXT.exec(str);
if (Matches!=null) {
Context = SyntaxAnalysis.CONTEXT[(""+Matches[1]).toLowerCase()];
//logger.debug("контескт:"+Context);
}
var Matches = SyntaxAnalysis.RE_COMMENTMODULE.exec(str);
if (Matches!=null){
if (!commentContext){
commentContext = new SyntaxAnalysis._1CCommentModule(moduleContext);
commentContext.startLine = i;
commentContext.description = str;
//logger.debug(""+ commentContext.description + "" + commentContext.startLine);
}
} else if (Context.length>0) {
//logger.debug("Context.length:"+Context.length);
} else {
//logger.debug('commentContext' + str);
commentContext = null;
}
Matches = SyntaxAnalysis.RE_PROC.exec(str);
if( Matches != null )
{
Meth = SyntaxAnalysis.Create1CMethodDescription(moduleContext);
Meth.Name = Matches[2];
Meth.StartLine = i;
Meth.IsProc = (Matches[1].toLowerCase() == 'процедура' || Matches[1].toLowerCase() == 'procedure');
Meth.Context = (Context.length>0)?Context:"НаСервере"; //Пока только для тонкого клиента.
str = str.substr(Matches.lastIndex);
var strParams = '';
Matches = SyntaxAnalysis.RE_PARAM_END.exec(str);
if (Matches!=null) {
strParams = Matches[1]
} else {
strParams = ''+str;
i++;
while (i<n) {
str = this.AnalyseComments(Lines[i]);
if ((Matches = SyntaxAnalysis.RE_PARAM_END.exec(str))!=null) {
strParams = strParams+"\n"+Matches[1]
break;
} else {
strParams = strParams+"\n"+str;
}
i++;
}
}
if(!commentContext){
//logger.debug("!commentContext:"+str);
} else {
//logger.debug(""+Meth.Name);
commentContext.endLine = (Context.length>0)?(i-2):(i-1);
commentContext._method = Meth;
moduleContext.moduleMethodComments.push(commentContext)
}
this.AnalyseParams(strParams, Meth);
var endproc = Matches[2]
moduleContext.addMethod(Meth);
proc_count++;
state = stInProc;
if( (Match = SyntaxAnalysis.RE_PROC_END.exec(endproc)) != null )
{
state = stInModule;
nextPart = endproc.substr(SyntaxAnalysis.RE_PROC_END.lastIndex);
if (nextPart)
continue;
}
}
else if ((Matches = SyntaxAnalysis.RE_VARS_DEF.exec(str)) != null)
{
str = Matches[1];
nextPart = Matches[4];
while( (Matches = SyntaxAnalysis.RE_VAR.exec(str)) != null )
{
if( PrevState == stInProc )
Meth.addVar(Matches[1]);
else
moduleContext.addVar(Matches[1]);
}
if (nextPart)
continue;
str = str.replace(SyntaxAnalysis.RE_SPACE, "");
if( str.substr(str.length-1) == ";" )
{
state = PrevState;
}
else if (str.substr(str.length-1) == ",")
{
PrevState = state;
state = stInVarsDef;
}
break;
}
break;
case stInProc:
Matches = SyntaxAnalysis.RE_PROC_END.exec(str);
if( Matches != null )
{
if( state == stInProc ) Meth.EndLine = i;
state = stInModule;
}
else if( (Matches = SyntaxAnalysis.RE_VARS_DEF.exec(str)) != null )
{
var exported = Matches[2];
var semicolon = Matches[3].replace(SyntaxAnalysis.RE_SPACE, "");
str = Matches[1];
while( (Matches = SyntaxAnalysis.RE_VAR.exec(str)) != null )
{
if( state == stInProc )
Meth.addVar(Matches[1]);
else
moduleContext.addVar(Matches[1]);
}
if( semicolon != ";" )
{
PrevState = state;
state = stInVarsDef;
}
}
else
{
while( (Matches = SyntaxAnalysis.RE_VAR_ASSIGN.exec(str)) != null )
{
var varName = Matches[1];
if( varName.indexOf(".", 0) >= 0 ) continue;
if (state == stInProc)
Meth.addVar(varName, null, true); // automatic var
else
moduleContext.addVar(varName);
}
if( state == stInProc )
{
while( (Matches = SyntaxAnalysis.RE_CALL.exec(str)) != null )
{
if( Matches[1].indexOf('.') >= 0 ) continue;
if( Meth.Calls.indexOf(Matches[1]) >= 0) continue;
Meth.Calls.push(Matches[1]);
}
}
}
break;
case stInVarsDef:
while( (Matches = SyntaxAnalysis.RE_VAR.exec(str)) != null )
{
if( PrevState == stInProc )
Meth.addVar(Matches[1]);
else
moduleContext.addVar(Matches[1]);
}
str = str.replace(SyntaxAnalysis.RE_SPACE, "");
if( str.substr(str.length-1) == ";" )
state = PrevState;
break;
}
i++;
nextPart = '';
}
return moduleContext;
}
////////////////////////////////////////////////////////////////////////////////////////
////{ _1CModule
SyntaxAnalysis._1CCommentModule = function(parentModule){
//logger.debug("new coment context" + parentModule.Methods.length);
this.startLine = 0;
this.endLine = 0;
this.description = "";
this.params = [];
this.returns = []
this._method = "";
this.parentModule = parentModule;
}
/**
* Парсим комментарии, определяем описание, параметры и возвращаемое значение
*/
SyntaxAnalysis._1CCommentModule.prototype.parseComment = function(source){
var RE_PARAM_THISTYPE = new RegExp("(\\/\\/\\s*)([A-zА-я0-9]{1,})\\s*-\\s*([A-zА-я0-9]*)\\s*-\\s*(.*)");
var RE_PARAM = new RegExp("(\\/\\/\\s*)([A-zА-я0-9]{1,})\\s*-\\s*(.*)");
this.description = "";
var stStart = 0; stDescription = 1; stInParam = 2; stInReturn = 3;
var state = stStart, PrevState;
var Lines = source.split("\n");
var n = Lines.length;
var i = 0;
var nextPart = '';
var param;
while (i < n)
{
var str = '';
if (nextPart)
{
str = nextPart;
}
else
{
str = Lines[i];
}
//logger.debug("Строка:"+str);
switch( state )
{
case stStart:
state = stDescription;
case stDescription:
if ((str.indexOf("Параметры") ==-1) && (str.indexOf("Возвращаемое значение:")==-1)) {
this.description =this.description + str.replace("//", "") + "\n";
} else if(str.indexOf("Параметры:")!=-1){
state = stInParam;
i++;
} else if(str.indexOf("Возвращаемое значение:")!=-1){
state = stInReturn;
i++;
}
break;
case stInParam:
if(str.indexOf("Возвращаемое значение:")!=-1){
if(!param){
this.params.push(param);
//logger.debug("param push");
param = null;
}
state = stInReturn;
break;
}
Mathes = RE_PARAM_THISTYPE.exec(str);
if (Mathes!=null){
if(param){
//logger.debug("push" + param);
this.params.push(param);
}
param = {}
param.ident = Mathes[1].length;
param.name = Mathes[2];
param.type = Mathes[3];
param.description = Mathes[4];
} else {
Mathes = RE_PARAM.exec(str);
if (Mathes!=null){
if(param){
//logger.debug("push" + param);
this.params.push(param);
}
param = {}
param.ident = Mathes[1].length;
param.name = Mathes[2];
param.description = Mathes[3];
param.type = null;
}
}
if (Mathes == null && !param){
param.description = param.description + str.replace(/\/\/\s*/, "");
}
break;
case stInReturn:
this.returns.push(str.replace(/\/\/\s*/, ""));
break;
}
i++;
nextPart = '';
}
if (param){
//logger.debug("push" + param);
this.params.push(param);
}
logger.debug("parseComments");
}
SyntaxAnalysis._1CCommentModule.prototype.compliteComment = function(){
var str = '';
str = "/** \n* @description "+this.description;
str = str + "\n* @method "+this._method.Name;
str = str + "\n* @name "+this._method.Name;
if (this._method.isProc == false){
str = str + "\n* @@function ";
}
str = str + "\n* @namespace АвансовыйОтчет";
logger.debug(this.params.length);
for (var i = 0; i <= this.params.length - 1; i++) {
str = str + "\n* @param " + this.params[i].name + " {"+ !this.params[i].type?this.params[i].type:"*" + " "+this.params[i].description;
};
for (var i = 0; i <= this.returns.length - 1; i++) {
str = str + "\n* @returns " + this.params[i].name + " {"+ !this.params[i].type?this.params[i].type:"*" + " "+this.params[i].description;
};
if ((this._method.isProc == false) && (this.returns.length = 0)){
str = str + "\n* @returns ";
}
str = str + "\n*/"
return str;
}
/**
* Анализируем исходный код.
* @param {Text} text
*/
function _1CModule(text) {
this.textWindow = null;
this.context = SyntaxAnalysis.AnalyseModule(text, true);
}
/* Возвращает исходный код метода по названию метода. */
_1CModule.prototype.getMethodSource = function(methodName) {
var method = this.context.getMethodByName(methodName);
if (!method) return undefined;
return this.textWindow.Range(method.StartLine + 1, 1, method.EndLine + 1).GetText();
}
/* Возвращает таблицу значений с описаниями методов модуля. */
_1CModule.prototype.getMethodsTable = function() {
return this.context._vtAllMethods;
}
/* Возвращает описание метода по номеру строки, находящейся внутри метода. */
_1CModule.prototype.getMethodByLineNumber = function (lineNo) {
var methods = this.context.Methods;
for (var i=0; i<methods.length; i++)
{
/* Помним, что нумерация строк начинается с 1,
а строки модуля в SyntaxAnalysis проиндексированы с 0. */
if (methods[i].StartLine + 1 <= lineNo && lineNo <= methods[i].EndLine + 1)
return methods[i];
}
return undefined;
}
/** Возвращает описание метода, которому принадлежит текущая строка
(строка, в которой находится курсор).
* @param {Number} lineNo номер строки.
*/
_1CModule.prototype.getActiveLineMethod = function (lineNo) {
//var pos = this.textWindow.GetCaretPos();
return this.getMethodByLineNumber(lineNo);
}
////} _1CModule
////////////////////////////////////////////////////////////////////////////////////////
////{ _1CModuleContextDescription
function _1CModuleContextDescription(initValueTable) {
// Массив всех методов модуля.
this.Methods = new Array();
// Ассоциативный массив Имя метода -> _1CMethodDescription
this._methodsByName = {};
// Массив всех явным образом объявленных переменных модуля.
this.ModuleVars = new Array();
this.moduleMethodComments = new Array();
// Ассоциативный массив Имя переменной -> Тип переменной (пока тип всегда null).
this._moduleVarsTypes = {};
this._vtAllMethods = null;
if (initValueTable)
{
// var v8Type_String = v8New('TypeDescription', 'Строка', undefined, v8New('StringQualifiers', 255));
// var v8Type_Number = v8New('TypeDescription', 'Число', v8New('NumberQualifiers', 10, 0));
// var v8Type_Boolean = v8New('TypeDescription', 'Булево');
this._vtAllMethods = new Array();
// var cols = this._vtAllMethods.Columns;
// // Добавляем колонки.
// cols.Add('Name', v8Type_String, 'Имя процедуры/функции');
// cols.Add('IsProc', v8Type_Boolean, 'Процедура');
// cols.Add('StartLine', v8Type_Number, 'N первой строки');
// cols.Add('EndLine', v8Type_Number, 'N последней строки');
// cols.Add('Context', v8Type_String, 'Контекст компиляции модуля');
//
// cols.Add('_method'); // _1CMethodDescription
}
}
_1CModuleContextDescription.prototype.addMethod = function (method) {
if (this._methodsByName[method.name])
Message('Метод ' + method.name + 'уже был объявлен ранее в этом модуле!');
this.Methods.push(method);
this._methodsByName[method.Name] = method;
// Добавляем метод в таблицу значений.
if (this._vtAllMethods)
{
//var methRow = this._vtAllMethods.Add();
method = {};
methRow.Name = method.Name;
methRow.IsProc = method.IsProc;
methRow.StartLine = method.StartLine;
methRow.EndLine = method.EndLine;
methRow.Context = method.Context;
methRow._method = method;
this._vtAllMethods.push(method);
}
}
_1CModuleContextDescription.prototype.getMethodByName = function (name) {
return this._methodsByName[name];
}
_1CModuleContextDescription.prototype.addVar = function (name, type, auto) {
if (this._moduleVarsTypes[name] === undefined)
{
this._moduleVarsTypes[name] = (type === undefined) ? null : type;
this.ModuleVars.push(name);
}
}
_1CModuleContextDescription.prototype.getVarType = function (name) {
return this._moduleVarsTypes[name];
}
////} _1CModuleContextDescription
////////////////////////////////////////////////////////////////////////////////////////
////{ _1CMethodDescription
function _1CMethodDescription(parentModule) {
// Идентификатор (имя) метода.
this.Name = "";
// Тип метода. Если истина - то это Процедура, иначе - это функция.
this.IsProc = false;
// Массив параметров метода.
this.Params = new Array();
// Массив явным образом объявленных переменных.
this.DeclaredVars = new Array();
// Массив автоматических локальных переменных (не объявленных явным образом).
this.AutomaticVars = new Array();
// Список вызовов: массив методов, вызываемых из данного метода.
this.Calls = new Array();
// Номер строки объявления метода.
this.StartLine = 0;
// Номер строки завершения объявления метода.
this.EndLine = 0;
//Номер начала строки комментария перед процедурой
this.StartCommentLine = 0;
//Номер окончания строки комментария перед процедурой
this.EndCommentLine = 0;
// Ассоциативный массив Имя переменной -> Тип переменной (пока тип всегда null).
this._varsTypes = {};
// Контекст модуля, в котором объявлен данный метод (_1CModuleContextDescription).
this.parentModule = parentModule;
}
_1CMethodDescription.prototype.addVar = function (name, type, auto) {
if (this._varsTypes[name] === undefined)
{
if (this.Params.indexOf(name) >= 0)
return;
if (this.parentModule && this.parentModule.getVarType(name) !== undefined)
return;
this._varsTypes[name] = (type === undefined) ? null : type;
if (auto)
this.AutomaticVars.push(name);
else
this.DeclaredVars.push(name);
}
}
_1CMethodDescription.prototype.getVarType = function (name) {
return this._varsTypes[name];
}
////} _1CMethodDescription
////////////////////////////////////////////////////////////////////////////////////////
////{ Вспомогательные функции объекта Array
if(!Array.prototype.indexOf) {
Array.prototype.indexOf = function(searchElement, fromIndex) {
for(var i = fromIndex||0, length = this.length; i<length; i++)
if(this[i] === searchElement) return i;
return -1
};
};
////} Вспомогательные функции объекта Array
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment