@zohodesk/codestandard-validator 0.0.2-exp-5 → 0.0.2-exp-7

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.
@@ -23,6 +23,13 @@ const {
23
23
  const {
24
24
  getBranchName
25
25
  } = require('../../utils/GitActions/gitActions');
26
+ const {
27
+ getConfigurationPrecommit,
28
+ getSupportedLanguage
29
+ } = require('../../utils/General/getGeneralInfo');
30
+ const {
31
+ impactBasedPrecommit
32
+ } = getConfigurationPrecommit();
26
33
 
27
34
  /**
28
35
  * @function isMergeCommit - This method check whether it is merge or not
@@ -141,6 +148,7 @@ async function preCommitHook() {
141
148
  let exemptionFiles = [];
142
149
  let current_branch = '';
143
150
  let hasEslintErrorsInChangedLines = false;
151
+ let hasEslintErrorsInFiles = false;
144
152
  let areFilesStaged = false;
145
153
  try {
146
154
  current_branch = await getBranchName();
@@ -162,7 +170,7 @@ async function preCommitHook() {
162
170
  let currentFileName = staged_files[file];
163
171
  let changedLinesArray = [];
164
172
  let eslintErrorsInChangedLines = [];
165
- if (path.extname(staged_files[file]) === '.js') {
173
+ if (getSupportedLanguage().includes(path.extname(staged_files[file]))) {
166
174
  try {
167
175
  let eslintErrorsInFile = await findEslintErrors(staged_files[file]);
168
176
  if (staged_files[file] && typeof staged_files[file] == 'string') {
@@ -174,34 +182,44 @@ async function preCommitHook() {
174
182
  let changedLinesEndArray = [];
175
183
 
176
184
  //Calculating changed lines in a file and storing them in respective arrays
177
- for (let number of changedLinesArray) {
178
- let changesStartLine = parseInt(number.split(' ')[2].split(',')[0]);
179
- changedLinesStartArray.push(changesStartLine);
180
- let changesEndLine = number.split(' ')[2].split(',')[1];
181
- if (changesEndLine === undefined) {
182
- changedLinesEndArray.push(changesStartLine);
183
- } else {
184
- changedLinesEndArray.push(changesStartLine + parseInt(changesEndLine) - 1);
185
+ if (impactBasedPrecommit) {
186
+ for (let number of changedLinesArray) {
187
+ let changesStartLine = parseInt(number.split(' ')[2].split(',')[0]);
188
+ changedLinesStartArray.push(changesStartLine);
189
+ let changesEndLine = number.split(' ')[2].split(',')[1];
190
+ if (changesEndLine === undefined) {
191
+ changedLinesEndArray.push(changesStartLine);
192
+ } else {
193
+ changedLinesEndArray.push(changesStartLine + parseInt(changesEndLine) - 1);
194
+ }
185
195
  }
186
- }
187
- for (let error = 1; error < eslintErrorsInFile.length - 2; error++) {
188
- //eslintErrorsInFile[error].trim() - 69:26 error => Do not hardcode content. Use I18N key instead no-hardcoding/no-hardcoding,
189
- //eslintErrorsInFile[error].trim().split(' ')[0] => 69:26
190
- //eslintErrorsInFile[error].trim().split(' ')[0].split(':')[0] => 69
196
+ for (let error = 1; error < eslintErrorsInFile.length - 2; error++) {
197
+ //eslintErrorsInFile[error].trim() - 69:26 error => Do not hardcode content. Use I18N key instead no-hardcoding/no-hardcoding,
198
+ //eslintErrorsInFile[error].trim().split(' ')[0] => 69:26
199
+ //eslintErrorsInFile[error].trim().split(' ')[0].split(':')[0] => 69
191
200
 
192
- let eslintErrorLineNumber = eslintErrorsInFile[error].trim().split(' ')[0].split(':')[0];
193
- for (let lineNumber in changedLinesStartArray) {
194
- if (eslintErrorLineNumber >= changedLinesStartArray[lineNumber] && eslintErrorLineNumber <= changedLinesEndArray[lineNumber]) {
195
- eslintErrorsInChangedLines.push(eslintErrorsInFile[error]);
201
+ let eslintErrorLineNumber = eslintErrorsInFile[error].trim().split(' ')[0].split(':')[0];
202
+ for (let lineNumber in changedLinesStartArray) {
203
+ if (eslintErrorLineNumber >= changedLinesStartArray[lineNumber] && eslintErrorLineNumber <= changedLinesEndArray[lineNumber]) {
204
+ eslintErrorsInChangedLines.push(eslintErrorsInFile[error]);
205
+ }
196
206
  }
197
207
  }
198
- }
199
- if (eslintErrorsInChangedLines.length > 0) {
200
- Logger.log(Logger.FAILURE_TYPE, `\x1b[1m${currentFileName}\x1b[0m`);
201
- for (let eslintError of eslintErrorsInChangedLines) {
202
- Logger.log(Logger.FAILURE_TYPE, `\x1b[37m${eslintError.trimEnd()}\x1b[0m`);
208
+ if (eslintErrorsInChangedLines.length > 0) {
209
+ Logger.log(Logger.FAILURE_TYPE, `\x1b[1m${currentFileName}\x1b[0m`);
210
+ for (let eslintError of eslintErrorsInChangedLines) {
211
+ Logger.log(Logger.FAILURE_TYPE, `\x1b[37m${eslintError.trimEnd()}\x1b[0m`);
212
+ }
213
+ hasEslintErrorsInChangedLines = true;
214
+ }
215
+ } else {
216
+ if (eslintErrorsInFile.length > 0) {
217
+ Logger.log(Logger.FAILURE_TYPE, `\x1b[1m${currentFileName}\x1b[0m`);
218
+ for (let eslintError of eslintErrorsInChangedLines) {
219
+ Logger.log(Logger.FAILURE_TYPE, `\x1b[37m${eslintError.trimEnd()}\x1b[0m`);
220
+ }
221
+ hasEslintErrorsInFiles = true;
203
222
  }
204
- hasEslintErrorsInChangedLines = true;
205
223
  }
206
224
  }
207
225
  }
@@ -220,7 +238,10 @@ async function preCommitHook() {
220
238
  if (hasEslintErrorsInChangedLines) {
221
239
  Logger.log(Logger.FAILURE_TYPE, `There are eslint errors present. So commit is aborted`);
222
240
  process.exit(1);
223
- } else if (!hasEslintErrorsInChangedLines && areFilesStaged) {
241
+ } else if (hasEslintErrorsInFiles) {
242
+ Logger.log(Logger.FAILURE_TYPE, `There are eslint errors present. So commit is aborted`);
243
+ process.exit(1);
244
+ } else if (!hasEslintErrorsInFiles && !hasEslintErrorsInChangedLines && areFilesStaged) {
224
245
  Logger.log(Logger.SUCCESS_TYPE, `Commit Successful`);
225
246
  process.exit(0);
226
247
  }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+
3
+ const path = require("path");
4
+
5
+ /**
6
+ * Configuration object for linting and reporting.
7
+ * @property {string} ruleConfigurationPath - The path to the ESLint configuration file.
8
+ * @property {boolean} impactBased - Indicates if the linting is impact-based.
9
+ * @property {string} lintReportPath - The path to the lint report JSON file.
10
+ * @property {string} metricServerHost - The URL of the SonarQube server.
11
+ * @property {string} exemptionInstanceHost - This is Exemption running host URL
12
+ * @property {string} metric_token - The token for authentication with the SonarQube server.
13
+ * @property {string} gitEndPoint - API EndPoint for Git Actions
14
+ * @property {string} tsConfigurationPath - The path of the ts configuration Path
15
+ * @property {number} projectId - project id of repository
16
+ * @property {boolean} impactBasedPrecommit - Indicates if the linting is impact-based in pre commit
17
+ * @property {string} token - Encrypted Authentication Token
18
+ * @property {string} compareBranch - Branch to compare diff
19
+ */
20
+
21
+ module.exports = {
22
+ ruleConfigurationPath: path.resolve(process.cwd(), ".eslintrc.js"),
23
+ impactBased: true,
24
+ lintReportPath: path.resolve(process.cwd(), "lint-report", "lintReport.json"),
25
+ metricServerHost: "https://client-linters.zohodesk.csez.zohocorpin.com",
26
+ exemptionInstanceHost: "",
27
+ metric_token: "zxh_9737850jh2l53ml17223929ihii73072j54j2260",
28
+ branchDiffPath: path.resolve(process.cwd(), "diffBranch.json"),
29
+ gitEndPoint: "https://zgit.csez.zohocorpin.com",
30
+ tsConfigurationPath: path.resolve(process.cwd(), 'tsconfig.json'),
31
+ projectId: `project-id`,
32
+ impactBasedPrecommit: true,
33
+ token: "w-OkG3f5OOM1Rkly8phZ",
34
+ compareBranch: 'release'
35
+ };
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
 
3
3
  const os = require('os');
4
+ const path = require('path');
5
+ const fs = require("fs");
4
6
 
5
7
  /**
6
8
  * @function getTimeStampInfo - to fetch various timestamp details
@@ -25,7 +27,40 @@ function getTimeStampInfo() {
25
27
  function getEnv() {
26
28
  return os.type();
27
29
  }
30
+
31
+ /**
32
+ * @function getConfigPath - get a path of lint configuration path
33
+ * @returns {string}
34
+ */
35
+ function getConfigPath() {
36
+ const configPath = path.resolve(process.cwd(), 'lint.config.js');
37
+ const defaultConfigPath = path.resolve(__dirname, '..', '..', 'setup', 'sample.config.js');
38
+ return fs.exitSync(configPath) ? configPath : defaultConfigPath;
39
+ }
40
+
41
+ /**
42
+ * @function getConfiguration - get configuration object of lint configuration
43
+ * @returns
44
+ */
45
+ function getConfigurationPrecommit() {
46
+ return require(getConfigPath());
47
+ }
48
+
49
+ /**
50
+ * @function getSupportedLanguage - get support language
51
+ * @returns {Array<string>}
52
+ */
53
+ function getSupportedLanguage() {
54
+ const _language = [];
55
+ _language.push('js');
56
+ _language.push('jsx');
57
+ _language.push('ts');
58
+ _language.push('tsx');
59
+ return _language;
60
+ }
28
61
  module.exports = {
62
+ getSupportedLanguage,
29
63
  getTimeStampInfo,
30
- getEnv
64
+ getEnv,
65
+ getConfigurationPrecommit
31
66
  };
@@ -23,7 +23,7 @@ function initializeHusky() {
23
23
  let isNavigationToRootDirectorySuccessful = navigateToRootDirectory(getRootDirectory());
24
24
  if (isNavigationToRootDirectorySuccessful) {
25
25
  try {
26
- execSync('npx husky@7 install');
26
+ execSync('npx husky install');
27
27
  return true;
28
28
  } catch (error) {
29
29
  Logger.log(Logger.FAILURE_TYPE, error.toString());
@@ -26,7 +26,7 @@ let isCustomPrecommitConfigurationSuccessful = false;
26
26
  * @returns {boolean} - indicating whether entire custom precommit hook setup using husky process is success or failure
27
27
  */
28
28
  function setupHusky() {
29
- let isHuskyPackageInstalled = true; //installHuskyPackage()
29
+ let isHuskyPackageInstalled = installHuskyPackage();
30
30
  if (isHuskyPackageInstalled) {
31
31
  let isHuskyInitializedSuccessfully = initializeHusky();
32
32
  if (isHuskyInitializedSuccessfully) {
package/changeLog.md CHANGED
@@ -1,6 +1,12 @@
1
1
  # Code standard library
2
2
 
3
3
  # 0.0.0
4
+ 1. Testing version of the library
5
+
6
+ # 0.0.1
7
+ - This version was mistakenly published without the build folder which will be corrected in the next release
8
+
9
+ # 0.0.2 - first stable release of the library
4
10
  1. Sets up a custom precommit hook using husky
5
11
  2. clones the common linter configuration repository in developer branch
6
12
  3. Based on the cloned configuration folder creates a eslint config file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zohodesk/codestandard-validator",
3
- "version": "0.0.2-exp-5",
3
+ "version": "0.0.2-exp-7",
4
4
  "description": "library to enforce code standard using eslint",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -1,18 +0,0 @@
1
- const { rulesConfig, plugins, extendPlugins } = require("@zohodesk/codestandard-validator"); //no i18n
2
- const [off, , error] = ["off", "warn", "error"];
3
- module.exports = {
4
- env: {
5
- browser: true,
6
- es2021: true,
7
- },
8
- extends: extendPlugins,
9
- parserOptions: {
10
- ecmaFeatures: {
11
- jsx: true,
12
- },
13
- ecmaVersion: 12,
14
- sourceType: "module",
15
- },
16
- plugins: plugins,
17
- overrides: rulesConfig
18
- };
@@ -1,9 +0,0 @@
1
- const { zsecurityRules } = require('./rule_configuration/configurations')
2
- const $plugins = require('./pluginVersion')
3
-
4
-
5
- module.exports = {
6
- rulesConfig : Object.assign([],[...zsecurityRules]),
7
- plugins:$plugins.map((plugin) => plugin?.plugin).filter(item => item !== undefined) ,
8
- extendPlugins:$plugins.map((plugin) => plugin?.extend).filter(item => item !== undefined)
9
- }
@@ -1,7 +0,0 @@
1
- module.exports = [
2
- {
3
- packageName: "@zohodesk/eslint-plugin-zsecurity",
4
- version: "0.0.1-beta.4",
5
- plugin: "@zohodesk/zsecurity"
6
- }
7
- ];
@@ -1,9 +0,0 @@
1
- module.exports.zsecurityRules = [
2
- {
3
- files: ["src/**/*.js"],
4
- rules: {
5
- "@zohodesk/zsecurity/no-unsecure-html": 'error',
6
- "@zohodesk/zsecurity/no-protocol-check": 'error',
7
- },
8
- },
9
- ];
@@ -1,18 +0,0 @@
1
- const { rulesConfig, plugins, extendPlugins } = require("@zohodesk/codestandard-validator"); //no i18n
2
- const [off, , error] = ["off", "warn", "error"];
3
- module.exports = {
4
- env: {
5
- browser: true,
6
- es2021: true,
7
- },
8
- extends: extendPlugins,
9
- parserOptions: {
10
- ecmaFeatures: {
11
- jsx: true,
12
- },
13
- ecmaVersion: 12,
14
- sourceType: "module",
15
- },
16
- plugins: plugins,
17
- overrides: rulesConfig
18
- };
@@ -1,16 +0,0 @@
1
- const { plugins , rulesConfig} = require('../../common/index')
2
- const { architectureRules } = require("./rule_configuration/architectureRules/bestPracticeRules")
3
- const { i18Configuration } = require("./rule_configuration/i18nRules/noHardcodingRuleExcludes")
4
- const $plugins = [...require('./pluginVersion'), ...plugins ]
5
-
6
-
7
- module.exports = {
8
- rulesConfig : Object.assign([],[...architectureRules,...i18Configuration,...rulesConfig]) ,
9
- plugins:$plugins.map((plugin) => plugin?.plugin).filter(item => item !== undefined) ,
10
- extendPlugins:$plugins.map((plugin) => plugin?.extend).filter(item => item !== undefined)
11
- }
12
-
13
-
14
-
15
-
16
-
@@ -1,17 +0,0 @@
1
- module.exports = [
2
- {
3
- packageName: "@zohodesk/eslint-plugin-architecturerules",
4
- version: "0.0.2-exp-4",
5
- plugin: "@zohodesk/architecturerules",
6
- },
7
- {
8
- packageName: "@zohodesk/eslint-plugin-no-hardcoding",
9
- version: "1.0.2",
10
- plugin: "@zohodesk/no-hardcoding",
11
- },
12
- {
13
- packageName: "@zohodesk/eslint-config-acceptancetest",
14
- version: "0.0.3",
15
- extend: "@zohodesk/eslint-config-acceptancetest",
16
- }
17
- ];
@@ -1,9 +0,0 @@
1
- module.exports.architectureRules = [
2
- {
3
- files: ['src/**/*.js'],
4
- rules: {
5
- '@zohodesk/architecturerules/no-unused-vars' : 'warn',
6
- '@zohodesk/architecturerules/no-comments-rule':'warn'
7
- }
8
- }
9
- ]
@@ -1,32 +0,0 @@
1
- let noHardcodingRuleExcludes = {
2
- objectKeys:["dataId","data-id","data-test-id","size","iconSize","iconName","align","alignBox","palette","scroll","boxSize","boxPosition","type","moduleName","apiname","apiName","id","target","tagName","module","folderName","componentGroup","hoverType","tabIndex","aria-Label","aria-label","ariaLabel","d","className","rel","heading","column","row","transform","color","condition","fieldName","animationStyle","textBoxVariant","searchBoxSize","src","display","href","width","height","enctype","target","left","top","position","location","backgroundColor","visibility","cursor","lineHeight","border","readOnly","textAlign","scrolling","class","padding","overflow","textBoxSize","right","top","bottom","left","fieldType","regex","searchModuleFieldName","searchConditionValue","logoName","_id"],
3
- props:["dataId","data-id","data-test-id","size","iconSize","iconName","align","alignBox","palette","scroll","boxSize","boxPosition","type","moduleName","apiname","apiName","id","target","tagName","module","folderName","componentGroup","hoverType","tabIndex","aria-Label","aria-label","ariaLabel","d","className","rel","heading","column","row","transform","color","condition","fieldName","animationStyle","textBoxVariant","searchBoxSize","src","display","href","width","height","enctype","target","left","top","position","location","backgroundColor","visibility","cursor","lineHeight","border","readOnly","textAlign","scrolling","class","padding","overflow","textBoxSize","right","top","bottom","left","fieldType","logoName","_id"],
4
- methods:["findIdex","findIndex","includes","getFieldIndex","oneOf","oneOfType","bind","getFieldLabel","addEventListener","removeEventListener","replace","split","indexOf","set","selectn","getAdditionalProperty","reject","sendResponse","resolve","mapStateToProps","setAttribute","updateError","concat","createElement","getproperty","addElement","getElementsByName","match","lastIndexOf","setRequestHeader","getParameter","getAttribute","RegExp","substring","equals","log","debug","addClass","open","find","setTimeout","attachEvent","addEventListener","removeAttribute","parseInt","replace","setInterval","getObj","detachEvent","removeEventListener","css","animate","bind","ajaxNew","set_cookie","getElementById","ajax","find","createTextNode","parents","removeClass","toggleClass","write","get_cookie","parseFromString","Error","error","querySelector","getDeptBasedData","timeEnd","time","deskCustomError"],
5
- allowedTags:["svg"],
6
- keys:["Product Category","Product Owner","Product Information","Summarize information","and then","and finally","Support Manager","Newbie Agent","Light Agent","All Accounts","All Contacts","All Cases","Open Cases","Closed Cases","My Cases","All Products","Published Solutions","Open Tasks","All Published Solutions","My Draft Solutions","My Review Solutions","Today+Overdue Tasks","Overdue Cases","My Overdue Cases","Spam Cases","Requests for review","Customer Responded Requests","Customer Responded Time","Unassigned Open Requests","Unassigned Open Cases","SLAViolated Requests","My Open Requests","My Accounts","My Contacts","My Products","All Solutions","My Solutions","All Tasks","My Open Tasks","Todays Tasks","Unpublished Solutions","Overdue Tasks","Tomorrows Tasks","All Contracts","Expired Contracts","My Contracts","My Tasks","Completed Tasks","Missed Chats","All Missed Calls","My Missed Calls","Accounts Unmapped with CRM","Accounts Mapped with CRM","Contacts Unmapped with CRM","Contacts Mapped with CRM","Agent Notification","Contact Notification","Contact Created Time","Public Email Templates","Request has been assigned to you","Request for review","Request moved to the Department","Team Notification","Secondary Contacts Notification","Created By","Created Time","Modified Time","Last Activity Time","Unsubscribed Mode","Unsubscribed Time","Reports To","Date of Birth","New Request Created","Your request has been closed","Requestor has replied to the request","Comment Added for the Request","Comment is deleted from a ticket","Comment is edited in a ticket","Resolution Updated for the Request","Notify support reps on new request","Notify Agent when a comment is deleted for their ticket","Notify Agent when a comment is edited from their ticket","Acknowledge contact on receiving a new ticket","Notify agent on assigning a task","Notify agents when a ticket is moved to their department","Notify all agents when a new ticket is created","Notify contact when a comment is added","Notify contact when a comment is deleted","Notify contact when a comment is edited","Notify contact when a resolution is added","Task has been assigned to you","Remind Task Owner on Set Time","Popular Articles","Remind At","User Information","To Address","Account Name","Contact Information",
7
- "All Requests","Product Code","Product Name","Unit Price","Due Date","Case Number","Not Started","In Progress","Waiting on someone else","On Hold","Contact Name","Last Week","Current Week","Last Month","Current Month","Previous FY","Current FY","Next FY","Previous FQ","Current FQ","Next FQ","Next Week","Next Month","Last 60 Days","Last 90 Days","Last 120 Days","Next 7 Days","Next 30 Days","Next 60 Days","Next 90 Days","Next 120 Days","Full Name","Due on","All User","View Requests","Add Request","View Thread","Paid User","Open in New Window","Recent articles","Contract Name","Contract End","Contract Start","Request Id","Contract Number","Sub Category","support.label.Additional Information","Closed Activities","Recent Views","Modified By","Other Phone","Home Phone","Account Number","Request Subject","Request Charge","Other Address","Mailing Address","Billing Address","Shipping Address","Response Due Date","Unassigned Cases","Case On Hold Time","Summarize information by","and then by","and finally by","Time to Respond","Closing Date","crm.label.Product Category","crm.label.Product Owner","crm.label.Product Information","crm.label.Contact Name","Sub Category","mousemove click","Manufacturer","Hour","Deactivated","Review","Notes","CREATEDTIME","Requests","Social","Reports","Report","Solution","Articles","Article","Customized","Title","Fax","Lookup","Chat","Community","Left","Cases","Solutions","Contract","Accounts","Account","Contacts","Contact","Products","Product","Home","Tasks","Calls","Events","IM","Phone","Mobile","Email","Type","Website","Name","Country","Comments","Priority","Description","Department","click","email","is","are","None","this","these","Total","Category","Pending","Status","Subject","Answer","Overdue","between","Top","Customer","Completed","Deferred","Highest","Lowest","High","Low","Normal","Inactive","Open","New","Web","Draft","Duplicate","Closed","Archived","Today","January","February","March","April","May","June","July","August","September","October","November","December","Jan","Feb","Mar","Apr","Jun","Jul","Aug","Sep","Oct","Nov","Dec","days","day","on","User","Records","Private","AllUsers","Month","Avg","General","and","or","currentDateTime","confirmmsg","maxlenmsg","AM","PM","for","Update","to","Direction","hour","hours","minute",
8
- "minutes","second","seconds","ago","Move","Unassigned","Expired","Prospect","potential","Lead","Request","Mode","Forums","More","Call","Task","Event","sendingportalinvitation","loading","Custom","loggedinuser_view","everyone_view","onlyagents_view","Agent","Ticket","Attachment","SMS","Subscriptions","ActiveUsersList","DeactivatedUsersList","NotConfirmedUsersList","Content","Created"],
9
- commentsToIgnoreHardcodingCheck:["no i18n"],
10
- stringsAllowedAsBlockCommentStart: ["ignore i18n start","no i18n start"],
11
- stringsAllowedAsBlockCommentEnd: ["ignore i18n end","no i18n end"],
12
- dateFormatPatterns:["DD MMM YYYY hh:mm A","DD\/MM\/YYYY h:mm A","DDD, DD MMM YYYY HH:mm:ss","DD MMM YYYY","hh:mm A"]
13
- }
14
- const [error,,off] = ['error','','off']
15
-
16
- let usegetI18NValueAppropriatelyExcludes = {
17
- i18nKeys:noHardcodingRuleExcludes.keys,
18
- commentsToIgnoreCheck:noHardcodingRuleExcludes.commentsToIgnoreHardcodingCheck
19
- }
20
-
21
- module.exports.i18Configuration = [
22
- {
23
- files: ['src/**/*.js'],
24
- rules: {
25
- '@zohodesk/no-hardcoding/no-hardcoding' : [error,{...noHardcodingRuleExcludes}],
26
- '@zohodesk/no-hardcoding/use-getI18NValue-method-appropriately': [error,{...usegetI18NValueAppropriatelyExcludes}]
27
- }
28
- }
29
- ]
30
-
31
-
32
-