Repository: platers/obsidian-linter
Pull request #329 (2022-08-01 02:26:19)

Use Obsidian Moment Instead of Local Locale Aware Moment
Using a local locale aware version of moment leads to a large increase in plugin size which leads to slower load times. If possible, it is probably better to go ahead and use the exported moment from the obsidian app as it is already locale aware. It will give improved startup times while also allowing us to use locale specific moment instances to maintain the locale specific logic in the service.

This cuts mobile loading time on my phone down from somewhere around 300 or 400 ms to about 220ms. The compiled file size is also 450ish KB instead of 750ish KB.

@mnaoumov , I wanted to see if you had any input on this change as you did a lot around getting the localization setup for moment.

Changes Made:
- Moved test folder from `src/test` to `tests` as this seems to be a more standard way of doing things and jest seems to play better with it this way
- Moved `__mocks__` into `tests` and made sure to add the export for moment which is the development instance of moment
- Updated documentation around tests directory
- Updated logic pertaining to local instance of moment to use the one provided by obsidian making sure to update the locale on it where needed


Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index d9f7e85..29ab65a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -61,7 +61,7 @@ Once the proof of concept, if requested, or refactor idea gets the green light,
 
 ### Adding Tests
 
-Tests are located in `src/test/`. File names for tests must end in `.test.ts` and should have dashes between words.  
+Tests are located in `__tests__/`. File names for tests must end in `.test.ts` and should have dashes between words.  
 Tests for rule other than the examples will be in the format `{RULE_ALIAS}.test.ts` which will help keep testing files manageable.
 
 A test will take the form of
diff --git a/__mocks__/obsidian.js b/__mocks__/obsidian.ts
similarity index 59%
rename from __mocks__/obsidian.js
rename to __mocks__/obsidian.ts
index 53c85a2..e6bb2ed 100644
--- a/__mocks__/obsidian.js
+++ b/__mocks__/obsidian.ts
@@ -1,1 +1,4 @@
 // This file is required to exclude obsidian package dependency from jest unit tests
+import moment from 'moment';
+
+export {moment as moment};
diff --git a/jest.config.ts b/jest.config.ts
index 1184bdc..7d26b39 100644
--- a/jest.config.ts
+++ b/jest.config.ts
@@ -8,5 +8,10 @@ const config: Config.InitialOptions = {
   transformIgnorePatterns: [
     '"/node_modules/(?!unified-lint-rule)',
   ],
+  testMatch: [
+    '**/__tests__/**/*.[jt]s?(x)',
+    '**/?(*.)+(spec|test).[jt]s?(x)',
+    '!**/__tests__/common.ts',
+  ],
 };
 export default config;
diff --git a/package-lock.json b/package-lock.json
index 7ae984c..49c376a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,7 +13,6 @@
         "diff-match-patch": "^1.0.5",
         "js-yaml": "^4.1.0",
         "loglevel": "^1.8.0",
-        "moment": "^2.29.4",
         "remark": "^14.0.2",
         "remark-gfm": "^3.0.1",
         "ts-dedent": "^2.2.0",
@@ -27,6 +26,7 @@
         "@babel/preset-typescript": "^7.18.6",
         "@rollup/plugin-commonjs": "^22.0.1",
         "@rollup/plugin-node-resolve": "^13.3.0",
+        "@rollup/plugin-replace": "^4.0.0",
         "@rollup/plugin-typescript": "^8.3.3",
         "@types/diff": "^5.0.2",
         "@types/diff-match-patch": "^1.0.32",
@@ -41,9 +41,9 @@
         "eslint-plugin-jest": "^26.6.0",
         "eslint-plugin-unicorn": "^43.0.2",
         "jest": "^28.1.3",
+        "moment": "^2.29.4",
         "obsidian": "^0.15.4",
         "rollup": "^2.77.0",
-        "rollup-plugin-external-globals": "^0.6.1",
         "rollup-plugin-glob-import": "^0.5.0",
         "rollup-plugin-terser": "^7.0.2",
         "ts-node": "^10.9.1",
@@ -2390,6 +2390,19 @@
         "rollup": "^2.42.0"
       }
     },
+    "node_modules/@rollup/plugin-replace": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-4.0.0.tgz",
+      "integrity": "sha512-+rumQFiaNac9y64OHtkHGmdjm7us9bo1PlbgQfdihQtuNxzjpaB064HbRnewUOggLQxVCCyINfStkgmBeQpv1g==",
+      "dev": true,
+      "dependencies": {
+        "@rollup/pluginutils": "^3.1.0",
+        "magic-string": "^0.25.7"
+      },
+      "peerDependencies": {
+        "rollup": "^1.20.0 || ^2.0.0"
+      }
+    },
     "node_modules/@rollup/plugin-typescript": {
       "version": "8.3.3",
       "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-8.3.3.tgz",
@@ -6387,6 +6400,7 @@
       "version": "2.29.4",
       "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
       "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
+      "dev": true,
       "engines": {
         "node": "*"
       }
@@ -7243,34 +7257,6 @@
         "fsevents": "~2.3.2"
       }
     },
