ts2famix 1.0.15 → 1.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22,11 +22,15 @@ var __importStar = (this && this.__importStar) || function (mod) {
22
22
  __setModuleDefault(result, mod);
23
23
  return result;
24
24
  };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
25
28
  Object.defineProperty(exports, "__esModule", { value: true });
26
29
  exports.FamixFunctionsIndex = void 0;
27
30
  const ts_morph_1 = require("ts-morph");
28
31
  const Famix = __importStar(require("../lib/famix/src/model/famix"));
29
32
  const fqn_1 = require("../fqn");
33
+ const grapheme_splitter_1 = __importDefault(require("grapheme-splitter"));
30
34
  /**
31
35
  * This class is used to build a Famix model for the index file anchors
32
36
  */
@@ -50,8 +54,40 @@ class FamixFunctionsIndex {
50
54
  if (sourceElement !== null) {
51
55
  fmxIndexFileAnchor.setFileName(sourceElement.getSourceFile().getFilePath());
52
56
  if (!(sourceElement instanceof ts_morph_1.CommentRange)) {
53
- fmxIndexFileAnchor.setStartPos(sourceElement.getStart());
54
- fmxIndexFileAnchor.setEndPos(sourceElement.getEnd());
57
+ // sourceElement.getStart() and sourceElement.getEnd() are based on text that has not been split into grapheme clusters, so we need to split the text and then adjust the start and end positions by finding the string inside the split text that has been joined together to form the original text
58
+ /**
59
+ * The following logic handles the case of multi-code point characters (e.g. emoji) in the source text.
60
+ * This is needed because Pharo/Smalltalk treats multi-code point characters as a single character,
61
+ * but JavaScript treats them as multiple characters. This means that the start and end positions
62
+ * of a source element in Pharo/Smalltalk will be different than the start and end positions of the
63
+ * same source element in JavaScript. This logic finds the start and end positions of the source
64
+ * element in JavaScript and then uses those positions to set the start and end positions of the
65
+ * Famix index file anchor.
66
+ * It depends on code in the 'grapheme-splitter' package in npm.
67
+ */
68
+ const splitter = new grapheme_splitter_1.default();
69
+ const sourceFileText = sourceElement.getSourceFile().getFullText();
70
+ const hasGraphemeClusters = splitter.countGraphemes(sourceFileText) > 1;
71
+ if (hasGraphemeClusters) {
72
+ const sourceElementText = sourceFileText.substring(sourceElement.getStart(), sourceElement.getEnd());
73
+ const sourceElementTextGraphemes = splitter.splitGraphemes(sourceElementText);
74
+ const sourceFileTextGraphemes = splitter.splitGraphemes(sourceFileText);
75
+ const start = sourceElement.getStart();
76
+ const numberOfGraphemeClustersBeforeStart = splitter.countGraphemes(sourceFileText.substring(0, start));
77
+ // find the start of the sourceElementTextGraphemes array in the sourceFileTextGraphemes array
78
+ const newStart = indexOfSplitArray({ searchArray: sourceFileTextGraphemes,
79
+ targetArray: sourceElementTextGraphemes,
80
+ start: start - numberOfGraphemeClustersBeforeStart });
81
+ const newEnd = newStart + sourceElementTextGraphemes.length;
82
+ // note: the +1 is because the source anchor is 1-based, but ts-morph is 0-based
83
+ fmxIndexFileAnchor.setStartPos(newStart + 1);
84
+ fmxIndexFileAnchor.setEndPos(newEnd + 1);
85
+ }
86
+ else {
87
+ // note: the +1 is because the source anchor is 1-based, but ts-morph is 0-based
88
+ fmxIndexFileAnchor.setStartPos(sourceElement.getStart() + 1);
89
+ fmxIndexFileAnchor.setEndPos(sourceElement.getEnd() + 1);
90
+ }
55
91
  }
56
92
  if (!(famixElement instanceof Famix.Association) && !(famixElement instanceof Famix.Comment) && !(sourceElement instanceof ts_morph_1.CommentRange) && !(sourceElement instanceof ts_morph_1.Identifier) && !(sourceElement instanceof ts_morph_1.ImportSpecifier) && !(sourceElement instanceof ts_morph_1.ExpressionWithTypeArguments)) {
57
93
  famixElement.setFullyQualifiedName(this.FQNFunctions.getFQN(sourceElement));
@@ -60,3 +96,23 @@ class FamixFunctionsIndex {
60
96
  }
61
97
  }
62
98
  exports.FamixFunctionsIndex = FamixFunctionsIndex;
99
+ /**
100
+ * This function works like indexOf, but it works with arrays of grapheme clusters.
101
+ * @param targetArray
102
+ */
103
+ function indexOfSplitArray(params) {
104
+ const { searchArray, targetArray, start = 0 } = params;
105
+ for (let i = start; i <= searchArray.length - targetArray.length; i++) {
106
+ let found = true;
107
+ for (let j = 0; j < targetArray.length; j++) {
108
+ if (searchArray[i + j] !== targetArray[j]) {
109
+ found = false;
110
+ break;
111
+ }
112
+ }
113
+ if (found) {
114
+ return i; // Return the index where the target array was found
115
+ }
116
+ }
117
+ return -1; // Return -1 if the target array was not found in the search array
118
+ }
@@ -52,14 +52,14 @@ class FamixFunctionsTypes {
52
52
  let fmxType;
53
53
  let isPrimitiveType = false;
54
54
  let isParameterizedType = false;
55
- console.info("Creating (or getting) type: " + typeName + "' of element: " + element.getText() + " of kind: " + element.getKindName());
55
+ console.info("Creating (or getting) type: '" + typeName + "' of element: " + element.getText() + " of kind: " + element.getKindName());
56
56
  const typeAncestor = this.findTypeAncestor(element);
57
57
  const ancestorFullyQualifiedName = this.FQNFunctions.getFQN(typeAncestor);
58
58
  const ancestor = this.getFamixEntityByFullyQualifiedName(ancestorFullyQualifiedName);
59
59
  if (!ancestor) {
60
60
  throw new Error(`Ancestor ${ancestorFullyQualifiedName} not found.`);
61
61
  }
62
- if (typeName === "number" || typeName === "string" || typeName === "boolean" || typeName === "bigint" || typeName === "symbol" || typeName === "undefined" || typeName === "null") {
62
+ if (typeName === "number" || typeName === "string" || typeName === "boolean" || typeName === "bigint" || typeName === "symbol" || typeName === "undefined" || typeName === "null" || typeName === "any" || typeName === "unknown" || typeName === "never" || typeName === "void") {
63
63
  isPrimitiveType = true;
64
64
  }
65
65
  if (!isPrimitiveType && typeName.includes("<") && typeName.includes(">") && !(typeName.includes("=>"))) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts2famix",
3
- "version": "1.0.15",
3
+ "version": "1.0.17",
4
4
  "description": "A TypeScript to JSON importer for Moose 10.",
5
5
  "main": "dist/ts2famix-cli.js",
6
6
  "scripts": {
@@ -26,13 +26,13 @@
26
26
  },
27
27
  "license": "MIT",
28
28
  "devDependencies": {
29
- "@types/jest": "^29.5.4",
30
- "@types/node": "^20.6.0",
29
+ "@types/jest": "^29.5.5",
30
+ "@types/node": "^20.6.3",
31
31
  "@types/yargs": "^17.0.24",
32
- "@typescript-eslint/eslint-plugin": "^6.6.0",
33
- "@typescript-eslint/parser": "^6.6.0",
32
+ "@typescript-eslint/eslint-plugin": "^6.7.2",
33
+ "@typescript-eslint/parser": "^6.7.2",
34
34
  "eslint": "^8.49.0",
35
- "jest": "^29.6.4",
35
+ "jest": "^29.7.0",
36
36
  "tplant": "^3.1.2",
37
37
  "ts-jest": "^29.1.1",
38
38
  "ts-node": "^10.9.1",
@@ -40,6 +40,7 @@
40
40
  "typescript": "^5.2.2"
41
41
  },
42
42
  "dependencies": {
43
+ "grapheme-splitter": "^1.0.4",
43
44
  "ts-morph": "^19.0.0",
44
45
  "tsutils": "^3.21.0",
45
46
  "yargs": "^17.7.2"
@@ -2,6 +2,7 @@ import { ClassDeclaration, ConstructorDeclaration, FunctionDeclaration, Identifi
2
2
  import * as Famix from "../lib/famix/src/model/famix";
3
3
  import { FamixRepository } from "../lib/famix/src/famix_repository";
4
4
  import { FQNFunctions } from "../fqn";
5
+ import GraphemeSplitter from "grapheme-splitter";
5
6
 
6
7
  /**
7
8
  * This class is used to build a Famix model for the index file anchors
@@ -30,10 +31,42 @@ export class FamixFunctionsIndex {
30
31
 
31
32
  if (sourceElement !== null) {
32
33
  fmxIndexFileAnchor.setFileName(sourceElement.getSourceFile().getFilePath());
33
-
34
34
  if (!(sourceElement instanceof CommentRange)) {
35
- fmxIndexFileAnchor.setStartPos(sourceElement.getStart());
36
- fmxIndexFileAnchor.setEndPos(sourceElement.getEnd());
35
+ // sourceElement.getStart() and sourceElement.getEnd() are based on text that has not been split into grapheme clusters, so we need to split the text and then adjust the start and end positions by finding the string inside the split text that has been joined together to form the original text
36
+ /**
37
+ * The following logic handles the case of multi-code point characters (e.g. emoji) in the source text.
38
+ * This is needed because Pharo/Smalltalk treats multi-code point characters as a single character,
39
+ * but JavaScript treats them as multiple characters. This means that the start and end positions
40
+ * of a source element in Pharo/Smalltalk will be different than the start and end positions of the
41
+ * same source element in JavaScript. This logic finds the start and end positions of the source
42
+ * element in JavaScript and then uses those positions to set the start and end positions of the
43
+ * Famix index file anchor.
44
+ * It depends on code in the 'grapheme-splitter' package in npm.
45
+ */
46
+ const splitter = new GraphemeSplitter();
47
+ const sourceFileText = sourceElement.getSourceFile().getFullText();
48
+ const hasGraphemeClusters = splitter.countGraphemes(sourceFileText) > 1;
49
+ if (hasGraphemeClusters) {
50
+ const sourceElementText = sourceFileText.substring(sourceElement.getStart(), sourceElement.getEnd());
51
+ const sourceElementTextGraphemes = splitter.splitGraphemes(sourceElementText);
52
+ const sourceFileTextGraphemes = splitter.splitGraphemes(sourceFileText);
53
+ const start = sourceElement.getStart();
54
+ const numberOfGraphemeClustersBeforeStart = splitter.countGraphemes(sourceFileText.substring(0, start));
55
+
56
+ // find the start of the sourceElementTextGraphemes array in the sourceFileTextGraphemes array
57
+ const newStart = indexOfSplitArray({searchArray: sourceFileTextGraphemes,
58
+ targetArray: sourceElementTextGraphemes,
59
+ start: start - numberOfGraphemeClustersBeforeStart});
60
+ const newEnd = newStart + sourceElementTextGraphemes.length;
61
+
62
+ // note: the +1 is because the source anchor is 1-based, but ts-morph is 0-based
63
+ fmxIndexFileAnchor.setStartPos(newStart + 1);
64
+ fmxIndexFileAnchor.setEndPos(newEnd + 1);
65
+ } else {
66
+ // note: the +1 is because the source anchor is 1-based, but ts-morph is 0-based
67
+ fmxIndexFileAnchor.setStartPos(sourceElement.getStart() + 1);
68
+ fmxIndexFileAnchor.setEndPos(sourceElement.getEnd() + 1);
69
+ }
37
70
  }
38
71
 
39
72
  if (!(famixElement instanceof Famix.Association) && !(famixElement instanceof Famix.Comment) && !(sourceElement instanceof CommentRange) && !(sourceElement instanceof Identifier) && !(sourceElement instanceof ImportSpecifier) && !(sourceElement instanceof ExpressionWithTypeArguments)) {
@@ -42,3 +75,30 @@ export class FamixFunctionsIndex {
42
75
  }
43
76
  }
44
77
  }
78
+
79
+
80
+ interface SearchParameters {
81
+ searchArray: string[];
82
+ targetArray: string[];
83
+ start?: number;
84
+ }
85
+ /**
86
+ * This function works like indexOf, but it works with arrays of grapheme clusters.
87
+ * @param targetArray
88
+ */
89
+ function indexOfSplitArray(params: SearchParameters): number {
90
+ const {searchArray, targetArray, start = 0} = params;
91
+ for (let i = start; i <= searchArray.length - targetArray.length; i++) {
92
+ let found = true;
93
+ for (let j = 0; j < targetArray.length; j++) {
94
+ if (searchArray[i + j] !== targetArray[j]) {
95
+ found = false;
96
+ break;
97
+ }
98
+ }
99
+ if (found) {
100
+ return i; // Return the index where the target array was found
101
+ }
102
+ }
103
+ return -1; // Return -1 if the target array was not found in the search array
104
+ }
@@ -34,7 +34,7 @@ export class FamixFunctionsTypes {
34
34
  let isPrimitiveType = false;
35
35
  let isParameterizedType = false;
36
36
 
37
- console.info("Creating (or getting) type: " + typeName + "' of element: " + element.getText() + " of kind: " + element.getKindName());
37
+ console.info("Creating (or getting) type: '" + typeName + "' of element: " + element.getText() + " of kind: " + element.getKindName());
38
38
 
39
39
  const typeAncestor = this.findTypeAncestor(element);
40
40
  const ancestorFullyQualifiedName = this.FQNFunctions.getFQN(typeAncestor);
@@ -43,7 +43,7 @@ export class FamixFunctionsTypes {
43
43
  throw new Error(`Ancestor ${ancestorFullyQualifiedName} not found.`);
44
44
  }
45
45
 
46
- if (typeName === "number" || typeName === "string" || typeName === "boolean" || typeName === "bigint" || typeName === "symbol" || typeName === "undefined" || typeName === "null") {
46
+ if (typeName === "number" || typeName === "string" || typeName === "boolean" || typeName === "bigint" || typeName === "symbol" || typeName === "undefined" || typeName === "null" || typeName === "any" || typeName === "unknown" || typeName === "never" || typeName === "void") {
47
47
  isPrimitiveType = true;
48
48
  }
49
49