text: "use strict";
  Object.defineProperty(exports, "__esModule", { value: true });
  const File_1 = require("./File");
  const path = require("path");
  const fsExtra = require("fs-extra");
  const chai_1 = require("chai");
  const sinon_1 = require("sinon");
  const brighterscript_1 = require("brighterscript");
  const TestHelpers_spec_1 = require("../TestHelpers.spec");
  const sinon = sinon_1.createSandbox();
  const tmpDir = path.join(process.cwd(), '.tmp');
  const rootDir = path.join(tmpDir, 'srcRootDir');
  const srcPath = path.join(rootDir, 'components', 'file.brs');
  const destPath = path.join(tmpDir, 'dest', 'file.brs');
  describe('prefixer/File', () => {
      let file;
      let f;
      let fileContents = '';
      let program;
      beforeEach(() => {
          fsExtra.ensureDirSync(tmpDir);
          fsExtra.emptyDirSync(tmpDir);
          file = new File_1.File(srcPath, destPath, rootDir);
          f = file;
          sinon.stub(fsExtra, 'readFile').callsFake(() => {
              return Promise.resolve(Buffer.from(fileContents));
          });
          initProgram();
      });
      afterEach(() => {
          sinon.restore();
      });
      function initProgram() {
          program = new brighterscript_1.Program({
              rootDir: rootDir
          });
      }
      /**
       * Set the contents of the file right before a test.
       * This also normalizes line endings to `\n` to make the tests consistent
       */
      function setFile(value, extension = 'brs') {
          fileContents = value.replace(/\r\n/, '\n');
          file.srcPath = f.srcPath.replace('.brs', '.' + extension);
          file.destPath = f.destPath.replace('.brs', '.' + extension);
          const relativeSrcPath = f.srcPath
              //make path relative
              .replace(rootDir, '')
              //remove leading slashes
              .replace(/^(\/|\\)*/, '');
          program.addOrReplaceFile(relativeSrcPath, fileContents);
      }
      /**
       * Convert the given line and column into an absolute offset from the current file contents
       */
      function getOffset(lineIndex, columnIndex) {
          let currentLineIndex = 0;
          let currentColumnIndex = 0;
          for (let offset = 0; offset < fileContents.length; offset++) {
              if (currentLineIndex === lineIndex && currentColumnIndex === columnIndex) {
                  return offset;
              }
              if (fileContents[offset] === '\n') {
                  currentLineIndex++;
                  currentColumnIndex = 0;
              }
              else {
                  currentColumnIndex++;
              }
          }
          return -1;
      }
      /**
       * Given an offset in the current file contents, compute the Position (line and column)
       */
      function getPositionFromOffset(targetOffset) {
          let currentLineIndex = 0;
          let currentColumnIndex = 0;
          for (let offset = 0; offset < fileContents.length; offset++) {
              if (targetOffset === offset) {
                  return {
                      line: currentLineIndex,
                      character: currentColumnIndex
                  };
              }
              if (fileContents[offset] === '\n') {
                  currentLineIndex++;
                  currentColumnIndex = 0;
              }
              else {
                  currentColumnIndex++;
              }
          }
      }
      describe('findFunctionDefinitions', () => {
          it('finds functions in multiple lines', () => {
              setFile(`
                  function Main()
                  end function
                  function Main2()
                  end function
              `);
              file.discover(program);
              chai_1.expect(TestHelpers_spec_1.pick(f.functionDefinitions, 'name', 'nameOffset')).to.eql([{
                      name: 'Main',
                      nameOffset: getOffset(1, 25)
                  }, {
                      name: 'Main2',
                      nameOffset: getOffset(3, 25)
                  }]);
          });
      });
      describe('findFunctionCalls', () => {
          it('works for brs files', () => {
              setFile(`
                  'should not match on "main"
                  sub main()
                      doSomething()
                      'should not match on "function"
                      func = function()
                          doSomethingInner()
                      end function
                  end sub
              `);
              file.discover(program);
              chai_1.expect(f.functionReferences).to.eql([{
                      name: 'doSomething',
                      offset: getOffset(3, 20)
                  }, {
                      name: 'doSomethingInner',
                      offset: getOffset(6, 24)
                  }]);
          });
          it('does not match object function calls', () => {
              setFile(`
                  sub getPerson()
                      person = {
                          speak: sub(message)
                              print message
                          end sub,
                          sayHello: sub()
                              m.speak("Hello")
                              speak("Person said hello")
                          end sub
                      }
                      person.speak("I'm a person")
                      speak("Made person speak")
                  end sub
                  sub speak(message)
                      print message
                  end sub
              `, 'brs');
              file.discover(program);
              chai_1.expect(f.functionReferences).to.eql([{
                      name: 'speak',
                      offset: getOffset(8, 28)
                  }, {
                      name: 'speak',
                      offset: getOffset(12, 20)
                  }]);
          });
      });
      describe('findIdentifiers', () => {
          function verifyIdentifier(text, ...expectedIdentifiers) {
              file = new File_1.File(srcPath, destPath, rootDir);
              f = file;
              initProgram();
              setFile(`
                  sub main()\n${text}
                  end sub`);
              file.discover(program);
              const identifiers = [];
              for (const identifier of expectedIdentifiers) {
                  identifiers.push({
                      name: identifier[0],
                      line: identifier[1],
                      character: identifier[2]
                  });
              }
              chai_1.expect(file.identifiers.map(x => {
                  const position = getPositionFromOffset(x.offset);
                  //subtract 2 lines from the position since we inject the text into an existing function
                  position.line -= 2;
                  return Object.assign({ name: x.name }, position);
              })).to.eql(identifiers);
          }
          it('finds simple assignment', () => {
              verifyIdentifier(`someVar = logInfo`, ['logInfo', 0, 10]);
          });
          it('finds dotted set', () => {
              verifyIdentifier(`object.var = logInfo`, ['logInfo', 0, 13]);
          });
          it('finds indexed set', () => {
              verifyIdentifier(`object["1"] = logInfo`, ['logInfo', 0, 14]);
          });
          it('finds key for indexed set', () => {
              verifyIdentifier(`object[logInfo] = "1"`, ['logInfo', 0, 7]);
          });
          it('function argument', () => {
              verifyIdentifier(`callAFunction(logInfo)`, ['logInfo', 0, 14]);
          });
          it('does not match function call name', () => {
              verifyIdentifier(`doSomething()`);
          });
          it('matches first of two function arguments', () => {
              verifyIdentifier(`callAFunction(logInfo, "asdf")`, ['logInfo', 0, 14]);
          });
          it('matches second of two function arguments', () => {
              verifyIdentifier(`callAFunction("asdf", logInfo)`, ['logInfo', 0, 22]);
          });
          it('does not match parameter name ', () => {
              verifyIdentifier(`
                  innerSub = sub (logInfo)
                  end sub
              `);
          });
          it('matches quirky AA multi-item-same-line syntax', () => {
              verifyIdentifier(`
                  person = {
                      log: logInfo1 : log2: logInfo2
                  }
              `, ['logInfo1', 2, 25], ['logInfo2', 2, 42]);
          });
          it('matches within array', () => {
              verifyIdentifier(`
                  arr = [
                      logInfo
                  ]
              `, ['logInfo', 2, 20]);
          });
          it('matches multi-line function argument', () => {
              verifyIdentifier(`
                  someFunction(
                      logInfo
                  )
              `, ['logInfo', 2, 20]);
          });
          it('matches multiple statements on same line', () => {
              verifyIdentifier(`print logInfo1 : print logInfo2`, ['logInfo1', 0, 6], ['logInfo2', 0, 23]);
          });
          it('matches standard AA member value', () => {
              verifyIdentifier(`
                  person = {
                      name: logInfo
                  }
              `, ['logInfo', 2, 26]);
          });
          //any expression where the function name is wrapped in parens
          it('matches wrapped in parens', () => {
              verifyIdentifier(`print "1" + (logInfo)`, ['logInfo', 0, 13]);
          });
          it('skips identifiers in strings and keyword identifiers', () => {
              verifyIdentifier(`
                  function main()
                      print "main function"
                  end function
              `);
          });
      });
      describe('findComponentDefinitions', () => {
          it('finds component name', () => {
              setFile(`<?xml version="1.0" encoding="utf-8" ?>
                  <component name="CustomComponent">
                  </component>
              `, 'xml');
              file.discover(program);
              chai_1.expect(f.componentDeclarations).to.eql([{
                      name: 'CustomComponent',
                      offset: getOffset(1, 33)
                  }]);
          });
          it('finds component name on separate line', () => {
              setFile(`<?xml version="1.0" encoding="utf-8" ?>
                  <component
                      name="CustomComponent">
                  </component>
              `, 'xml');
              file.discover(program);
              chai_1.expect(f.componentDeclarations).to.eql([{
                      name: 'CustomComponent',
                      offset: getOffset(2, 26)
                  }]);
          });
      });
      describe('findComponentReferences', () => {
          it('finds component name from `extends` attribute', () => {
              setFile(`<?xml version="1.0" encoding="utf-8" ?>
                  <component name="CustomComponent" extends="ParentComponent" >
                  </component>
              `, 'xml');
              file.discover(program);
              chai_1.expect(f.componentReferences).to.eql([{
                      name: 'ParentComponent',
                      offset: getOffset(1, 59)
                  }]);
          });
          it('finds component name from `extends` attribute on different line', () => {
              setFile(`<?xml version="1.0" encoding="utf-8" ?>
                  <component name="CustomComponent"
                      extends="ParentComponent" >
                  </component>
              `, 'xml');
              file.discover(program);
              chai_1.expect(f.componentReferences).to.eql([{
                      name: 'ParentComponent',
                      offset: getOffset(2, 29)
                  }]);
          });
          it('finds component name in `createObject`', () => {
              setFile(`
                  sub main()
                      'normal
                      createObject("RoSGNode", "Component1")
                      'extra spaces
                      createObject ( "RoSGNode" , "Component2" )
                      'lower rosgnode
                      createObject("rosgnode","Component3")
                  end sub
              `);
              file.discover(program);
              chai_1.expect(f.componentReferences).to.eql([{
                      name: 'Component1',
                      offset: getOffset(3, 46)
                  }, {
                      name: 'Component2',
                      offset: getOffset(5, 49)
                  }, {
                      name: 'Component3',
                      offset: getOffset(7, 45)
                  }]);
          });
          it('finds component name in `node.createChild`', () => {
              setFile(`
                  sub main()
                      'normal
                      node.CreateChild("Component1")
  
                      'lots of spaces
                      node . CreateChild ( "Component2" )
  
                      'multi-line
                      node . CreateChild (
                          "Component3"
                      )
  
                      'function call to get node
                      getNode().CreateChild("Component4")
  
                      'function call to get value (this one should be ignored)
                      node.CreateChild(getNodeName())
                  end sub
              `);
              file.discover(program);
              chai_1.expect(f.componentReferences).to.eql([{
                      name: 'Component1',
                      offset: getOffset(3, 38)
                  }, {
                      name: 'Component2',
                      offset: getOffset(6, 42)
                  }, {
                      name: 'Component3',
                      offset: getOffset(10, 25)
                  }, {
                      name: 'Component4',
                      offset: getOffset(14, 43)
                  }]);
          });
          it('finds component name in xml usage', () => {
              setFile(`<?xml version="1.0" encoding="utf-8" ?>
                  <component name="CustomComponent">
                      <children>
                          <CustomComponent2></CustomComponent2>
                          <MarkupList />
                          <SomeCustomComponent />
                      </children>
                  </component>
              `, 'xml');
              file.discover(program);
              chai_1.expect(file.componentReferences.sort((a, b) => {
                  if (a.offset > b.offset) {
                      return 1;
                  }
                  else if (a.offset < b.offset) {
                      return -1;
                  }
                  return 0;
              })).to.eql([{
                      name: 'CustomComponent2',
                      offset: getOffset(3, 25)
                  }, {
                      name: 'CustomComponent2',
                      offset: getOffset(3, 44)
                  }, {
                      name: 'MarkupList',
                      offset: getOffset(4, 25)
                  }, {
                      name: 'SomeCustomComponent',
                      offset: getOffset(5, 25)
                  }]);
          });
      });
      describe('findFileReferences', () => {
          it('finds absolute script paths in xml tags', () => {
              setFile(`<?xml version="1.0" encoding="utf-8" ?>
                  <component name="CustomComponent">
                      <script uri="pkg:/source/lib.brs" />
                      <script uri="pkg:/components/folder1/somelib.brs" />
                      <Poster uri="pkg:/images/CoolPhoto.png" />
                  </component>
              `, 'xml');
              file.discover(program);
              chai_1.expect(file.fileReferences).to.eql([{
                      path: 'pkg:/source/lib.brs',
                      offset: getOffset(2, 33)
                  }, {
                      path: 'pkg:/components/folder1/somelib.brs',
                      offset: getOffset(3, 33)
                  }, {
                      path: 'pkg:/images/CoolPhoto.png',
                      offset: getOffset(4, 33)
                  }]);
          });
          it('finds relative file paths in xml script tags', () => {
              setFile(`<?xml version="1.0" encoding="utf-8" ?>
                  <component name="CustomComponent">
                      <script uri="../lib.brs" />
                      <script uri="comp.brs" />
                  </component>
              `, 'xml');
              file.discover(program);
              chai_1.expect(file.fileReferences).to.eql([{
                      path: '../lib.brs',
                      offset: getOffset(2, 33)
                  }, {
                      path: 'comp.brs',
                      offset: getOffset(3, 33)
                  }]);
          });
          it('finds pkg paths in brs files', () => {
              setFile(`
                  sub main()
                      filePath = "pkg:/images/CoolPhoto.brs"
                  end sub
              `);
              file.discover(program);
              chai_1.expect(file.fileReferences).to.eql([{
                      path: 'pkg:/images/CoolPhoto.brs',
                      offset: getOffset(2, 32)
                  }]);
          });
      });
      describe('applyEdits', () => {
          it('leaves file intact with no edits', () => {
              setFile('hello world', 'brs');
              file.discover(program);
              file.applyEdits();
              chai_1.expect(file.bscFile.fileContents).to.equal('hello world');
          });
          it('applies zero-length edit as an insert', () => {
              setFile('hello world', 'brs');
              file.discover(program);
              file.addEdit(5, 5, ' my');
              file.applyEdits();
              chai_1.expect(file.bscFile.fileContents).to.equal('hello my world');
          });
          it('removes text with zero-length edit', () => {
              setFile('hello world', 'brs');
              file.discover(program);
              file.addEdit(5, 11, '');
              file.applyEdits();
              chai_1.expect(file.bscFile.fileContents).to.equal('hello');
          });
          it('replaces text', () => {
              setFile('hello Jim, how are you', 'brs');
              file.discover(program);
              file.addEdit(6, 9, 'Michael');
              file.applyEdits();
              chai_1.expect(file.bscFile.fileContents).to.equal('hello Michael, how are you');
          });
          it('works at beginning of string', () => {
              setFile('hello world', 'brs');
              file.discover(program);
              file.addEdit(0, 5, 'goodbye');
              file.applyEdits();
              chai_1.expect(file.bscFile.fileContents).to.equal('goodbye world');
          });
          it('works with out-of-order edits', () => {
              setFile('one two three', 'brs');
              file.discover(program);
              //middle
              file.addEdit(4, 7, 'twelve');
              //last
              file.addEdit(8, 13, 'thirteen');
              //first
              file.addEdit(0, 3, 'eleven');
              file.applyEdits();
              chai_1.expect(file.bscFile.fileContents).to.equal('eleven twelve thirteen');
          });
          it('finds component interface function names', () => {
              setFile(`<?xml version="1.0" encoding="utf-8" ?>
                  <component name="LoggerComponent">
                      <interface>
                          <function name="doSomething"/>
                          <function
                              name="doSomethingElse" />
                      </interface>
                  </component>
              `, 'xml');
              file.discover(program);
              chai_1.expect(file.componentInterfaceFunctions).to.eql([{
                      name: 'doSomething',
                      offset: getOffset(3, 40)
                  }, {
                      name: 'doSomethingElse',
                      offset: getOffset(5, 34)
                  }]);
          });
      });
      describe('namespaces', () => {
          it('finds namespaces', () => {
              setFile(`
                  namespace NameA
                      sub logWarning()
                      end sub
                  end namespace
                  sub logInfo()
                  end sub
                  namespace NameA.NameB
                      sub logError()
                      end sub
                  end namespace
              `, 'd.bs');
              file.discover(program);
              chai_1.expect(file.namespaces).to.eql([{
                      name: 'NameA',
                      offset: getOffset(1, 26)
                  }, {
                      name: 'NameA.NameB',
                      offset: getOffset(7, 26)
                  }]);
          });
      });
  });
  //# sourceMappingURL=File.spec.js.map