ScopeAnalyzer.ts 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import { inject, injectable, } from 'inversify';
  2. import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
  3. import * as eslintScope from 'eslint-scope';
  4. import * as estraverse from 'estraverse';
  5. import * as ESTree from 'estree';
  6. import { IOptions } from '../../interfaces/options/IOptions';
  7. import { IScopeAnalyzer } from '../../interfaces/analyzers/scope-analyzer/IScopeAnalyzer';
  8. import { ObfuscationTarget } from '../../enums/ObfuscationTarget';
  9. import { ecmaVersion } from '../../constants/EcmaVersion';
  10. import { NodeGuards } from '../../node/NodeGuards';
  11. @injectable()
  12. export class ScopeAnalyzer implements IScopeAnalyzer {
  13. /**
  14. * @type {eslintScope.AnalysisOptions}
  15. */
  16. private static readonly eslintScopeOptions: eslintScope.AnalysisOptions = {
  17. ecmaVersion,
  18. optimistic: true
  19. };
  20. /**
  21. * @type {acorn.Options['sourceType'][]}
  22. */
  23. private static readonly sourceTypes: acorn.Options['sourceType'][] = [
  24. 'script',
  25. 'module'
  26. ];
  27. /**
  28. * @type {number}
  29. */
  30. private static readonly emptyRangeValue: number = 0;
  31. /**
  32. * @type {IOptions}
  33. */
  34. private readonly options: IOptions;
  35. /**
  36. * @type {eslintScope.ScopeManager | null}
  37. */
  38. private scopeManager: eslintScope.ScopeManager | null = null;
  39. /**
  40. * @param {IOptions} options
  41. */
  42. constructor (
  43. @inject(ServiceIdentifiers.IOptions) options: IOptions
  44. ) {
  45. this.options = options;
  46. }
  47. /**
  48. * `eslint-scope` reads `ranges` property of a nodes
  49. * Should attach that property to the some custom nodes
  50. *
  51. * @param {Node} astTree
  52. */
  53. private static attachMissingRanges (astTree: ESTree.Node): void {
  54. estraverse.replace(astTree, {
  55. enter: (node: ESTree.Node): ESTree.Node => {
  56. if (!node.range) {
  57. node.range = [
  58. ScopeAnalyzer.emptyRangeValue,
  59. ScopeAnalyzer.emptyRangeValue
  60. ];
  61. }
  62. return node;
  63. }
  64. });
  65. }
  66. /**
  67. * @param {Node} node
  68. * @returns {boolean}
  69. */
  70. private static isRootNode (node: ESTree.Node): boolean {
  71. return NodeGuards.isProgramNode(node) || node.parentNode === node;
  72. }
  73. /**
  74. * @param {Program} astTree
  75. */
  76. public analyze (astTree: ESTree.Node): void {
  77. const sourceTypeLength: number = ScopeAnalyzer.sourceTypes.length;
  78. ScopeAnalyzer.attachMissingRanges(astTree);
  79. for (let i: number = 0; i < sourceTypeLength; i++) {
  80. try {
  81. this.scopeManager = eslintScope.analyze(astTree, {
  82. ...ScopeAnalyzer.eslintScopeOptions,
  83. nodejsScope: this.options.target === ObfuscationTarget.Node,
  84. sourceType: ScopeAnalyzer.sourceTypes[i]
  85. });
  86. return;
  87. } catch (error) {
  88. if (i < sourceTypeLength - 1) {
  89. continue;
  90. }
  91. throw new Error(error);
  92. }
  93. }
  94. throw new Error(`Scope analyzing error`);
  95. }
  96. /**
  97. * @param {Node} node
  98. * @returns {Scope}
  99. */
  100. public acquireScope (node: ESTree.Node): eslintScope.Scope {
  101. if (!this.scopeManager) {
  102. throw new Error('Scope manager is not defined');
  103. }
  104. const scope: eslintScope.Scope | null = this.scopeManager.acquire(
  105. node,
  106. ScopeAnalyzer.isRootNode(node)
  107. );
  108. if (!scope) {
  109. throw new Error('Cannot acquire scope for node');
  110. }
  111. return scope;
  112. }
  113. }