-    "node_modules/rollup-plugin-external-globals": {
-      "version": "0.6.1",
-      "resolved": "https://registry.npmjs.org/rollup-plugin-external-globals/-/rollup-plugin-external-globals-0.6.1.tgz",
-      "integrity": "sha512-mlp3KNa5sE4Sp9UUR2rjBrxjG79OyZAh/QC18RHIjM+iYkbBwNXSo8DHRMZWtzJTrH8GxQ+SJvCTN3i14uMXIA==",
-      "dev": true,
-      "dependencies": {
-        "@rollup/pluginutils": "^4.0.0",
-        "estree-walker": "^2.0.1",
-        "is-reference": "^1.2.1",
-        "magic-string": "^0.25.7"
-      },
-      "peerDependencies": {
-        "rollup": "^2.25.0"
-      }
-    },
-    "node_modules/rollup-plugin-external-globals/node_modules/@rollup/pluginutils": {
-      "version": "4.2.1",
-      "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz",
-      "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==",
-      "dev": true,
-      "dependencies": {
-        "estree-walker": "^2.0.1",
-        "picomatch": "^2.2.2"
-      },
-      "engines": {
-        "node": ">= 8.0.0"
-      }
-    },
     "node_modules/rollup-plugin-glob-import": {
       "version": "0.5.0",
       "resolved": "https://registry.npmjs.org/rollup-plugin-glob-import/-/rollup-plugin-glob-import-0.5.0.tgz",
@@ -9961,6 +9947,16 @@
         "resolve": "^1.19.0"
       }
     },
+    "@rollup/plugin-replace": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-4.0.0.tgz",
+      "integrity": "sha512-+rumQFiaNac9y64OHtkHGmdjm7us9bo1PlbgQfdihQtuNxzjpaB064HbRnewUOggLQxVCCyINfStkgmBeQpv1g==",
+      "dev": true,
+      "requires": {
+        "@rollup/pluginutils": "^3.1.0",
+        "magic-string": "^0.25.7"
+      }
+    },
     "@rollup/plugin-typescript": {
       "version": "8.3.3",
       "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-8.3.3.tgz",
@@ -12860,7 +12856,8 @@
     "moment": {
       "version": "2.29.4",
       "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
-      "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
+      "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
+      "dev": true
     },
     "mri": {
       "version": "1.2.0",
@@ -13516,30 +13513,6 @@
         "fsevents": "~2.3.2"
       }
     },
-    "rollup-plugin-external-globals": {
-      "version": "0.6.1",
-      "resolved": "https://registry.npmjs.org/rollup-plugin-external-globals/-/rollup-plugin-external-globals-0.6.1.tgz",
-      "integrity": "sha512-mlp3KNa5sE4Sp9UUR2rjBrxjG79OyZAh/QC18RHIjM+iYkbBwNXSo8DHRMZWtzJTrH8GxQ+SJvCTN3i14uMXIA==",
-      "dev": true,
-      "requires": {
-        "@rollup/pluginutils": "^4.0.0",
-        "estree-walker": "^2.0.1",
-        "is-reference": "^1.2.1",
-        "magic-string": "^0.25.7"
-      },
-      "dependencies": {
-        "@rollup/pluginutils": {
-          "version": "4.2.1",
-          "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz",
-          "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==",
-          "dev": true,
-          "requires": {
-            "estree-walker": "^2.0.1",
-            "picomatch": "^2.2.2"
-          }
-        }
-      }
-    },
     "rollup-plugin-glob-import": {
       "version": "0.5.0",
       "resolved": "https://registry.npmjs.org/rollup-plugin-glob-import/-/rollup-plugin-glob-import-0.5.0.tgz",
diff --git a/package.json b/package.json
index 6796cad..474b4fb 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
     "@babel/preset-typescript": "^7.18.6",
     "@rollup/plugin-commonjs": "^22.0.1",
     "@rollup/plugin-node-resolve": "^13.3.0",
+    "@rollup/plugin-replace": "^4.0.0",
     "@rollup/plugin-typescript": "^8.3.3",
     "@types/diff": "^5.0.2",
     "@types/diff-match-patch": "^1.0.32",
@@ -35,9 +36,9 @@
     "eslint-plugin-jest": "^26.6.0",
     "eslint-plugin-unicorn": "^43.0.2",
     "jest": "^28.1.3",
+    "moment": "^2.29.4",
     "obsidian": "^0.15.4",
     "rollup": "^2.77.0",
-    "rollup-plugin-external-globals": "^0.6.1",
     "rollup-plugin-glob-import": "^0.5.0",
     "rollup-plugin-terser": "^7.0.2",
     "ts-node": "^10.9.1",
@@ -51,9 +52,8 @@
     "loglevel": "^1.8.0",
     "remark": "^14.0.2",
     "remark-gfm": "^3.0.1",
+    "ts-dedent": "^2.2.0",
     "unified": "^10.1.2",
-    "unified-lint-rule": "^2.1.0",
-    "moment": "^2.29.4",
-    "ts-dedent": "^2.2.0"
+    "unified-lint-rule": "^2.1.0"
   }
 }
diff --git a/rollup.config.js b/rollup.config.js
index fb7e88f..12f4a0c 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -1,8 +1,8 @@
 import typescript from '@rollup/plugin-typescript';
 import {nodeResolve} from '@rollup/plugin-node-resolve';
 import commonjs from '@rollup/plugin-commonjs';
-import externalGlobals from 'rollup-plugin-external-globals';
 import {plugin as globImport} from 'rollup-plugin-glob-import';
+import replace from '@rollup/plugin-replace';
 
 const isProd = (process.env.BUILD === 'production');
 
