- (chore) update packages
- (refactor) use tsquery for querying AST - (feat) autodetect usage of marker function and remove --marker cli argument - (bugfix) extract strings when TranslateService is declared directly as a class parameter. Closes https://github.com/biesbjerg/ngx-translate-extract/issues/83 - (bugfix) handle split strings: marker('hello ' + 'world') is now extracted as a single string: 'hello world'
This commit is contained in:
		| @@ -52,7 +52,7 @@ If you want to use spaces instead, you can do the following: | |||||||
| `ngx-translate-extract --input ./src --output ./src/i18n/en.json --format-indentation '  '` | `ngx-translate-extract --input ./src --output ./src/i18n/en.json --format-indentation '  '` | ||||||
|  |  | ||||||
| ## Mark strings for extraction using a marker function | ## Mark strings for extraction using a marker function | ||||||
| If, for some reason, you want to extract strings not passed directly to `TranslateService`'s `get()` or `instant()` methods, you can wrap them in a custom marker function to let `ngx-translate-extract` know you want to extract them. | If, for some reason, you want to extract strings not passed directly to `TranslateService`'s `get()` or `instant()` methods, you can wrap them in a marker function to let `ngx-translate-extract` know you want to extract them. | ||||||
|  |  | ||||||
| Install marker function: | Install marker function: | ||||||
| `npm install @biesbjerg/ngx-translate-extract-marker` | `npm install @biesbjerg/ngx-translate-extract-marker` | ||||||
| @@ -94,8 +94,6 @@ Options: | |||||||
|   --output, -o                Paths where you would like to save extracted |   --output, -o                Paths where you would like to save extracted | ||||||
|                               strings. You can use path expansion, glob patterns |                               strings. You can use path expansion, glob patterns | ||||||
|                               and multiple paths              [array] [required] |                               and multiple paths              [array] [required] | ||||||
|   --marker, -m                Extract strings passed to a marker function |  | ||||||
|                                                        [string] [default: false] |  | ||||||
|   --format, -f                Output format |   --format, -f                Output format | ||||||
|           [string] [choices: "json", "namespaced-json", "pot"] [default: "json"] |           [string] [choices: "json", "namespaced-json", "pot"] [default: "json"] | ||||||
|   --format-indentation, --fi  Output format indentation   [string] [default: "	"] |   --format-indentation, --fi  Output format indentation   [string] [default: "	"] | ||||||
|   | |||||||
							
								
								
									
										151
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										151
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,13 +1,13 @@ | |||||||
| { | { | ||||||
|   "name": "@biesbjerg/ngx-translate-extract", |   "name": "@biesbjerg/ngx-translate-extract", | ||||||
|   "version": "3.0.4", |   "version": "4.0.0", | ||||||
|   "lockfileVersion": 1, |   "lockfileVersion": 1, | ||||||
|   "requires": true, |   "requires": true, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@angular/compiler": { |     "@angular/compiler": { | ||||||
|       "version": "8.2.2", |       "version": "8.2.6", | ||||||
|       "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-8.2.2.tgz", |       "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-8.2.6.tgz", | ||||||
|       "integrity": "sha512-UMhOQehvi9u1r4u48Ymwm5JkdOKoH057ImCo26WqRqJBUgA44xwmUsKLFAmSg1JqzWCO5pBDyA3RaNBscD8ZzQ==", |       "integrity": "sha512-NdTY2n0XNRmKixbKDWB++9tEDLFwN0/Bp/1lXJ4qF/8V5Wju6IJ/UZZKjR5C4uiKtF17T3GzubhXgghumt5UVA==", | ||||||
|       "requires": { |       "requires": { | ||||||
|         "tslib": "^1.9.0" |         "tslib": "^1.9.0" | ||||||
|       } |       } | ||||||
| @@ -32,10 +32,18 @@ | |||||||
|         "js-tokens": "^4.0.0" |         "js-tokens": "^4.0.0" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "@phenomnomnominal/tsquery": { | ||||||
|  |       "version": "3.0.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@phenomnomnominal/tsquery/-/tsquery-3.0.0.tgz", | ||||||
|  |       "integrity": "sha512-SW8lKitBHWJ9fAYkJ9kJivuctwNYCh3BUxLdH0+XiR1GPBiu+7qiZzh8p8jqlj1LgVC1TbvfNFroaEsmYlL8Iw==", | ||||||
|  |       "requires": { | ||||||
|  |         "esquery": "^1.0.1" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "@types/chai": { |     "@types/chai": { | ||||||
|       "version": "4.2.0", |       "version": "4.2.2", | ||||||
|       "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.0.tgz", |       "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.2.tgz", | ||||||
|       "integrity": "sha512-zw8UvoBEImn392tLjxoavuonblX/4Yb9ha4KBU10FirCfwgzhKO0dvyJSF9ByxV1xK1r2AgnAi/tvQaLgxQqxA==", |       "integrity": "sha512-8V2aCcPM3WLuJvJpF6N60uUvdZb7NHjpjQlLk1QmZbTP4XZET/FX0c3TJ3K7qt4L98FS1Pifa8BVofTVuJFWVA==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|     "@types/events": { |     "@types/events": { | ||||||
| @@ -83,9 +91,9 @@ | |||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|     "@types/node": { |     "@types/node": { | ||||||
|       "version": "7.0.8", |       "version": "12.7.5", | ||||||
|       "resolved": "https://registry.npmjs.org/@types/node/-/node-7.0.8.tgz", |       "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.5.tgz", | ||||||
|       "integrity": "sha1-JeTdgEtjDJFq5nEjPm1x9s4YEko=", |       "integrity": "sha512-9fq4jZVhPNW8r+UYKnxF1e2HkDWOWKM5bC2/7c9wPV835I0aOrVbS/Hw/pWPk2uKrNXQqg9Z959Kz+IYDd5p3w==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|     "@types/yargs": { |     "@types/yargs": { | ||||||
| @@ -143,9 +151,12 @@ | |||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|     "ansi-escapes": { |     "ansi-escapes": { | ||||||
|       "version": "3.2.0", |       "version": "4.2.1", | ||||||
|       "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", |       "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.2.1.tgz", | ||||||
|       "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==" |       "integrity": "sha512-Cg3ymMAdN10wOk/VYfLV7KCQyv7EDirJ64500sU7n9UlmioEtDuU5Gd+hj73hXSU/ex7tHJSssmyftDdkMLO8Q==", | ||||||
|  |       "requires": { | ||||||
|  |         "type-fest": "^0.5.2" | ||||||
|  |       } | ||||||
|     }, |     }, | ||||||
|     "ansi-regex": { |     "ansi-regex": { | ||||||
|       "version": "3.0.0", |       "version": "3.0.0", | ||||||
| @@ -162,9 +173,9 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "arg": { |     "arg": { | ||||||
|       "version": "4.1.0", |       "version": "4.1.1", | ||||||
|       "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.0.tgz", |       "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.1.tgz", | ||||||
|       "integrity": "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==", |       "integrity": "sha512-SlmP3fEA88MBv0PypnXZ8ZfJhwmDeIE3SP71j37AiXQBXYosPV0x6uISAaHYSlSVhmHOVkomen0tbGk6Anlebw==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|     "argparse": { |     "argparse": { | ||||||
| @@ -482,6 +493,19 @@ | |||||||
|       "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", |       "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|  |     "esquery": { | ||||||
|  |       "version": "1.0.1", | ||||||
|  |       "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", | ||||||
|  |       "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", | ||||||
|  |       "requires": { | ||||||
|  |         "estraverse": "^4.0.0" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "estraverse": { | ||||||
|  |       "version": "4.3.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", | ||||||
|  |       "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" | ||||||
|  |     }, | ||||||
|     "esutils": { |     "esutils": { | ||||||
|       "version": "2.0.3", |       "version": "2.0.3", | ||||||
|       "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", |       "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", | ||||||
| @@ -550,9 +574,9 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "gettext-parser": { |     "gettext-parser": { | ||||||
|       "version": "4.0.1", |       "version": "4.0.2", | ||||||
|       "resolved": "https://registry.npmjs.org/gettext-parser/-/gettext-parser-4.0.1.tgz", |       "resolved": "https://registry.npmjs.org/gettext-parser/-/gettext-parser-4.0.2.tgz", | ||||||
|       "integrity": "sha512-ny1f9saN1xnhto5UzDOp7djJy7NbK6ebDAmOFXwp0DDu5KkQ5u3WF6giFU3BXHVqkS+3bxjXS1AmSUbX64fblA==", |       "integrity": "sha512-JPCBpGzm01te+nTenJwWqKDzixYPY4pInedixpcMl4GPEJeia/cH2TJCh32IggDrrLYrzqA8OitXZLpBdrx4Gg==", | ||||||
|       "requires": { |       "requires": { | ||||||
|         "content-type": "^1.0.4", |         "content-type": "^1.0.4", | ||||||
|         "encoding": "^0.1.12", |         "encoding": "^0.1.12", | ||||||
| @@ -1147,9 +1171,9 @@ | |||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|     "source-map-support": { |     "source-map-support": { | ||||||
|       "version": "0.5.12", |       "version": "0.5.13", | ||||||
|       "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", |       "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", | ||||||
|       "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", |       "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "requires": { |       "requires": { | ||||||
|         "buffer-from": "^1.0.0", |         "buffer-from": "^1.0.0", | ||||||
| @@ -1173,18 +1197,11 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "string_decoder": { |     "string_decoder": { | ||||||
|       "version": "1.2.0", |       "version": "1.3.0", | ||||||
|       "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz", |       "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", | ||||||
|       "integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==", |       "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", | ||||||
|       "requires": { |       "requires": { | ||||||
|         "safe-buffer": "~5.1.0" |         "safe-buffer": "~5.2.0" | ||||||
|       }, |  | ||||||
|       "dependencies": { |  | ||||||
|         "safe-buffer": { |  | ||||||
|           "version": "5.1.2", |  | ||||||
|           "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", |  | ||||||
|           "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" |  | ||||||
|         } |  | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "strip-ansi": { |     "strip-ansi": { | ||||||
| @@ -1217,18 +1234,26 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "supports-hyperlinks": { |     "supports-hyperlinks": { | ||||||
|       "version": "1.0.1", |       "version": "2.0.0", | ||||||
|       "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-1.0.1.tgz", |       "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.0.0.tgz", | ||||||
|       "integrity": "sha512-HHi5kVSefKaJkGYXbDuKbUGRVxqnWGn3J2e39CYcNJEfWciGq2zYtOhXLTlvrOZW1QU7VX67w7fMmWafHX9Pfw==", |       "integrity": "sha512-bFhn0MQ8qefLyJ3K7PpHiPUTuTVPWw6RXfaMeV6xgJLXtBbszyboz1bvGTVv4R0YpQm2DqlXXn0fFHhxUHVE5w==", | ||||||
|       "requires": { |       "requires": { | ||||||
|         "has-flag": "^2.0.0", |         "has-flag": "^4.0.0", | ||||||
|         "supports-color": "^5.0.0" |         "supports-color": "^7.0.0" | ||||||
|       }, |       }, | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "has-flag": { |         "has-flag": { | ||||||
|           "version": "2.0.0", |           "version": "4.0.0", | ||||||
|           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", |           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", | ||||||
|           "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=" |           "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" | ||||||
|  |         }, | ||||||
|  |         "supports-color": { | ||||||
|  |           "version": "7.0.0", | ||||||
|  |           "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.0.0.tgz", | ||||||
|  |           "integrity": "sha512-WRt32iTpYEZWYOpcetGm0NPeSvaebccx7hhS/5M6sAiqnhedtFCHFxkjzZlJvFNCPowiKSFGiZk5USQDFy83vQ==", | ||||||
|  |           "requires": { | ||||||
|  |             "has-flag": "^4.0.0" | ||||||
|  |           } | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
| @@ -1238,18 +1263,18 @@ | |||||||
|       "integrity": "sha512-I42EWhJ+2aeNQawGx1VtpO0DFI9YcfuvAMNIdKyf/6sRbHJ4P+ZQ/zIT87tE+ln1ymAGcCJds4dolfSAS0AcNg==" |       "integrity": "sha512-I42EWhJ+2aeNQawGx1VtpO0DFI9YcfuvAMNIdKyf/6sRbHJ4P+ZQ/zIT87tE+ln1ymAGcCJds4dolfSAS0AcNg==" | ||||||
|     }, |     }, | ||||||
|     "terminal-link": { |     "terminal-link": { | ||||||
|       "version": "1.3.0", |       "version": "2.0.0", | ||||||
|       "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-1.3.0.tgz", |       "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.0.0.tgz", | ||||||
|       "integrity": "sha512-nFaWG/gs3brGi3opgWU2+dyFGbQ7tueSRYOBOD8URdDXCbAGqDEZzuskCc+okCClYcJFDPwn8e2mbv4FqAnWFA==", |       "integrity": "sha512-rdBAY35jUvVapqCuhehjenLbYY73cVgRQ6podD6u9EDBomBBHjCOtmq2InPgPpTysOIOsQ5PdBzwSC/sKjv6ew==", | ||||||
|       "requires": { |       "requires": { | ||||||
|         "ansi-escapes": "^3.2.0", |         "ansi-escapes": "^4.2.1", | ||||||
|         "supports-hyperlinks": "^1.0.1" |         "supports-hyperlinks": "^2.0.0" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "ts-node": { |     "ts-node": { | ||||||
|       "version": "8.3.0", |       "version": "8.4.1", | ||||||
|       "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.3.0.tgz", |       "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.4.1.tgz", | ||||||
|       "integrity": "sha512-dyNS/RqyVTDcmNM4NIBAeDMpsAdaQ+ojdf0GOLqE6nwJOgzEkdRNzJywhDfwnuvB10oa6NLVG1rUJQCpRN7qoQ==", |       "integrity": "sha512-5LpRN+mTiCs7lI5EtbXmF/HfMeCjzt7DH9CZwtkr6SywStrNQC723wG+aOWFiLNn7zT3kD/RnFqi3ZUfr4l5Qw==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "requires": { |       "requires": { | ||||||
|         "arg": "^4.1.0", |         "arg": "^4.1.0", | ||||||
| @@ -1273,16 +1298,16 @@ | |||||||
|       "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" |       "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" | ||||||
|     }, |     }, | ||||||
|     "tslint": { |     "tslint": { | ||||||
|       "version": "5.19.0", |       "version": "5.20.0", | ||||||
|       "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.19.0.tgz", |       "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.0.tgz", | ||||||
|       "integrity": "sha512-1LwwtBxfRJZnUvoS9c0uj8XQtAnyhWr9KlNvDIdB+oXyT+VpsOAaEhEgKi1HrZ8rq0ki/AAnbGSv4KM6/AfVZw==", |       "integrity": "sha512-2vqIvkMHbnx8acMogAERQ/IuINOq6DFqgF8/VDvhEkBqQh/x6SP0Y+OHnKth9/ZcHQSroOZwUQSN18v8KKF0/g==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "requires": { |       "requires": { | ||||||
|         "@babel/code-frame": "^7.0.0", |         "@babel/code-frame": "^7.0.0", | ||||||
|         "builtin-modules": "^1.1.1", |         "builtin-modules": "^1.1.1", | ||||||
|         "chalk": "^2.3.0", |         "chalk": "^2.3.0", | ||||||
|         "commander": "^2.12.1", |         "commander": "^2.12.1", | ||||||
|         "diff": "^3.2.0", |         "diff": "^4.0.1", | ||||||
|         "glob": "^7.1.1", |         "glob": "^7.1.1", | ||||||
|         "js-yaml": "^3.13.1", |         "js-yaml": "^3.13.1", | ||||||
|         "minimatch": "^3.0.4", |         "minimatch": "^3.0.4", | ||||||
| @@ -1291,6 +1316,14 @@ | |||||||
|         "semver": "^5.3.0", |         "semver": "^5.3.0", | ||||||
|         "tslib": "^1.8.0", |         "tslib": "^1.8.0", | ||||||
|         "tsutils": "^2.29.0" |         "tsutils": "^2.29.0" | ||||||
|  |       }, | ||||||
|  |       "dependencies": { | ||||||
|  |         "diff": { | ||||||
|  |           "version": "4.0.1", | ||||||
|  |           "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", | ||||||
|  |           "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==", | ||||||
|  |           "dev": true | ||||||
|  |         } | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "tslint-eslint-rules": { |     "tslint-eslint-rules": { | ||||||
| @@ -1342,9 +1375,9 @@ | |||||||
|       "integrity": "sha512-DWkS49EQKVX//Tbupb9TFa19c7+MK1XmzkrZUR8TAktmE/DizXoaoJV6TZ/tSIPXipqNiRI6CyAe7x69Jb6RSw==" |       "integrity": "sha512-DWkS49EQKVX//Tbupb9TFa19c7+MK1XmzkrZUR8TAktmE/DizXoaoJV6TZ/tSIPXipqNiRI6CyAe7x69Jb6RSw==" | ||||||
|     }, |     }, | ||||||
|     "typescript": { |     "typescript": { | ||||||
|       "version": "3.5.3", |       "version": "3.6.3", | ||||||
|       "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz", |       "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.3.tgz", | ||||||
|       "integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==" |       "integrity": "sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==" | ||||||
|     }, |     }, | ||||||
|     "util": { |     "util": { | ||||||
|       "version": "0.10.3", |       "version": "0.10.3", | ||||||
| @@ -1636,9 +1669,9 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "yn": { |     "yn": { | ||||||
|       "version": "3.1.0", |       "version": "3.1.1", | ||||||
|       "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.0.tgz", |       "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", | ||||||
|       "integrity": "sha512-kKfnnYkbTfrAdd0xICNFw7Atm8nKpLcLv9AZGEt+kczL/WQVai4e2V6ZN8U/O+iI6WrNuJjNNOyu4zfhl9D3Hg==", |       "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   | |||||||
							
								
								
									
										20
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "@biesbjerg/ngx-translate-extract", |   "name": "@biesbjerg/ngx-translate-extract", | ||||||
|   "version": "3.0.5", |   "version": "4.0.0", | ||||||
|   "description": "Extract strings from projects using ngx-translate", |   "description": "Extract strings from projects using ngx-translate", | ||||||
|   "main": "dist/index.js", |   "main": "dist/index.js", | ||||||
|   "typings": "dist/index.d.ts", |   "typings": "dist/index.d.ts", | ||||||
| @@ -40,36 +40,38 @@ | |||||||
|   }, |   }, | ||||||
|   "homepage": "https://github.com/biesbjerg/ngx-translate-extract", |   "homepage": "https://github.com/biesbjerg/ngx-translate-extract", | ||||||
|   "engines": { |   "engines": { | ||||||
|     "node": ">=4.3.2" |     "node": ">=8" | ||||||
|   }, |   }, | ||||||
|   "config": {}, |   "config": {}, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@types/chai": "^4.2.0", |     "@types/chai": "^4.2.2", | ||||||
|     "@types/flat": "^0.0.28", |     "@types/flat": "^0.0.28", | ||||||
|     "@types/glob": "^7.1.1", |     "@types/glob": "^7.1.1", | ||||||
|     "@types/mkdirp": "^0.5.2", |     "@types/mkdirp": "^0.5.2", | ||||||
|     "@types/mocha": "^5.2.7", |     "@types/mocha": "^5.2.7", | ||||||
|  |     "@types/node": "^12.7.5", | ||||||
|     "@types/yargs": "^13.0.2", |     "@types/yargs": "^13.0.2", | ||||||
|     "chai": "^4.2.0", |     "chai": "^4.2.0", | ||||||
|     "mocha": "^6.2.0", |     "mocha": "^6.2.0", | ||||||
|     "ts-node": "^8.3.0", |     "ts-node": "^8.4.1", | ||||||
|     "tslint": "^5.19.0", |     "tslint": "^5.20.0", | ||||||
|     "tslint-eslint-rules": "^5.4.0" |     "tslint-eslint-rules": "^5.4.0" | ||||||
|   }, |   }, | ||||||
|   "bundledDependencies": [ |   "bundledDependencies": [ | ||||||
|     "flat" |     "flat" | ||||||
|   ], |   ], | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@angular/compiler": "^8.2.2", |     "@angular/compiler": "^8.2.6", | ||||||
|  |     "@phenomnomnominal/tsquery": "^3.0.0", | ||||||
|     "boxen": "^4.1.0", |     "boxen": "^4.1.0", | ||||||
|     "colorette": "^1.1.0", |     "colorette": "^1.1.0", | ||||||
|     "flat": "github:lenchvolodymyr/flat#ffe77ef", |     "flat": "github:lenchvolodymyr/flat#ffe77ef", | ||||||
|     "gettext-parser": "^4.0.1", |     "gettext-parser": "^4.0.2", | ||||||
|     "glob": "^7.1.4", |     "glob": "^7.1.4", | ||||||
|     "mkdirp": "^0.5.1", |     "mkdirp": "^0.5.1", | ||||||
|     "path": "^0.12.7", |     "path": "^0.12.7", | ||||||
|     "terminal-link": "^1.3.0", |     "terminal-link": "^2.0.0", | ||||||
|     "typescript": "^3.5.3", |     "typescript": "^3.6.3", | ||||||
|     "yargs": "^14.0.0" |     "yargs": "^14.0.0" | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ import { ParserInterface } from '../parsers/parser.interface'; | |||||||
| import { PipeParser } from '../parsers/pipe.parser'; | import { PipeParser } from '../parsers/pipe.parser'; | ||||||
| import { DirectiveParser } from '../parsers/directive.parser'; | import { DirectiveParser } from '../parsers/directive.parser'; | ||||||
| import { ServiceParser } from '../parsers/service.parser'; | import { ServiceParser } from '../parsers/service.parser'; | ||||||
| import { FunctionParser } from '../parsers/function.parser'; | import { MarkerParser } from '../parsers/marker.parser'; | ||||||
| import { PostProcessorInterface } from '../post-processors/post-processor.interface'; | import { PostProcessorInterface } from '../post-processors/post-processor.interface'; | ||||||
| import { SortByKeyPostProcessor } from '../post-processors/sort-by-key.post-processor'; | import { SortByKeyPostProcessor } from '../post-processors/sort-by-key.post-processor'; | ||||||
| import { KeyAsDefaultValuePostProcessor } from '../post-processors/key-as-default-value.post-processor'; | import { KeyAsDefaultValuePostProcessor } from '../post-processors/key-as-default-value.post-processor'; | ||||||
| @@ -29,7 +29,7 @@ export const cli = yargs | |||||||
| 		normalize: true | 		normalize: true | ||||||
| 	}) | 	}) | ||||||
| 	.check(options => { | 	.check(options => { | ||||||
| 		options.input.forEach((dir: string) => { | 		(options.input as unknown as string[]).forEach((dir: string) => { | ||||||
| 			if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) { | 			if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) { | ||||||
| 				throw new Error(`The path you supplied was not found: '${dir}'`); | 				throw new Error(`The path you supplied was not found: '${dir}'`); | ||||||
| 			} | 			} | ||||||
| @@ -50,12 +50,6 @@ export const cli = yargs | |||||||
| 		normalize: true, | 		normalize: true, | ||||||
| 		required: true | 		required: true | ||||||
| 	}) | 	}) | ||||||
| 	.option('marker', { |  | ||||||
| 		alias: 'm', |  | ||||||
| 		describe: 'Extract strings passed to a marker function', |  | ||||||
| 		default: false, |  | ||||||
| 		type: 'string' |  | ||||||
| 	}) |  | ||||||
| 	.option('format', { | 	.option('format', { | ||||||
| 		alias: 'f', | 		alias: 'f', | ||||||
| 		describe: 'Output format', | 		describe: 'Output format', | ||||||
| @@ -96,7 +90,7 @@ export const cli = yargs | |||||||
| 	.exitProcess(true) | 	.exitProcess(true) | ||||||
| 	.parse(process.argv); | 	.parse(process.argv); | ||||||
|  |  | ||||||
| const extractTask = new ExtractTask(cli.input, cli.output, { | const extractTask = new ExtractTask(cli.input as unknown as string[], cli.output, { | ||||||
| 	replace: cli.replace, | 	replace: cli.replace, | ||||||
| 	patterns: cli.patterns | 	patterns: cli.patterns | ||||||
| }); | }); | ||||||
| @@ -105,13 +99,9 @@ const extractTask = new ExtractTask(cli.input, cli.output, { | |||||||
| const parsers: ParserInterface[] = [ | const parsers: ParserInterface[] = [ | ||||||
| 	new PipeParser(), | 	new PipeParser(), | ||||||
| 	new DirectiveParser(), | 	new DirectiveParser(), | ||||||
| 	new ServiceParser() | 	new ServiceParser(), | ||||||
|  | 	new MarkerParser() | ||||||
| ]; | ]; | ||||||
| if (cli.marker) { |  | ||||||
| 	parsers.push(new FunctionParser({ |  | ||||||
| 		identifier: cli.marker |  | ||||||
| 	})); |  | ||||||
| } |  | ||||||
| extractTask.setParsers(parsers); | extractTask.setParsers(parsers); | ||||||
|  |  | ||||||
| // Post processors | // Post processors | ||||||
|   | |||||||
| @@ -100,17 +100,20 @@ export class ExtractTask implements TaskInterface { | |||||||
| 	 * Extract strings from specified input dirs using configured parsers | 	 * Extract strings from specified input dirs using configured parsers | ||||||
| 	 */ | 	 */ | ||||||
| 	protected extract(): TranslationCollection { | 	protected extract(): TranslationCollection { | ||||||
| 		let extracted: TranslationCollection = new TranslationCollection(); | 		let collection: TranslationCollection = new TranslationCollection(); | ||||||
| 		this.inputs.forEach(dir => { | 		this.inputs.forEach(dir => { | ||||||
| 			this.readDir(dir, this.options.patterns).forEach(path => { | 			this.readDir(dir, this.options.patterns).forEach(path => { | ||||||
| 				this.out(dim('- %s'), path); | 				this.out(dim('- %s'), path); | ||||||
| 				const contents: string = fs.readFileSync(path, 'utf-8'); | 				const contents: string = fs.readFileSync(path, 'utf-8'); | ||||||
| 				this.parsers.forEach(parser => { | 				this.parsers.forEach(parser => { | ||||||
| 					extracted = extracted.union(parser.extract(contents, path)); | 					const extracted = parser.extract(contents, path); | ||||||
|  | 					if (extracted) { | ||||||
|  | 						collection = collection.union(extracted); | ||||||
|  | 					} | ||||||
| 				}); | 				}); | ||||||
| 			}); | 			}); | ||||||
| 		}); | 		}); | ||||||
| 		return extracted; | 		return collection; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
|   | |||||||
| @@ -6,11 +6,10 @@ export * from './cli/tasks/task.interface'; | |||||||
| export * from './cli/tasks/extract.task'; | export * from './cli/tasks/extract.task'; | ||||||
|  |  | ||||||
| export * from './parsers/parser.interface'; | export * from './parsers/parser.interface'; | ||||||
| export * from './parsers/abstract-ast.parser'; |  | ||||||
| export * from './parsers/directive.parser'; | export * from './parsers/directive.parser'; | ||||||
| export * from './parsers/pipe.parser'; | export * from './parsers/pipe.parser'; | ||||||
| export * from './parsers/service.parser'; | export * from './parsers/service.parser'; | ||||||
| export * from './parsers/function.parser'; | export * from './parsers/marker.parser'; | ||||||
|  |  | ||||||
| export * from './compilers/compiler.interface'; | export * from './compilers/compiler.interface'; | ||||||
| export * from './compilers/compiler.factory'; | export * from './compilers/compiler.factory'; | ||||||
|   | |||||||
| @@ -1,48 +0,0 @@ | |||||||
| import { |  | ||||||
| 	createSourceFile, |  | ||||||
| 	SourceFile, |  | ||||||
| 	CallExpression, |  | ||||||
| 	Node, |  | ||||||
| 	SyntaxKind, |  | ||||||
| 	StringLiteral, |  | ||||||
| 	NoSubstitutionTemplateLiteral |  | ||||||
| } from 'typescript'; |  | ||||||
|  |  | ||||||
| export abstract class AbstractAstParser { |  | ||||||
|  |  | ||||||
| 	protected sourceFile: SourceFile; |  | ||||||
|  |  | ||||||
| 	protected createSourceFile(path: string, contents: string): SourceFile { |  | ||||||
| 		return createSourceFile(path, contents, null, /*setParentNodes */ false); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	/** |  | ||||||
| 	 * Get strings from function call's first argument |  | ||||||
| 	 */ |  | ||||||
| 	protected getStringLiterals(callNode: CallExpression): string[] { |  | ||||||
| 		if (!callNode.arguments.length) { |  | ||||||
| 			return[]; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		const firstArg = callNode.arguments[0]; |  | ||||||
|  |  | ||||||
| 		return this.findNodes(firstArg, [ |  | ||||||
| 			SyntaxKind.StringLiteral, |  | ||||||
| 			SyntaxKind.NoSubstitutionTemplateLiteral |  | ||||||
| 		]) |  | ||||||
| 		.map((node: StringLiteral | NoSubstitutionTemplateLiteral) => node.text); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	/** |  | ||||||
| 	 * Find all child nodes of a kind |  | ||||||
| 	 */ |  | ||||||
| 	protected findNodes(node: Node, kinds: SyntaxKind[]): Node[] { |  | ||||||
| 		const childrenNodes: Node[] = node.getChildren(this.sourceFile); |  | ||||||
| 		const initialValue: Node[] = kinds.includes(node.kind) ? [node] : []; |  | ||||||
|  |  | ||||||
| 		return childrenNodes.reduce((result: Node[], childNode: Node) => { |  | ||||||
| 			return result.concat(this.findNodes(childNode, kinds)); |  | ||||||
| 		}, initialValue); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,60 +0,0 @@ | |||||||
| import { Node, CallExpression, SyntaxKind, Identifier } from 'typescript'; |  | ||||||
|  |  | ||||||
| import { ParserInterface } from './parser.interface'; |  | ||||||
| import { AbstractAstParser } from './abstract-ast.parser'; |  | ||||||
| import { TranslationCollection } from '../utils/translation.collection'; |  | ||||||
|  |  | ||||||
| export class FunctionParser extends AbstractAstParser implements ParserInterface { |  | ||||||
|  |  | ||||||
| 	protected functionIdentifier: string = 'marker'; |  | ||||||
|  |  | ||||||
| 	public constructor(options?: any) { |  | ||||||
| 		super(); |  | ||||||
| 		if (options && typeof options.identifier !== 'undefined') { |  | ||||||
| 			this.functionIdentifier = options.identifier; |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	public extract(template: string, path: string): TranslationCollection { |  | ||||||
| 		let collection: TranslationCollection = new TranslationCollection(); |  | ||||||
|  |  | ||||||
| 		this.sourceFile = this.createSourceFile(path, template); |  | ||||||
|  |  | ||||||
| 		const callNodes = this.findCallNodes(); |  | ||||||
| 		callNodes.forEach(callNode => { |  | ||||||
| 			const keys: string[] = this.getStringLiterals(callNode); |  | ||||||
| 			if (keys && keys.length) { |  | ||||||
| 				collection = collection.addKeys(keys); |  | ||||||
| 			} |  | ||||||
| 		}); |  | ||||||
| 		return collection; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	/** |  | ||||||
| 	 * Find all calls to marker function |  | ||||||
| 	 */ |  | ||||||
| 	protected findCallNodes(node?: Node): CallExpression[] { |  | ||||||
| 		if (!node) { |  | ||||||
| 			node = this.sourceFile; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		let callNodes = this.findNodes(node, [SyntaxKind.CallExpression]) as CallExpression[]; |  | ||||||
| 		callNodes = callNodes |  | ||||||
| 			.filter(callNode => { |  | ||||||
| 				// Only call expressions with arguments |  | ||||||
| 				if (callNode.arguments.length < 1) { |  | ||||||
| 					return false; |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				const identifier = (callNode.getChildAt(0) as Identifier).text; |  | ||||||
| 				if (identifier !== this.functionIdentifier) { |  | ||||||
| 					return false; |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				return true; |  | ||||||
| 			}); |  | ||||||
|  |  | ||||||
| 		return callNodes; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| } |  | ||||||
							
								
								
									
										34
									
								
								src/parsers/marker.parser.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/parsers/marker.parser.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | import { tsquery } from '@phenomnomnominal/tsquery'; | ||||||
|  |  | ||||||
|  | import { ParserInterface } from './parser.interface'; | ||||||
|  | import { TranslationCollection } from '../utils/translation.collection'; | ||||||
|  | import { getNamedImportAlias, findFunctionCallExpressions, getStringsFromExpression } from '../utils/ast-helpers'; | ||||||
|  |  | ||||||
|  | const MARKER_PACKAGE_MODULE_NAME = '@biesbjerg/ngx-translate-extract-marker'; | ||||||
|  | const MARKER_PACKAGE_IMPORT_NAME = 'marker'; | ||||||
|  |  | ||||||
|  | export class MarkerParser implements ParserInterface { | ||||||
|  |  | ||||||
|  | 	public extract(contents: string, filePath: string): TranslationCollection { | ||||||
|  | 		const sourceFile = tsquery.ast(contents, filePath); | ||||||
|  |  | ||||||
|  | 		const markerFnName = getNamedImportAlias(sourceFile, MARKER_PACKAGE_MODULE_NAME, MARKER_PACKAGE_IMPORT_NAME); | ||||||
|  | 		if (!markerFnName) { | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		let collection: TranslationCollection = new TranslationCollection(); | ||||||
|  |  | ||||||
|  | 		const callNodes = findFunctionCallExpressions(sourceFile, markerFnName); | ||||||
|  | 		callNodes.forEach(callNode => { | ||||||
|  | 			const [firstArgNode] = callNode.arguments; | ||||||
|  | 			if (!firstArgNode) { | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  | 			const strings = getStringsFromExpression(firstArgNode); | ||||||
|  | 			collection = collection.addKeys(strings); | ||||||
|  | 		}); | ||||||
|  | 		return collection; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -1,142 +1,41 @@ | |||||||
| import { | import { tsquery } from '@phenomnomnominal/tsquery'; | ||||||
| 	SourceFile, |  | ||||||
| 	Node, |  | ||||||
| 	ConstructorDeclaration, |  | ||||||
| 	Identifier, |  | ||||||
| 	TypeReferenceNode, |  | ||||||
| 	ClassDeclaration, |  | ||||||
| 	SyntaxKind, |  | ||||||
| 	CallExpression, |  | ||||||
| 	PropertyAccessExpression, |  | ||||||
| 	isPropertyAccessExpression |  | ||||||
| } from 'typescript'; |  | ||||||
|  |  | ||||||
| import { ParserInterface } from './parser.interface'; | import { ParserInterface } from './parser.interface'; | ||||||
| import { AbstractAstParser } from './abstract-ast.parser'; |  | ||||||
| import { TranslationCollection } from '../utils/translation.collection'; | import { TranslationCollection } from '../utils/translation.collection'; | ||||||
|  | import { findClasses, findClassPropertyByType, findMethodCallExpression, getStringsFromExpression } from '../utils/ast-helpers'; | ||||||
|  |  | ||||||
| export class ServiceParser extends AbstractAstParser implements ParserInterface { | const TRANSLATE_SERVICE_TYPE_REFERENCE = 'TranslateService'; | ||||||
|  | const TRANSLATE_SERVICE_METHOD_NAMES = ['get', 'instant', 'stream']; | ||||||
|  |  | ||||||
| 	protected sourceFile: SourceFile; | export class ServiceParser implements ParserInterface { | ||||||
|  |  | ||||||
|  | 	public extract(source: string, filePath: string): TranslationCollection { | ||||||
|  | 		const sourceFile = tsquery.ast(source, filePath); | ||||||
|  |  | ||||||
|  | 		const classNodes = findClasses(sourceFile); | ||||||
|  | 		if (!classNodes) { | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 	public extract(template: string, path: string): TranslationCollection { |  | ||||||
| 		let collection: TranslationCollection = new TranslationCollection(); | 		let collection: TranslationCollection = new TranslationCollection(); | ||||||
|  |  | ||||||
| 		this.sourceFile = this.createSourceFile(path, template); |  | ||||||
| 		const classNodes = this.findClassNodes(this.sourceFile); |  | ||||||
| 		classNodes.forEach(classNode => { | 		classNodes.forEach(classNode => { | ||||||
| 			const constructorNode = this.findConstructorNode(classNode); | 			const propName: string = findClassPropertyByType(classNode, TRANSLATE_SERVICE_TYPE_REFERENCE); | ||||||
| 			if (!constructorNode) { | 			if (!propName) { | ||||||
| 				return; | 				return; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			const propertyName: string = this.findTranslateServicePropertyName(constructorNode); | 			const callNodes = findMethodCallExpression(classNode, propName, TRANSLATE_SERVICE_METHOD_NAMES); | ||||||
| 			if (!propertyName) { |  | ||||||
| 				return; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			const callNodes = this.findCallNodes(classNode, propertyName); |  | ||||||
| 			callNodes.forEach(callNode => { | 			callNodes.forEach(callNode => { | ||||||
| 				const keys: string[] = this.getStringLiterals(callNode); | 				const [firstArgNode] = callNode.arguments; | ||||||
| 				if (keys && keys.length) { | 				if (!firstArgNode) { | ||||||
| 					collection = collection.addKeys(keys); | 					return; | ||||||
| 				} | 				} | ||||||
|  | 				const strings = getStringsFromExpression(firstArgNode); | ||||||
|  | 				collection = collection.addKeys(strings); | ||||||
| 			}); | 			}); | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
| 		return collection; | 		return collection; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	/** |  | ||||||
| 	 * Detect what the TranslateService instance property |  | ||||||
| 	 * is called by inspecting constructor arguments |  | ||||||
| 	 */ |  | ||||||
| 	protected findTranslateServicePropertyName(constructorNode: ConstructorDeclaration): string { |  | ||||||
| 		if (!constructorNode) { |  | ||||||
| 			return null; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		const result = constructorNode.parameters.find(parameter => { |  | ||||||
| 			// Skip if visibility modifier is not present (we want it set as an instance property) |  | ||||||
| 			if (!parameter.modifiers) { |  | ||||||
| 				return false; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			// Parameter has no type |  | ||||||
| 			if (!parameter.type) { |  | ||||||
| 				return false; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			// Make sure className is of the correct type |  | ||||||
| 			const parameterType: Identifier = (parameter.type as TypeReferenceNode).typeName as Identifier; |  | ||||||
| 			if (!parameterType) { |  | ||||||
| 				return false; |  | ||||||
| 			} |  | ||||||
| 			const className: string = parameterType.text; |  | ||||||
| 			if (className !== 'TranslateService') { |  | ||||||
| 				return false; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			return true; |  | ||||||
| 		}); |  | ||||||
|  |  | ||||||
| 		if (result) { |  | ||||||
| 			return (result.name as Identifier).text; |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	/** |  | ||||||
| 	 * Find class nodes |  | ||||||
| 	 */ |  | ||||||
| 	protected findClassNodes(node: Node): ClassDeclaration[] { |  | ||||||
| 		return this.findNodes(node, [SyntaxKind.ClassDeclaration]) as ClassDeclaration[]; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	/** |  | ||||||
| 	 * Find constructor |  | ||||||
| 	 */ |  | ||||||
| 	protected findConstructorNode(node: ClassDeclaration): ConstructorDeclaration { |  | ||||||
| 		const constructorNodes = this.findNodes(node, [SyntaxKind.Constructor]) as ConstructorDeclaration[]; |  | ||||||
| 		if (constructorNodes) { |  | ||||||
| 			return constructorNodes[0]; |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	/** |  | ||||||
| 	 * Find all calls to TranslateService methods |  | ||||||
| 	 */ |  | ||||||
| 	protected findCallNodes(node: Node, propertyIdentifier: string): CallExpression[] { |  | ||||||
| 		let callNodes = this.findNodes(node, [SyntaxKind.CallExpression]) as CallExpression[]; |  | ||||||
| 		callNodes = callNodes |  | ||||||
| 			.filter(callNode => { |  | ||||||
| 				// Only call expressions with arguments |  | ||||||
| 				if (callNode.arguments.length < 1) { |  | ||||||
| 					return false; |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				const propAccess = callNode.getChildAt(0).getChildAt(0) as PropertyAccessExpression; |  | ||||||
| 				if (!propAccess || !isPropertyAccessExpression(propAccess)) { |  | ||||||
| 					return false; |  | ||||||
| 				} |  | ||||||
| 				if (!propAccess.getFirstToken() || propAccess.getFirstToken().kind !== SyntaxKind.ThisKeyword) { |  | ||||||
| 					return false; |  | ||||||
| 				} |  | ||||||
| 				if (propAccess.name.text !== propertyIdentifier) { |  | ||||||
| 					return false; |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				const methodAccess = callNode.getChildAt(0) as PropertyAccessExpression; |  | ||||||
| 				if (!methodAccess || methodAccess.kind !== SyntaxKind.PropertyAccessExpression) { |  | ||||||
| 					return false; |  | ||||||
| 				} |  | ||||||
| 				if (!methodAccess.name || (methodAccess.name.text !== 'get' && methodAccess.name.text !== 'instant' && methodAccess.name.text !== 'stream')) { |  | ||||||
| 					return false; |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				return true; |  | ||||||
| 			}); |  | ||||||
|  |  | ||||||
| 		return callNodes; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										135
									
								
								src/utils/ast-helpers.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								src/utils/ast-helpers.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,135 @@ | |||||||
|  | import { tsquery } from '@phenomnomnominal/tsquery'; | ||||||
|  | import { | ||||||
|  | 	Node, | ||||||
|  | 	NamedImports, | ||||||
|  | 	Identifier, | ||||||
|  | 	ClassDeclaration, | ||||||
|  | 	CallExpression, | ||||||
|  | 	isStringLiteralLike, | ||||||
|  | 	isArrayLiteralExpression, | ||||||
|  | 	Expression, | ||||||
|  | 	isBinaryExpression, | ||||||
|  | 	SyntaxKind, | ||||||
|  | 	isConditionalExpression, | ||||||
|  | 	PropertyAccessExpression | ||||||
|  | } from 'typescript'; | ||||||
|  |  | ||||||
|  | export function getNamedImports(node: Node, moduleName: string): NamedImports[] { | ||||||
|  | 	const query = `ImportDeclaration[moduleSpecifier.text="${moduleName}"] NamedImports`; | ||||||
|  | 	return tsquery<NamedImports>(node, query); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function getNamedImportAlias(node: Node, moduleName: string, importName: string): string | null { | ||||||
|  | 	const [namedImportNode] = getNamedImports(node, moduleName); | ||||||
|  | 	if (!namedImportNode) { | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	const query = `ImportSpecifier:has(Identifier[name="${importName}"]) > Identifier`; | ||||||
|  | 	const identifiers = tsquery<Identifier>(namedImportNode, query); | ||||||
|  | 	if (identifiers.length === 1) { | ||||||
|  | 		return identifiers[0].text; | ||||||
|  | 	} | ||||||
|  | 	if (identifiers.length > 1) { | ||||||
|  | 		return identifiers[identifiers.length - 1].text; | ||||||
|  | 	} | ||||||
|  | 	return null; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function findClasses(node: Node): ClassDeclaration[] { | ||||||
|  | 	const query = 'ClassDeclaration'; | ||||||
|  | 	return tsquery<ClassDeclaration>(node, query); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function findClassPropertyByType(node: ClassDeclaration, type: string): string | null { | ||||||
|  | 	return findClassPropertyConstructorParameterByType(node, type) || findClassPropertyDeclarationByType(node, type); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function findClassPropertyConstructorParameterByType(node: ClassDeclaration, type: string): string | null { | ||||||
|  | 	const query = `Constructor Parameter:has(TypeReference > Identifier[name="${type}"]):has(PublicKeyword,ProtectedKeyword,PrivateKeyword) > Identifier`; | ||||||
|  | 	const [result] = tsquery<Identifier>(node, query); | ||||||
|  | 	if (result) { | ||||||
|  | 		return result.text; | ||||||
|  | 	} | ||||||
|  | 	return null; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function findClassPropertyDeclarationByType(node: ClassDeclaration, type: string): string | null { | ||||||
|  | 	const query = `PropertyDeclaration:has(TypeReference > Identifier[name="${type}"]) > Identifier`; | ||||||
|  | 	const [result] = tsquery<Identifier>(node, query); | ||||||
|  | 	if (result) { | ||||||
|  | 		return result.text; | ||||||
|  | 	} | ||||||
|  | 	return null; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function findFunctionCallExpressions(node: Node, fnName: string | string[]): CallExpression[] { | ||||||
|  | 	if (Array.isArray(fnName)) { | ||||||
|  | 		fnName = fnName.join('|'); | ||||||
|  | 	} | ||||||
|  | 	const query = `CallExpression:has(Identifier[name="${fnName}"]):not(:has(PropertyAccessExpression))`; | ||||||
|  | 	const nodes = tsquery<CallExpression>(node, query); | ||||||
|  | 	return nodes; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function findMethodCallExpression(node: Node, prop: string, fnName: string | string[]): CallExpression[] { | ||||||
|  | 	if (Array.isArray(fnName)) { | ||||||
|  | 		fnName = fnName.join('|'); | ||||||
|  | 	} | ||||||
|  | 	const query = `CallExpression > PropertyAccessExpression:has(Identifier[name=/^(${fnName})$/]):has(PropertyAccessExpression:has(Identifier[name="${prop}"]):has(ThisKeyword))`; | ||||||
|  | 	let nodes = tsquery<PropertyAccessExpression>(node, query).map(node => node.parent as CallExpression); | ||||||
|  | 	return nodes; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function getStringsFromExpression(expression: Expression): string[] { | ||||||
|  | 	if (isStringLiteralLike(expression)) { | ||||||
|  | 		return [expression.text]; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (isArrayLiteralExpression(expression)) { | ||||||
|  | 		return expression.elements.reduce((result: string[], element: Expression) => { | ||||||
|  | 			const strings = this.getStringsFromExpression(element); | ||||||
|  | 			return [ | ||||||
|  | 				...result, | ||||||
|  | 				...strings | ||||||
|  | 			]; | ||||||
|  | 		}, []); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (isBinaryExpression(expression)) { | ||||||
|  | 		const [left] = this.getStringsFromExpression(expression.left); | ||||||
|  | 		const [right] = this.getStringsFromExpression(expression.right); | ||||||
|  |  | ||||||
|  | 		if (expression.operatorToken.kind === SyntaxKind.PlusToken) { | ||||||
|  | 			if (typeof left === 'string' && typeof right === 'string') { | ||||||
|  | 				return [left + right]; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (expression.operatorToken.kind === SyntaxKind.BarBarToken) { | ||||||
|  | 			const result = []; | ||||||
|  | 			if (typeof left === 'string') { | ||||||
|  | 				result.push(left); | ||||||
|  | 			} | ||||||
|  | 			if (typeof right === 'string') { | ||||||
|  | 				result.push(right); | ||||||
|  | 			} | ||||||
|  | 			return result; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (isConditionalExpression(expression)) { | ||||||
|  | 		const [whenTrue] = this.getStringsFromExpression(expression.whenTrue); | ||||||
|  | 		const [whenFalse] = this.getStringsFromExpression(expression.whenFalse); | ||||||
|  |  | ||||||
|  | 		const result = []; | ||||||
|  | 		if (typeof whenTrue === 'string') { | ||||||
|  | 			result.push(whenTrue); | ||||||
|  | 		} | ||||||
|  | 		if (typeof whenFalse === 'string') { | ||||||
|  | 			result.push(whenFalse); | ||||||
|  | 		} | ||||||
|  | 		return result; | ||||||
|  | 	} | ||||||
|  | 	return []; | ||||||
|  | } | ||||||
| @@ -1,15 +1,15 @@ | |||||||
| import { expect } from 'chai'; | import { expect } from 'chai'; | ||||||
| 
 | 
 | ||||||
| import { FunctionParser } from '../../src/parsers/function.parser'; | import { MarkerParser } from '../../src/parsers/marker.parser'; | ||||||
| 
 | 
 | ||||||
| describe('FunctionParser', () => { | describe('MarkerParser', () => { | ||||||
| 
 | 
 | ||||||
| 	const componentFilename: string = 'test.component.ts'; | 	const componentFilename: string = 'test.component.ts'; | ||||||
| 
 | 
 | ||||||
| 	let parser: FunctionParser; | 	let parser: MarkerParser; | ||||||
| 
 | 
 | ||||||
| 	beforeEach(() => { | 	beforeEach(() => { | ||||||
| 		parser = new FunctionParser(); | 		parser = new MarkerParser(); | ||||||
| 	}); | 	}); | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @@ -224,6 +224,7 @@ describe('ServiceParser', () => { | |||||||
| 						console.log(translations[variable]); | 						console.log(translations[variable]); | ||||||
| 					}); | 					}); | ||||||
| 				} | 				} | ||||||
|  | 			} | ||||||
| 		`; | 		`; | ||||||
| 		const keys = parser.extract(contents, componentFilename).keys(); | 		const keys = parser.extract(contents, componentFilename).keys(); | ||||||
| 		expect(keys).to.deep.equal(['yes']); | 		expect(keys).to.deep.equal(['yes']); | ||||||
| @@ -263,4 +264,39 @@ describe('ServiceParser', () => { | |||||||
| 		expect(keys).to.deep.equal(['Extract me!', 'Hello!']); | 		expect(keys).to.deep.equal(['Extract me!', 'Hello!']); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
|  | 	it('should extract strings when TranslateService is declared as a property', () => { | ||||||
|  | 		const contents = ` | ||||||
|  | 			export class MyComponent { | ||||||
|  | 				protected translateService: TranslateService; | ||||||
|  | 				public constructor() { | ||||||
|  | 					this.translateService = new TranslateService(); | ||||||
|  | 				} | ||||||
|  | 				public test() { | ||||||
|  | 					this.translateService.instant('Hello World'); | ||||||
|  | 				} | ||||||
|  | 		`; | ||||||
|  | 		const keys = parser.extract(contents, componentFilename).keys(); | ||||||
|  | 		expect(keys).to.deep.equal(['Hello World']); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	it('should extract strings passed to TranslateServices methods only', () => { | ||||||
|  | 		const contents = ` | ||||||
|  | 			export class AppComponent implements OnInit { | ||||||
|  | 				constructor(protected config: Config, protected translateService: TranslateService) {} | ||||||
|  |  | ||||||
|  | 				public ngOnInit(): void { | ||||||
|  | 					this.localizeBackButton(); | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				protected localizeBackButton(): void { | ||||||
|  | 					this.translateService.onLangChange.subscribe((event: LangChangeEvent) => { | ||||||
|  | 						this.config.set('backButtonText', this.translateService.instant('Back')); | ||||||
|  | 					}); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		`; | ||||||
|  | 		const keys = parser.extract(contents, componentFilename).keys(); | ||||||
|  | 		expect(keys).to.deep.equal(['Back']); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
| }); | }); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user