@@ -28,7 +28,7 @@ export default [
     plugins: [
       typescript({inlineSources: !isProd}),
       nodeResolve({browser: true}),
-      commonjs(),
+      commonjs({exclude: 'tests/**'}),
       globImport({
         format: 'default',
       }),
@@ -45,11 +45,16 @@ export default [
     external: ['obsidian'],
     plugins: [
       typescript(),
-      nodeResolve({browser: true}),
-      commonjs(),
-      externalGlobals({
-        'obsidian': 'obsidian',
+      replace({
+        values: {
+          'obsidian.moment': 'moment',
+          'var obsidian = require(\'obsidian\');': 'var moment = require(\'moment\');',
+        },
+        preventAssignment: true,
+        delimiters: ['', ''],
       }),
+      nodeResolve({browser: true}),
+      commonjs({exclude: 'tests/**'}),
       globImport({
         format: 'default',
       }),
diff --git a/src/main.ts b/src/main.ts
index fe7b10f..bf8388f 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -5,8 +5,7 @@ import dedent from 'ts-dedent';
 import {stripCr} from './utils/strings';
 import log from 'loglevel';
 import {logInfo, logError, logDebug, setLogLevel} from './logger';
-import moment from './moment-with-locales';
-
+import {moment} from 'obsidian';
 import './rules-registry';
 import EscapeYamlSpecialCharacters from './rules/escape-yaml-special-characters';
 import FormatTagsInYaml from './rules/format-tags-in-yaml';
@@ -71,6 +70,7 @@ const langToMomentLocale = {
 export default class LinterPlugin extends Plugin {
   settings: LinterSettings;
   private eventRef: EventRef;
+  private momentLocale: string;
 
   async onload() {
     logInfo('Loading plugin');
@@ -249,8 +249,8 @@ export default class LinterPlugin extends Plugin {
     // remove hashtags from tags before parsing yaml
     [newText] = FormatTagsInYaml.applyIfEnabled(newText, this.settings, disabledRules);
 
-    const modifiedAtTime = moment(file.stat.mtime).format();
-    const createdAtTime = moment(file.stat.ctime).format();
+    const modifiedAtTime = moment(file.stat.mtime, this.momentLocale).format();
+    const createdAtTime = moment(file.stat.ctime, this.momentLocale).format();
 
     for (const rule of rules) {
       // if you are run prior to or after the regular rules or are a disabled rule, skip running the rule
@@ -262,24 +262,28 @@ export default class LinterPlugin extends Plugin {
         fileCreatedTime: createdAtTime,
         fileModifiedTime: modifiedAtTime,
         fileName: file.basename,
-        moment: moment,
+        locale: this.momentLocale,
       });
     }
 
+    let currentTime = moment();
+    currentTime.locale(this.momentLocale);
     // run yaml timestamp at the end to help determine if something has changed
     let isYamlTimestampEnabled;
     [newText, isYamlTimestampEnabled] = YamlTimestamp.applyIfEnabled(newText, this.settings, disabledRules, {
       fileCreatedTime: createdAtTime,
       fileModifiedTime: modifiedAtTime,
-      currentTime: moment(),
+      currentTime: currentTime,
       alreadyModified: oldText != newText,
-      moment: moment,
+      locale: this.momentLocale,
     });
 
     const yamlTimestampOptions = YamlTimestamp.getRuleOptions(this.settings);
 
+    currentTime = moment();
+    currentTime.locale(this.momentLocale);
     [newText] = YamlKeySort.applyIfEnabled(newText, this.settings, disabledRules, {
-      currentTimeFormatted: moment().format(yamlTimestampOptions.format),
+      currentTimeFormatted: currentTime.format(yamlTimestampOptions.format),
       yamlTimestampDateModifiedEnabled: isYamlTimestampEnabled && yamlTimestampOptions.dateModified,
       dateModifiedKey: yamlTimestampOptions.dateModifiedKey,
     });
@@ -416,8 +420,12 @@ export default class LinterPlugin extends Plugin {
       momentLocale = systemLang;
     }
 
+    this.momentLocale = momentLocale;
+    const oldLocale = moment.locale();
     const currentLocale = moment.locale(momentLocale);
     logDebug(`Trying to switch Moment.js locale to ${momentLocale}, got ${currentLocale}`);
+
+    moment.locale(oldLocale);
   }
 
   private displayChangedMessage(charsAdded: number, charsRemoved: number) {
@@ -516,7 +524,6 @@ class SettingTab extends PluginSettingTab {
   }
 
   addLocaleOverrideSetting(): void {
-    const {moment} = window;
     const sysLocale = navigator.language?.toLowerCase();
 
     new Setting(this.containerEl)
diff --git a/src/moment-with-locales.ts b/src/moment-with-locales.ts
deleted file mode 100644
index 0832057..0000000
--- a/src/moment-with-locales.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import moment from 'moment';
-import '../node_modules/moment/locale/*';
-
-moment.locale('en');
-export default moment;
diff --git a/src/rules/yaml-timestamp.ts b/src/rules/yaml-timestamp.ts
index 46c0265..a631ef1 100644
--- a/src/rules/yaml-timestamp.ts
+++ b/src/rules/yaml-timestamp.ts
@@ -2,20 +2,20 @@ import {Options, RuleType} from '../rules';
 import RuleBuilder, {BooleanOptionBuilder, ExampleBuilder, MomentFormatOptionBuilder, OptionBuilderBase, TextOptionBuilder} from './rule-builder';
 import dedent from 'ts-dedent';
 import {formatYAML, initYAML} from '../utils/yaml';
-import moment from '../moment-with-locales';
+import {moment} from 'obsidian';
 import {escapeDollarSigns} from '../utils/regex';
 import {insert} from '../utils/strings';
 
 class YamlTimestampOptions implements Options {
   alreadyModified?: boolean;
   dateCreatedKey?: string = 'date created';
-  moment?: typeof moment = moment;
   dateCreated?: boolean = true;
   fileCreatedTime?: string;
   format?: string = 'dddd, MMMM Do YYYY, h:mm:ss a';
   dateModified?: boolean = true;
   dateModifiedKey?: string = 'date modified';
   fileModifiedTime?: string;
+  locale?: string = 'en';
   currentTime?: moment.Moment;
 }
 
@@ -44,10 +44,9 @@ export default class YamlTimestamp extends RuleBuilder<YamlTimestampOptions> {
       const created_key_match = new RegExp(created_key_match_str);
       const created_match = new RegExp(created_match_str);
 
-      const moment = options.moment;
-
       if (options.dateCreated) {
         const created_date = moment(options.fileCreatedTime);
+        created_date.locale(options.locale);
 
         const formatted_date = created_date.format(options.format);
         const created_date_line = `\n${options.dateCreatedKey}: ${formatted_date}`;
@@ -70,7 +69,7 @@ export default class YamlTimestamp extends RuleBuilder<YamlTimestampOptions> {
 
           textModified = true;
         } else if (keyWithValueFound) {
-          const createdDateTime = moment(text.match(created_match)[0].replace(options.dateCreatedKey + ':', '').trim(), options.format, true);
+          const createdDateTime = moment(text.match(created_match)[0].replace(options.dateCreatedKey + ':', '').trim(), options.format, options.locale, true);
           if (createdDateTime == undefined || !createdDateTime.isValid()) {
             text = text.replace(
                 created_match,
@@ -89,6 +88,7 @@ export default class YamlTimestamp extends RuleBuilder<YamlTimestampOptions> {
         const modified_match = new RegExp(modified_match_str);
 
         const modified_date = moment(options.fileModifiedTime);
+        modified_date.locale(options.locale);
         // using the current time helps prevent issues where the previous modified time was greater
         // than 5 seconds prior to the time the linter will finish with the file (i.e. helps prevent accidental infinite loops on updating the date modified value)
         const formatted_modified_date = options.currentTime.format(options.format);
@@ -96,7 +96,7 @@ export default class YamlTimestamp extends RuleBuilder<YamlTimestampOptions> {
 
         const keyWithValueFound = modified_match.test(text);
         if (keyWithValueFound) {
-          const modifiedDateTime = moment(text.match(modified_match)[0].replace(options.dateModifiedKey + ':', '').trim(), options.format, true);
+          const modifiedDateTime = moment(text.match(modified_match)[0].replace(options.dateModifiedKey + ':', '').trim(), options.format, options.locale, true);
           if (textModified || modifiedDateTime == undefined || !modifiedDateTime.isValid() ||
               Math.abs(modifiedDateTime.diff(modified_date, 'seconds')) > 5
           ) {
diff --git a/tsconfig.json b/tsconfig.json
index 70e2c26..b10d93d 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -8,7 +8,7 @@
     "allowJs": true,
     "noImplicitAny": true,
     "moduleResolution": "node",
-    "esModuleInterop": true,
+    "allowSyntheticDefaultImports": true,
     "importHelpers": true,
     "experimentalDecorators": true,
     "strictBindCallApply": true,


Test patch:
diff --git a/src/test/capitalize-headings.test.ts b/__tests__/capitalize-headings.test.ts
similarity index 97%
rename from src/test/capitalize-headings.test.ts
rename to __tests__/capitalize-headings.test.ts
index 50b1de1..6d74352 100644
--- a/src/test/capitalize-headings.test.ts
+++ b/__tests__/capitalize-headings.test.ts
@@ -1,4 +1,4 @@
-import CapitalizeHeadings from '../rules/capitalize-headings';
+import CapitalizeHeadings from '../src/rules/capitalize-headings';
 import dedent from 'ts-dedent';
 import {ruleTest} from './common';
 
diff --git a/src/test/common.ts b/__tests__/common.ts
similarity index 87%
rename from src/test/common.ts
rename to __tests__/common.ts
index 7ec64eb..51b5185 100644
--- a/src/test/common.ts
+++ b/__tests__/common.ts
@@ -1,5 +1,5 @@
-import {Options} from '../rules';
-import RuleBuilder, {RuleBuilderBase} from '../rules/rule-builder';
+import {Options} from '../src/rules';
+import RuleBuilder, {RuleBuilderBase} from '../src/rules/rule-builder';
 
 type TestCase<TOptions extends Options> = {
   testName: string,
diff --git a/src/test/consecutive-blank-lines.test.ts b/__tests__/consecutive-blank-lines.test.ts
similarity index 86%
rename from src/test/consecutive-blank-lines.test.ts
rename to __tests__/consecutive-blank-lines.test.ts
index 3380f83..1f86605 100644
--- a/src/test/consecutive-blank-lines.test.ts
+++ b/__tests__/consecutive-blank-lines.test.ts
@@ -1,4 +1,4 @@
-import ConsecutiveBlankLines from '../rules/consecutive-blank-lines';
+import ConsecutiveBlankLines from '../src/rules/consecutive-blank-lines';
 import dedent from 'ts-dedent';
 import {ruleTest} from './common';
 
diff --git a/src/test/convert-spaces-to-tabs.test.ts b/__tests__/convert-spaces-to-tabs.test.ts
similarity index 91%
rename from src/test/convert-spaces-to-tabs.test.ts
rename to __tests__/convert-spaces-to-tabs.test.ts
index 3ba1031..d5de292 100644
--- a/src/test/convert-spaces-to-tabs.test.ts
+++ b/__tests__/convert-spaces-to-tabs.test.ts
@@ -1,4 +1,4 @@
-import ConvertSpacesToTabs from '../rules/convert-spaces-to-tabs';
+import ConvertSpacesToTabs from '../src/rules/convert-spaces-to-tabs';
 import dedent from 'ts-dedent';
 import {ruleTest} from './common';
 
diff --git a/src/test/disabled-rules.test.ts b/__tests__/disabled-rules.test.ts
similarity index 96%
rename from src/test/disabled-rules.test.ts
rename to __tests__/disabled-rules.test.ts
index 76e96c9..8441d94 100644
--- a/src/test/disabled-rules.test.ts
+++ b/__tests__/disabled-rules.test.ts
@@ -1,5 +1,5 @@
 import dedent from 'ts-dedent';
-import {getDisabledRules, rules} from '../rules';
+import {getDisabledRules, rules} from '../src/rules';
 
 describe('Disabled rules parsing', () => {
   it('No YAML', () => {
diff --git a/src/test/empty-line-around-code-fences.test.ts b/__tests__/empty-line-around-code-fences.test.ts
similarity index 91%
rename from src/test/empty-line-around-code-fences.test.ts
rename to __tests__/empty-line-around-code-fences.test.ts
index 94960e9..833f0b0 100644
--- a/src/test/empty-line-around-code-fences.test.ts
+++ b/__tests__/empty-line-around-code-fences.test.ts
@@ -1,6 +1,6 @@
+import EmptyLineAroundCodeFences from '../src/rules/empty-line-around-code-fences';
 import dedent from 'ts-dedent';
 import {ruleTest} from './common';
-import EmptyLineAroundCodeFences from '../rules/empty-line-around-code-fences';
 
 ruleTest({
   RuleBuilderClass: EmptyLineAroundCodeFences,
diff --git a/src/test/empty-line-around-tables.test.ts b/__tests__/empty-line-around-tables.test.ts
similarity index 94%
rename from src/test/empty-line-around-tables.test.ts
rename to __tests__/empty-line-around-tables.test.ts
index da544a5..06eca7d 100644
--- a/src/test/empty-line-around-tables.test.ts
+++ b/__tests__/empty-line-around-tables.test.ts
@@ -1,6 +1,6 @@
 import dedent from 'ts-dedent';
 import {ruleTest} from './common';
-import EmptyLineAroundTables from '../rules/empty-line-around-tables';
+import EmptyLineAroundTables from '../src/rules/empty-line-around-tables';
 
 ruleTest({
   RuleBuilderClass: EmptyLineAroundTables,
diff --git a/src/test/examples.test.ts b/__tests__/examples.test.ts
similarity index 86%
rename from src/test/examples.test.ts
rename to __tests__/examples.test.ts
index 7ce64dd..0192b5d 100644
--- a/src/test/examples.test.ts
+++ b/__tests__/examples.test.ts
@@ -1,7 +1,7 @@
 import dedent from 'ts-dedent';
-import {Example, rules} from '../rules';
-import {yamlRegex, escapeRegExp} from '../utils/regex';
-import '../rules-registry';
+import {Example, rules} from '../src/rules';
+import {yamlRegex, escapeRegExp} from '../src/utils/regex';
+import '../src/rules-registry';
 
 describe('Examples pass', () => {
   for (const rule of rules) {
diff --git a/src/test/file-name-heading.test.ts b/__tests__/file-name-heading.test.ts
similarity index 88%
rename from src/test/file-name-heading.test.ts
rename to __tests__/file-name-heading.test.ts
index 864e34a..79d2728 100644
--- a/src/test/file-name-heading.test.ts
+++ b/__tests__/file-name-heading.test.ts
@@ -1,4 +1,4 @@
-import FileNameHeading from '../rules/file-name-heading';
+import FileNameHeading from '../src/rules/file-name-heading';
 import dedent from 'ts-dedent';
 import {ruleTest} from './common';
 
diff --git a/src/test/header-increment.test.ts b/__tests__/header-increment.test.ts
similarity index 94%
rename from src/test/header-increment.test.ts
rename to __tests__/header-increment.test.ts
index c006e64..0814def 100644
--- a/src/test/header-increment.test.ts
+++ b/__tests__/header-increment.test.ts
@@ -1,4 +1,4 @@
-import HeaderIncrement from '../rules/header-increment';
+import HeaderIncrement from '../src/rules/header-increment';
 import dedent from 'ts-dedent';
 import {ruleTest} from './common';
 
diff --git a/src/test/heading-blank-lines.test.ts b/__tests__/heading-blank-lines.test.ts
similarity index 95%
rename from src/test/heading-blank-lines.test.ts
rename to __tests__/heading-blank-lines.test.ts
index df724ca..3cb6668 100644
--- a/src/test/heading-blank-lines.test.ts
+++ b/__tests__/heading-blank-lines.test.ts
@@ -1,4 +1,4 @@
-import HeadingBlankLines from '../rules/heading-blank-lines';
+import HeadingBlankLines from '../src/rules/heading-blank-lines';
 import dedent from 'ts-dedent';
 import {ruleTest} from './common';
 
diff --git a/src/test/insert-yaml-attributes.test.ts b/__tests__/insert-yaml-attributes.test.ts
similarity index 95%
rename from src/test/insert-yaml-attributes.test.ts
rename to __tests__/insert-yaml-attributes.test.ts
index 3987e83..bd7377c 100644
--- a/src/test/insert-yaml-attributes.test.ts
+++ b/__tests__/insert-yaml-attributes.test.ts
@@ -1,4 +1,4 @@
-import InsertYamlAttributes from '../rules/insert-yaml-attributes';
+import InsertYamlAttributes from '../src/rules/insert-yaml-attributes';
 import dedent from 'ts-dedent';
 import {ruleTest} from './common';
 
diff --git a/src/test/missing-fields.test.ts b/__tests__/missing-fields.test.ts
similarity index 75%
rename from src/test/missing-fields.test.ts
rename to __tests__/missing-fields.test.ts
index fceefd7..043a814 100644
--- a/src/test/missing-fields.test.ts
+++ b/__tests__/missing-fields.test.ts
@@ -1,5 +1,5 @@
-import {Rule, rules} from '../rules';
-import '../rules-registry';
+import {Rule, rules} from '../src/rules';
+import '../src/rules-registry';
 
 describe('Check missing fields', () => {
   test.each(rules)('$name', (rule: Rule) => {
diff --git a/src/test/move-footnotes-to-the-bottom.test.ts b/__tests__/move-footnotes-to-the-bottom.test.ts
similarity index 99%
rename from src/test/move-footnotes-to-the-bottom.test.ts
rename to __tests__/move-footnotes-to-the-bottom.test.ts
index fc72385..88e10dd 100644
--- a/src/test/move-footnotes-to-the-bottom.test.ts
+++ b/__tests__/move-footnotes-to-the-bottom.test.ts
@@ -1,4 +1,4 @@
-import MoveFootnotesToTheBottom from '../rules/move-footnotes-to-the-bottom';
+import MoveFootnotesToTheBottom from '../src/rules/move-footnotes-to-the-bottom';
 import dedent from 'ts-dedent';
 import {ruleTest} from './common';
 
diff --git a/src/test/no-bare-urls.test.ts b/__tests__/no-bare-urls.test.ts
similarity index 91%
rename from src/test/no-bare-urls.test.ts
rename to __tests__/no-bare-urls.test.ts
index 151498e..a335001 100644
--- a/src/test/no-bare-urls.test.ts
+++ b/__tests__/no-bare-urls.test.ts
@@ -1,4 +1,4 @@
-import NoBareUrls from '../rules/no-bare-urls';
+import NoBareUrls from '../src/rules/no-bare-urls';
 import dedent from 'ts-dedent';
 import {ruleTest} from './common';
 
diff --git a/src/test/paragraph-blank-lines.test.ts b/__tests__/paragraph-blank-lines.test.ts
similarity index 98%
rename from src/test/paragraph-blank-lines.test.ts
rename to __tests__/paragraph-blank-lines.test.ts
index 9a6febb..b4644e9 100644
--- a/src/test/paragraph-blank-lines.test.ts
+++ b/__tests__/paragraph-blank-lines.test.ts
@@ -1,4 +1,4 @@
-import ParagraphBlankLines from '../rules/paragraph-blank-lines';
+import ParagraphBlankLines from '../src/rules/paragraph-blank-lines';
 import dedent from 'ts-dedent';
 import {ruleTest} from './common';
 
diff --git a/src/test/remove-empty-lines-between-list-markers-and-checklists.test.ts b/__tests__/remove-empty-lines-between-list-markers-and-checklists.test.ts
similarity index 96%
rename from src/test/remove-empty-lines-between-list-markers-and-checklists.test.ts
rename to __tests__/remove-empty-lines-between-list-markers-and-checklists.test.ts
index e4d9c27..c4b8256 100644
--- a/src/test/remove-empty-lines-between-list-markers-and-checklists.test.ts
+++ b/__tests__/remove-empty-lines-between-list-markers-and-checklists.test.ts
@@ -1,4 +1,4 @@
-import RemoveEmptyLinesBetweenListMarkersAndChecklists from '../rules/remove-empty-lines-between-list-markers-and-checklists';
+import RemoveEmptyLinesBetweenListMarkersAndChecklists from '../src/rules/remove-empty-lines-between-list-markers-and-checklists';
 import dedent from 'ts-dedent';
 import {ruleTest} from './common';
 
diff --git a/src/test/remove-hyphenated-line-breaks.test.ts b/__tests__/remove-hyphenated-line-breaks.test.ts
similarity index 89%
rename from src/test/remove-hyphenated-line-breaks.test.ts
rename to __tests__/remove-hyphenated-line-breaks.test.ts
index 88dd1e2..e07bf84 100644
--- a/src/test/remove-hyphenated-line-breaks.test.ts
+++ b/__tests__/remove-hyphenated-line-breaks.test.ts
@@ -1,4 +1,4 @@
-import RemoveHyphenatedLineBreaks from '../rules/remove-hyphenated-line-breaks';
+import RemoveHyphenatedLineBreaks from '../src/rules/remove-hyphenated-line-breaks';
 import dedent from 'ts-dedent';
 import {ruleTest} from './common';
 
diff --git a/src/test/remove-link-spacing.test.ts b/__tests__/remove-link-spacing.test.ts
similarity index 88%
rename from src/test/remove-link-spacing.test.ts
rename to __tests__/remove-link-spacing.test.ts
index 6556537..678d7f8 100644
--- a/src/test/remove-link-spacing.test.ts
+++ b/__tests__/remove-link-spacing.test.ts
@@ -1,4 +1,4 @@
-import RemoveLinkSpacing from '../rules/remove-link-spacing';
+import RemoveLinkSpacing from '../src/rules/remove-link-spacing';
 import dedent from 'ts-dedent';
 import {ruleTest} from './common';
 
diff --git a/src/test/remove-multiple-spaces.test.ts b/__tests__/remove-multiple-spaces.test.ts
similarity index 98%
rename from src/test/remove-multiple-spaces.test.ts
rename to __tests__/remove-multiple-spaces.test.ts
index f545ae6..cbe8abf 100644
--- a/src/test/remove-multiple-spaces.test.ts
+++ b/__tests__/remove-multiple-spaces.test.ts
@@ -1,4 +1,4 @@
-import RemoveMultipleSpaces from '../rules/remove-multiple-spaces';
+import RemoveMultipleSpaces from '../src/rules/remove-multiple-spaces';
 import dedent from 'ts-dedent';
 import {ruleTest} from './common';
 
diff --git a/src/test/space-after-list-markers.test.ts b/__tests__/space-after-list-markers.test.ts
similarity index 83%
rename from src/test/space-after-list-markers.test.ts
rename to __tests__/space-after-list-markers.test.ts
index 9c0fe6f..04abb34 100644
--- a/src/test/space-after-list-markers.test.ts
+++ b/__tests__/space-after-list-markers.test.ts
@@ -1,4 +1,4 @@
-import SpaceAfterListMarkers from '../rules/space-after-list-markers';
+import SpaceAfterListMarkers from '../src/rules/space-after-list-markers';
 import dedent from 'ts-dedent';
 import {ruleTest} from './common';
 
diff --git a/src/test/space-between-chinese-and-english-or-numbers.test.ts b/__tests__/space-between-chinese-and-english-or-numbers.test.ts
similarity index 83%
rename from src/test/space-between-chinese-and-english-or-numbers.test.ts
rename to __tests__/space-between-chinese-and-english-or-numbers.test.ts
index e8c249a..d489a23 100644
--- a/src/test/space-between-chinese-and-english-or-numbers.test.ts
+++ b/__tests__/space-between-chinese-and-english-or-numbers.test.ts
@@ -1,4 +1,4 @@
-import SpaceBetweenChineseAndEnglishOrNumbers from '../rules/space-between-chinese-and-english-or-numbers';
+import SpaceBetweenChineseAndEnglishOrNumbers from '../src/rules/space-between-chinese-and-english-or-numbers';
 import dedent from 'ts-dedent';
 import {ruleTest} from './common';
 
diff --git a/src/test/trailing-spaces.test.ts b/__tests__/trailing-spaces.test.ts
similarity index 97%
rename from src/test/trailing-spaces.test.ts
rename to __tests__/trailing-spaces.test.ts
index eee60c5..00cc3a5 100644
--- a/src/test/trailing-spaces.test.ts
+++ b/__tests__/trailing-spaces.test.ts
@@ -1,4 +1,4 @@
-import TrailingSpaces from '../rules/trailing-spaces';
+import TrailingSpaces from '../src/rules/trailing-spaces';
 import dedent from 'ts-dedent';
 import {ruleTest} from './common';
 
diff --git a/src/test/two-spaces-between-lines-with-content.test.ts b/__tests__/two-spaces-between-lines-with-content.test.ts
similarity index 94%
rename from src/test/two-spaces-between-lines-with-content.test.ts
rename to __tests__/two-spaces-between-lines-with-content.test.ts
index 15f746e..ab79f47 100644
--- a/src/test/two-spaces-between-lines-with-content.test.ts
+++ b/__tests__/two-spaces-between-lines-with-content.test.ts
@@ -1,4 +1,4 @@
-import TwoSpacesBetweenLinesWithContent from '../rules/two-spaces-between-lines-with-content';
+import TwoSpacesBetweenLinesWithContent from '../src/rules/two-spaces-between-lines-with-content';
 import dedent from 'ts-dedent';
 import {ruleTest} from './common';
 
diff --git a/src/test/yaml-key-sort.test.ts b/__tests__/yaml-key-sort.test.ts
similarity index 95%
rename from src/test/yaml-key-sort.test.ts
rename to __tests__/yaml-key-sort.test.ts
index 617ad2b..6d941f8 100644
--- a/src/test/yaml-key-sort.test.ts
+++ b/__tests__/yaml-key-sort.test.ts
@@ -1,4 +1,4 @@
-import YamlKeySort from '../rules/yaml-key-sort';
+import YamlKeySort from '../src/rules/yaml-key-sort';
 import dedent from 'ts-dedent';
 import {ruleTest} from './common';
 
diff --git a/src/test/yaml-timestamp.test.ts b/__tests__/yaml-timestamp.test.ts
similarity index 95%
rename from src/test/yaml-timestamp.test.ts
rename to __tests__/yaml-timestamp.test.ts
index 6a62198..b0440ea 100644
--- a/src/test/yaml-timestamp.test.ts
+++ b/__tests__/yaml-timestamp.test.ts
@@ -1,7 +1,7 @@
 import dedent from 'ts-dedent';
-import moment from '../moment-with-locales';
+import moment from 'moment';
 import {ruleTest} from './common';
-import YamlTimestamp from '../rules/yaml-timestamp';
+import YamlTimestamp from '../src/rules/yaml-timestamp';
 
 ruleTest({
   RuleBuilderClass: YamlTimestamp,
@@ -156,19 +156,16 @@ ruleTest({
         ---
       `,
       options: () => {
-        moment.locale('fr');
+        const locale = 'fr';
         return {
           dateCreated: false,
           dateModifiedKey: 'modified',
           fileModifiedTime: '2020-02-01T00:00:00-00:00',
-          currentTime: moment('samedi, février 1er 2020, 12:00:05 am', 'dddd, MMMM Do YYYY, h:mm:ss a'),
+          currentTime: moment('samedi, février 1er 2020, 12:00:05 am', 'dddd, MMMM Do YYYY, h:mm:ss a', locale),
           alreadyModified: false,
-          moment: moment,
+          locale: locale,
         };
       },
-      afterTestFunc: () => {
-        moment.locale('en');
-      },
     },
     {
       testName: 'Updates modified key when something has changed inside of the YAML timestamp rule',
@@ -280,17 +277,14 @@ ruleTest({
         ---
       `,
       options: () => {
-        moment.locale('fr');
         return {
           dateModified: false,
           dateCreatedKey: 'created',
           fileCreatedTime: '2020-02-01T00:00:00-00:00',
           alreadyModified: false,
+          locale: 'fr',
         };
       },
-      afterTestFunc: () => {
-        moment.locale('en');
-      },
     },
     {
       testName: 'Updates modified value when format has changed causing created value updated',
diff --git a/src/test/yaml-title-alias.test.ts b/__tests__/yaml-title-alias.test.ts
similarity index 99%
rename from src/test/yaml-title-alias.test.ts
rename to __tests__/yaml-title-alias.test.ts
index 5ba6e41..639fb36 100644
--- a/src/test/yaml-title-alias.test.ts
+++ b/__tests__/yaml-title-alias.test.ts
@@ -1,4 +1,4 @@
-import YamlTitleAlias from '../rules/yaml-title-alias';
+import YamlTitleAlias from '../src/rules/yaml-title-alias';
 import dedent from 'ts-dedent';
 import {ruleTest} from './common';
 
diff --git a/src/test/yaml-title.test.ts b/__tests__/yaml-title.test.ts
similarity index 97%
rename from src/test/yaml-title.test.ts
rename to __tests__/yaml-title.test.ts
index 139fdbb..608fc3a 100644
--- a/src/test/yaml-title.test.ts
+++ b/__tests__/yaml-title.test.ts
@@ -1,4 +1,4 @@
-import YamlTitle from '../rules/yaml-title';
+import YamlTitle from '../src/rules/yaml-title';
 import dedent from 'ts-dedent';
 import {ruleTest} from './common';
 
diff --git a/src/test/moment-locales.test.ts b/src/test/moment-locales.test.ts
deleted file mode 100644
index 0038433..0000000
--- a/src/test/moment-locales.test.ts
+++ /dev/null
@@ -1,150 +0,0 @@
-import moment from '../moment-with-locales';
-
-describe('moment', () => {
-  const locales = [
-    'af',
-    'ar',
-    'ar-dz',
-    'ar-kw',
-    'ar-ly',
-    'ar-ma',
-    'ar-sa',
-    'ar-tn',
-    'az',
-    'be',
-    'bg',
-    'bm',
-    'bn',
-    'bn-bd',
-    'bo',
-    'br',
-    'bs',
-    'ca',
-    'cs',
-    'cv',
-    'cy',
-    'da',
-    'de',
-    'de-at',
-    'de-ch',
-    'dv',
-    'el',
-    'en-au',
-    'en-ca',
-    'en-gb',
-    'en-ie',
-    'en-il',
-    'en-in',
-    'en-nz',
-    'en-sg',
-    'eo',
-    'es',
-    'es-do',
-    'es-mx',
-    'es-us',
-    'et',
-    'eu',
-    'fa',
-    'fi',
-    'fil',
-    'fo',
-    'fr',
-    'fr-ca',
-    'fr-ch',
-    'fy',
-    'ga',
-    'gd',
-    'gl',
-    'gom-deva',
-    'gom-latn',
-    'gu',
-    'he',
-    'hi',
-    'hr',
-    'hu',
-    'hy-am',
-    'id',
-    'is',
-    'it',
-    'it-ch',
-    'ja',
-    'jv',
-    'ka',
-    'kk',
-    'km',
-    'kn',
-    'ko',
-    'ku',
-    'ky',
-    'lb',
-    'lo',
-    'lt',
-    'lv',
-    'me',
-    'mi',
-    'mk',
-    'ml',
-    'mn',
-    'mr',
-    'ms',
-    'ms-my',
-    'mt',
-    'my',
-    'nb',
-    'ne',
-    'nl',
-    'nl-be',
-    'nn',
-    'oc-lnc',
-    'pa-in',
-    'pl',
-    'pt',
-    'pt-br',
-    'ro',
-    'ru',
-    'sd',
-    'se',
-    'si',
-    'sk',
-    'sl',
-    'sq',
-    'sr',
-    'sr-cyrl',
-    'ss',
-    'sv',
-    'sw',
-    'ta',
-    'te',
-    'tet',
-    'tg',
-    'th',
-    'tk',
-    'tlh',
-    'tl-ph',
-    'tr',
-    'tzl',
-    'tzm',
-    'tzm-latn',
-    'ug-cn',
-    'uk',
-    'ur',
-    'uz',
-    'uz-latn',
-    'vi',
-    'x-pseudo',
-    'yo',
-    'zh-cn',
-    'zh-hk',
-    'zh-mo',
-    'zh-tw',
-  ];
-
-  for (const locale of locales) {
-    it(`can set ${locale} locale`, () => {
-      const oldLocale = moment.locale();
-      const newLocale = moment.locale(locale);
-      expect(newLocale).toBe(locale);
-      moment.locale(oldLocale);
-    });
-  }
-});