This commit is contained in:
Ulf Gebhardt 2023-04-10 22:07:35 +02:00
parent d1def0cadc
commit e85059b13c
Signed by: ulfgebhardt
GPG Key ID: DA6B843E748679C9
9 changed files with 3228 additions and 700 deletions

3
.eslintignore Normal file
View File

@ -0,0 +1,3 @@
node_modules
**/*.min.js
build

172
.eslintrc.js Normal file
View File

@ -0,0 +1,172 @@
// eslint-disable-next-line import/no-commonjs, import/unambiguous
module.exports = {
root: true,
env: {
node: true,
},
parser: '@typescript-eslint/parser',
plugins: ['prettier', '@typescript-eslint', 'jest', 'import', 'n'],
extends: [
'standard',
'eslint:recommended',
'plugin:prettier/recommended',
'plugin:import/recommended',
'plugin:import/typescript',
],
settings: {
'import/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx'],
},
'import/resolver': {
typescript: {
project: ['./tsconfig.json'],
},
node: true,
},
},
rules: {
'no-console': ['error'],
'no-debugger': 'error',
'prettier/prettier': [
'error',
{
htmlWhitespaceSensitivity: 'ignore',
},
],
// jest
'jest/no-disabled-tests': 'error',
'jest/no-focused-tests': 'error',
'jest/no-identical-title': 'error',
'jest/prefer-to-have-length': 'error',
'jest/valid-expect': 'error',
// import
'import/export': 'error',
'import/no-deprecated': 'error',
'import/no-empty-named-blocks': 'error',
'import/no-extraneous-dependencies': 'error',
'import/no-mutable-exports': 'error',
'import/no-unused-modules': 'error',
'import/no-named-as-default': 'error',
'import/no-named-as-default-member': 'error',
'import/no-amd': 'error',
'import/no-commonjs': 'error',
'import/no-import-module-exports': 'error',
'import/no-nodejs-modules': 'off',
'import/unambiguous': 'error',
'import/default': 'error',
'import/named': 'error',
'import/namespace': 'error',
'import/no-absolute-path': 'error',
'import/no-cycle': 'error',
'import/no-dynamic-require': 'error',
'import/no-internal-modules': 'off',
'import/no-relative-packages': 'error',
'import/no-relative-parent-imports': ['error', { ignore: ['@/*'] }],
'import/no-self-import': 'error',
'import/no-unresolved': 'error',
'import/no-useless-path-segments': 'error',
'import/no-webpack-loader-syntax': 'error',
'import/consistent-type-specifier-style': 'error',
'import/exports-last': 'off',
'import/extensions': 'error',
'import/first': 'error',
'import/group-exports': 'off',
'import/newline-after-import': 'error',
'import/no-anonymous-default-export': 'error',
'import/no-default-export': 'error',
'import/no-duplicates': 'error',
'import/no-named-default': 'error',
'import/no-namespace': 'error',
'import/no-unassigned-import': 'error',
'import/order': [
'error',
{
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
'newlines-between': 'always',
pathGroups: [
{
pattern: '@?*/**',
group: 'external',
position: 'after',
},
{
pattern: '@/**',
group: 'external',
position: 'after',
},
],
alphabetize: {
order: 'asc' /* sort in ascending order. Options: ['ignore', 'asc', 'desc'] */,
caseInsensitive: true /* ignore case. Options: [true, false] */,
},
distinctGroup: true,
},
],
'import/prefer-default-export': 'off',
// n
'n/handle-callback-err': 'error',
'n/no-callback-literal': 'error',
'n/no-exports-assign': 'error',
'n/no-extraneous-import': 'error',
'n/no-extraneous-require': 'error',
'n/no-hide-core-modules': 'error',
'n/no-missing-import': 'off', // not compatible with typescript
'n/no-missing-require': 'error',
'n/no-new-require': 'error',
'n/no-path-concat': 'error',
'n/no-process-exit': 'error',
'n/no-unpublished-bin': 'error',
'n/no-unpublished-import': 'off', // TODO need to exclude seeds
'n/no-unpublished-require': 'error',
'n/no-unsupported-features': ['error', { ignores: ['modules'] }],
'n/no-unsupported-features/es-builtins': 'error',
'n/no-unsupported-features/es-syntax': 'error',
'n/no-unsupported-features/node-builtins': 'error',
'n/process-exit-as-throw': 'error',
'n/shebang': 'error',
'n/callback-return': 'error',
'n/exports-style': 'error',
'n/file-extension-in-import': 'off',
'n/global-require': 'error',
'n/no-mixed-requires': 'error',
'n/no-process-env': 'error',
'n/no-restricted-import': 'error',
'n/no-restricted-require': 'error',
'n/no-sync': 'error',
'n/prefer-global/buffer': 'error',
'n/prefer-global/console': 'error',
'n/prefer-global/process': 'error',
'n/prefer-global/text-decoder': 'error',
'n/prefer-global/text-encoder': 'error',
'n/prefer-global/url': 'error',
'n/prefer-global/url-search-params': 'error',
'n/prefer-promises/dns': 'error',
'n/prefer-promises/fs': 'error',
},
overrides: [
// only for ts files
{
files: ['*.ts', '*.tsx'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
'plugin:@typescript-eslint/strict',
],
rules: {
// allow explicitly defined dangling promises
'@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
'no-void': ['error', { allowAsStatement: true }],
// ignore prefer-regexp-exec rule to allow string.match(regex)
'@typescript-eslint/prefer-regexp-exec': 'off',
// this should not run on ts files: https://github.com/import-js/eslint-plugin-import/issues/2215#issuecomment-911245486
'import/unambiguous': 'off',
},
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
// this is to properly reference the referenced projects without requirement of compiling it
EXPERIMENTAL_useSourceOfProjectReferenceRedirect: true,
},
},
],
}

9
.prettierrc.js Normal file
View File

@ -0,0 +1,9 @@
module.exports = {
semi: false,
printWidth: 100,
singleQuote: true,
trailingComma: "all",
tabWidth: 2,
bracketSpacing: true,
endOfLine: "auto",
};

View File

@ -1,4 +1,3 @@
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
// eslint-disable-next-line import/no-commonjs, import/unambiguous
module.exports = {
verbose: true,
@ -10,26 +9,5 @@ module.exports = {
lines: 100,
},
},
// setupFiles: ['<rootDir>/test/testSetup.ts'],
// setupFilesAfterEnv: ['<rootDir>/test/extensions.ts'],
modulePathIgnorePatterns: ['<rootDir>/build/'],
/*moduleNameMapper: {
'@/(.*)': '<rootDir>/src/$1',
'@arg/(.*)': '<rootDir>/src/graphql/arg/$1',
'@enum/(.*)': '<rootDir>/src/graphql/enum/$1',
'@model/(.*)': '<rootDir>/src/graphql/model/$1',
'@union/(.*)': '<rootDir>/src/graphql/union/$1',
'@repository/(.*)': '<rootDir>/src/typeorm/repository/$1',
'@test/(.*)': '<rootDir>/test/$1',
'@entity/(.*)':
// eslint-disable-next-line n/no-process-env
process.env.NODE_ENV === 'development'
? '<rootDir>/../database/entity/$1'
: '<rootDir>/../database/build/entity/$1',
'@dbTools/(.*)':
// eslint-disable-next-line n/no-process-env
process.env.NODE_ENV === 'development'
? '<rootDir>/../database/src/$1'
: '<rootDir>/../database/build/src/$1',
},*/
}

2880
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,8 @@
"main": "build/index.js",
"scripts": {
"test": "jest",
"build": "tsc"
"build": "tsc",
"lint": "eslint --max-warnings=0 ."
},
"repository": {
"type": "git",
@ -26,8 +27,21 @@
"devDependencies": {
"@types/jest": "^29.5.0",
"@types/node": "^18.15.11",
"@typescript-eslint/eslint-plugin": "^5.58.0",
"@typescript-eslint/parser": "^5.58.0",
"eslint": "^8.38.0",
"eslint-config-prettier": "^8.8.0",
"eslint-config-standard": "^17.0.0",
"eslint-import-resolver-typescript": "^3.5.5",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jest": "^27.2.1",
"eslint-plugin-n": "^15.7.0",
"eslint-plugin-prettier": "^4.2.1",
"jest": "^29.5.0",
"ts-jest": "^29.1.0",
"typescript": "^5.0.4"
},
"engines": {
"node": ">=14"
}
}

View File

@ -1,439 +1,399 @@
import { open } from 'fs/promises';
import { TOC, parse } from './'
import { open } from 'fs/promises'
import path from 'path'
import { TOC, parse } from '.'
describe('parse TOC', () => {
describe('empty file', () => {
it('returns empty TOC object', async () => {
const file = await open(__dirname + '/../test/empty.toc', 'r');
const toc = await parse(file)
expect(toc).toEqual(new TOC())
const file = await open(path.join(__dirname, '../test/empty.toc'), 'r')
const toc = await parse(file)
expect(toc).toEqual(new TOC())
})
})
describe('basic file', () => {
it('returns basic TOC object', async () => {
const file = await open(__dirname + '/../test/basic.toc', 'r');
const toc = await parse(file)
expect(toc).toEqual({
AddonCompartmentFunc: null,
AddonCompartmentFuncOnEnter: null,
AddonCompartmentFuncOnLeave: null,
Author: null,
DefaultState: true,
Dependencies: [],
IconAtlas: null,
IconTexture: null,
Interface: 50400,
LoadManagers: [],
LoadOnDemand: false,
LoadWith: [],
Notes: "\"Just basic\"",
NotesLocalized: {},
OptionalDeps: [],
SavedVariables: [],
SavedVariablesPerCharacter: [],
Title: "Basic Toc File",
TitleLocalized: {},
Version: "4.2",
files: [
"Dornhoeschen.xml",
"Rapunzel.lua",
],
xTags: {},
})
const file = await open(path.join(__dirname, '../test/basic.toc'), 'r')
const toc = await parse(file)
expect(toc).toEqual({
AddonCompartmentFunc: null,
AddonCompartmentFuncOnEnter: null,
AddonCompartmentFuncOnLeave: null,
Author: null,
DefaultState: true,
Dependencies: [],
IconAtlas: null,
IconTexture: null,
Interface: 50400,
LoadManagers: [],
LoadOnDemand: false,
LoadWith: [],
Notes: '"Just basic"',
NotesLocalized: {},
OptionalDeps: [],
SavedVariables: [],
SavedVariablesPerCharacter: [],
Title: 'Basic Toc File',
TitleLocalized: {},
Version: '4.2',
files: ['Dornhoeschen.xml', 'Rapunzel.lua'],
xTags: {},
})
})
})
describe('complete file', () => {
it('returns complete TOC object', async () => {
const file = await open(__dirname + '/../test/complete.toc', 'r');
const toc = await parse(file)
expect(toc).toEqual({
"AddonCompartmentFunc": "Func1",
"AddonCompartmentFuncOnEnter": "Func2",
"AddonCompartmentFuncOnLeave": "Func3",
"Author": "ulfgebhardt",
"DefaultState": false,
"Dependencies": [
"Lib1",
"Lib2",
"Lib3",
],
"IconAtlas": "IconB",
"IconTexture": "IconA",
"Interface": 123,
"LoadManagers": [
"Addon1",
"Addon2",
"Addon3",
],
"LoadOnDemand": true,
"LoadWith": [
"AddonA",
"AddonB",
"AddonC",
],
"Notes": "Describes the full Toc API",
"NotesLocalized": {
"deDE": "Notes deDE",
"enGB": "Notes enGB",
"enUS": "Notes enUS",
"esES": "Notes esES",
"esMX": "Notes esMX",
"frFR": "Notes frFR",
"itIT": "Notes itIT",
"koKR": "Notes koKR",
"ptBR": "Notes ptBR",
"ruRU": "Notes ruRU",
"zhCN": "Notes zhCN",
"zhTW": "Notes zhTW",
},
"OptionalDeps": [
"LibA",
"LibB",
"LibC",
],
"SavedVariables": [
"Var1",
"Var2",
"Var3",
],
"SavedVariablesPerCharacter": [
"CVar1",
"CVar2",
"CVar3",
],
"Title": "Complete Toc File",
"TitleLocalized": {
"deDE": "Title deDE",
"enGB": "Title enGB",
"enUS": "Title enUS",
"esES": "Title esES",
"esMX": "Title esMX",
"frFR": "Title frFR",
"itIT": "Title itIT",
"koKR": "Title koKR",
"ptBR": "Title ptBR",
"ruRU": "Title ruRU",
"zhCN": "Title zhCN",
"zhTW": "Title zhTW",
},
"Version": "456",
"files": [
"Dornhoeschen.xml",
"Rapunzel.lua",
],
"xTags": {
"Tag": "XTag",
}
})
const file = await open(path.join(__dirname, '../test/complete.toc'), 'r')
const toc = await parse(file)
expect(toc).toEqual({
AddonCompartmentFunc: 'Func1',
AddonCompartmentFuncOnEnter: 'Func2',
AddonCompartmentFuncOnLeave: 'Func3',
Author: 'ulfgebhardt',
DefaultState: false,
Dependencies: ['Lib1', 'Lib2', 'Lib3'],
IconAtlas: 'IconB',
IconTexture: 'IconA',
Interface: 123,
LoadManagers: ['Addon1', 'Addon2', 'Addon3'],
LoadOnDemand: true,
LoadWith: ['AddonA', 'AddonB', 'AddonC'],
Notes: 'Describes the full Toc API',
NotesLocalized: {
deDE: 'Notes deDE',
enGB: 'Notes enGB',
enUS: 'Notes enUS',
esES: 'Notes esES',
esMX: 'Notes esMX',
frFR: 'Notes frFR',
itIT: 'Notes itIT',
koKR: 'Notes koKR',
ptBR: 'Notes ptBR',
ruRU: 'Notes ruRU',
zhCN: 'Notes zhCN',
zhTW: 'Notes zhTW',
},
OptionalDeps: ['LibA', 'LibB', 'LibC'],
SavedVariables: ['Var1', 'Var2', 'Var3'],
SavedVariablesPerCharacter: ['CVar1', 'CVar2', 'CVar3'],
Title: 'Complete Toc File',
TitleLocalized: {
deDE: 'Title deDE',
enGB: 'Title enGB',
enUS: 'Title enUS',
esES: 'Title esES',
esMX: 'Title esMX',
frFR: 'Title frFR',
itIT: 'Title itIT',
koKR: 'Title koKR',
ptBR: 'Title ptBR',
ruRU: 'Title ruRU',
zhCN: 'Title zhCN',
zhTW: 'Title zhTW',
},
Version: '456',
files: ['Dornhoeschen.xml', 'Rapunzel.lua'],
xTags: {
Tag: 'XTag',
},
})
})
})
describe('Recount file', () => {
it('returns Recount TOC object', async () => {
const file = await open(__dirname + '/../test/Recount.toc', 'r');
const toc = await parse(file)
expect(toc).toEqual({
AddonCompartmentFunc: null,
AddonCompartmentFuncOnEnter: null,
AddonCompartmentFuncOnLeave: null,
Author: "Cryect, ported to 2.4 by Elsia, maintained by Resike from 5.4",
DefaultState: true,
Dependencies: [],
IconAtlas: null,
IconTexture: null,
Interface: 50400,
LoadManagers: [],
LoadOnDemand: false,
LoadWith: [],
Notes: "Records Damage and Healing for Graph Based Display.",
NotesLocalized: {
"ruRU": "Записывает урон и исцеления и отоброжает различные графики.",
"zhCN": "基于 Graph 裤开发的伤害/治疗统计插件.",
"zhTW": "圖形化顯示的傷害/治療統計插件.",
},
OptionalDeps: [
"Ace3",
"LibDropdown-1.0",
"LibSharedMedia-3.0",
"LibBossIDs-1.0",
"LibGraph-2.0",
],
SavedVariables: ["RecountDB"],
SavedVariablesPerCharacter: ["RecountPerCharDB"],
Title: "Recount",
TitleLocalized: {},
Version: "r1269",
files: [
"embeds.xml",
"locales\\Recount-enUS.lua",
"locales\\Recount-deDE.lua",
"locales\\Recount-esES.lua",
"locales\\Recount-esMX.lua",
"locales\\Recount-frFR.lua",
"locales\\Recount-ptBR.lua",
"locales\\Recount-ruRU.lua",
"locales\\Recount-koKR.lua",
"locales\\Recount-zhTW.lua",
"locales\\Recount-zhCN.lua",
"Recount.lua",
"Fonts.lua",
"colors.lua",
"Widgets.lua",
"WindowOrder.lua",
"Fights.lua",
"Recount_Modes.lua",
"TrackerModules\\TrackerModule_Dispels.lua",
"TrackerModules\\TrackerModule_Interrupts.lua",
"TrackerModules\\TrackerModule_Resurrection.lua",
"TrackerModules\\TrackerModule_CCBreakers.lua",
"TrackerModules\\TrackerModule_PowerGains.lua",
"Tracker.lua",
"roster.lua",
"LazySync.lua",
"deletion.lua",
"zonefilters.lua",
"debug.lua",
"GUI_Main.lua",
"GUI_Detail.lua",
"GUI_DeathGraph.lua",
"GUI_Graph.lua",
"GUI_Reset.lua",
"GUI_Report.lua",
"GUI_Config.lua",
"GUI_Realtime.lua"
],
xTags: {
"Curse-Packaged-Version": "r1269",
"Curse-Project-ID": "recount",
"Curse-Project-Name": "Recount",
"Curse-Repository-ID": "wow/recount/mainline"
},
})
const file = await open(path.join(__dirname, '../test/Recount.toc'), 'r')
const toc = await parse(file)
expect(toc).toEqual({
AddonCompartmentFunc: null,
AddonCompartmentFuncOnEnter: null,
AddonCompartmentFuncOnLeave: null,
Author: 'Cryect, ported to 2.4 by Elsia, maintained by Resike from 5.4',
DefaultState: true,
Dependencies: [],
IconAtlas: null,
IconTexture: null,
Interface: 50400,
LoadManagers: [],
LoadOnDemand: false,
LoadWith: [],
Notes: 'Records Damage and Healing for Graph Based Display.',
NotesLocalized: {
ruRU: 'Записывает урон и исцеления и отоброжает различные графики.',
zhCN: '基于 Graph 裤开发的伤害/治疗统计插件.',
zhTW: '圖形化顯示的傷害/治療統計插件.',
},
OptionalDeps: [
'Ace3',
'LibDropdown-1.0',
'LibSharedMedia-3.0',
'LibBossIDs-1.0',
'LibGraph-2.0',
],
SavedVariables: ['RecountDB'],
SavedVariablesPerCharacter: ['RecountPerCharDB'],
Title: 'Recount',
TitleLocalized: {},
Version: 'r1269',
files: [
'embeds.xml',
'locales\\Recount-enUS.lua',
'locales\\Recount-deDE.lua',
'locales\\Recount-esES.lua',
'locales\\Recount-esMX.lua',
'locales\\Recount-frFR.lua',
'locales\\Recount-ptBR.lua',
'locales\\Recount-ruRU.lua',
'locales\\Recount-koKR.lua',
'locales\\Recount-zhTW.lua',
'locales\\Recount-zhCN.lua',
'Recount.lua',
'Fonts.lua',
'colors.lua',
'Widgets.lua',
'WindowOrder.lua',
'Fights.lua',
'Recount_Modes.lua',
'TrackerModules\\TrackerModule_Dispels.lua',
'TrackerModules\\TrackerModule_Interrupts.lua',
'TrackerModules\\TrackerModule_Resurrection.lua',
'TrackerModules\\TrackerModule_CCBreakers.lua',
'TrackerModules\\TrackerModule_PowerGains.lua',
'Tracker.lua',
'roster.lua',
'LazySync.lua',
'deletion.lua',
'zonefilters.lua',
'debug.lua',
'GUI_Main.lua',
'GUI_Detail.lua',
'GUI_DeathGraph.lua',
'GUI_Graph.lua',
'GUI_Reset.lua',
'GUI_Report.lua',
'GUI_Config.lua',
'GUI_Realtime.lua',
],
xTags: {
'Curse-Packaged-Version': 'r1269',
'Curse-Project-ID': 'recount',
'Curse-Project-Name': 'Recount',
'Curse-Repository-ID': 'wow/recount/mainline',
},
})
})
})
describe('strict checks', () => {
describe('for lines longer then 1024 characters', () => {
it('fails with strict = true', async () => {
const file = await open(__dirname + '/../test/strict.1024.toc', 'r');
expect(parse(file)).rejects.toThrowError('Line is longer than TOC_LINE_MAX_LENGTH characters, WoW will truncate!')
})
it('succeeds with strict = false', async () => {
const file = await open(__dirname + '/../test/strict.1024.toc', 'r');
const toc = await parse(file, false)
expect(toc).toEqual({
AddonCompartmentFunc: null,
AddonCompartmentFuncOnEnter: null,
AddonCompartmentFuncOnLeave: null,
Author: null,
DefaultState: true,
Dependencies: [],
IconAtlas: null,
IconTexture: null,
Interface: null,
LoadManagers: [],
LoadOnDemand: false,
LoadWith: [],
Notes: null,
NotesLocalized: {},
OptionalDeps: [],
SavedVariables: [],
SavedVariablesPerCharacter: [],
Title: "Strict Check - 1024",
TitleLocalized: {},
Version: null,
files: [
"somefile.lua",
],
xTags: {
"Long": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
},
})
})
})
describe('for double colon', () => {
it('fails with strict = true', async () => {
const file = await open(__dirname + '/../test/strict.doublecolon.toc', 'r');
expect(parse(file)).rejects.toThrowError('Tag could not be parsed: X-Invalid-Tag1,, Weird Tag1')
})
it('succeeds with strict = false', async () => {
const file = await open(__dirname + '/../test/strict.doublecolon.toc', 'r');
it('fails with strict = true', async () => {
const file = await open(path.join(__dirname, '../test/strict.1024.toc'), 'r')
await expect(parse(file)).rejects.toThrowError(
'Line is longer than TOC_LINE_MAX_LENGTH characters, WoW will truncate!',
)
})
it('succeeds with strict = false', async () => {
const file = await open(path.join(__dirname, '../test/strict.1024.toc'), 'r')
const toc = await parse(file, false)
expect(toc).toEqual({
AddonCompartmentFunc: null,
AddonCompartmentFuncOnEnter: null,
AddonCompartmentFuncOnLeave: null,
Author: null,
DefaultState: true,
Dependencies: [],
IconAtlas: null,
IconTexture: null,
Interface: null,
LoadManagers: [],
LoadOnDemand: false,
LoadWith: [],
Notes: null,
NotesLocalized: {},
OptionalDeps: [],
SavedVariables: [],
SavedVariablesPerCharacter: [],
Title: "Strict Check - Double Colon",
TitleLocalized: {},
Version: null,
files: [
"somefile.lua",
],
xTags: {
},
AddonCompartmentFuncOnLeave: null,
Author: null,
DefaultState: true,
Dependencies: [],
IconAtlas: null,
IconTexture: null,
Interface: null,
LoadManagers: [],
LoadOnDemand: false,
LoadWith: [],
Notes: null,
NotesLocalized: {},
OptionalDeps: [],
SavedVariables: [],
SavedVariablesPerCharacter: [],
Title: 'Strict Check - 1024',
TitleLocalized: {},
Version: null,
files: ['somefile.lua'],
xTags: {
Long: 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd',
},
})
})
})
})
describe('for duplicate tag', () => {
it('fails with strict = true', async () => {
const file = await open(__dirname + '/../test/strict.duplicate.toc', 'r');
expect(parse(file)).rejects.toThrowError('Duplicate value: X-Duplicate-Tag')
})
it('succeeds with strict = false', async () => {
const file = await open(__dirname + '/../test/strict.duplicate.toc', 'r');
describe('for double colon', () => {
it('fails with strict = true', async () => {
const file = await open(path.join(__dirname, '../test/strict.doublecolon.toc'), 'r')
await expect(parse(file)).rejects.toThrowError(
'Tag could not be parsed: X-Invalid-Tag1,, Weird Tag1',
)
})
it('succeeds with strict = false', async () => {
const file = await open(path.join(__dirname, '../test/strict.doublecolon.toc'), 'r')
const toc = await parse(file, false)
expect(toc).toEqual({
AddonCompartmentFunc: null,
AddonCompartmentFuncOnEnter: null,
AddonCompartmentFuncOnLeave: null,
Author: null,
DefaultState: true,
Dependencies: [],
IconAtlas: null,
IconTexture: null,
Interface: null,
LoadManagers: [],
LoadOnDemand: false,
LoadWith: [],
Notes: null,
NotesLocalized: {},
OptionalDeps: [],
SavedVariables: [],
SavedVariablesPerCharacter: [],
Title: "Strict Check - Duplicate Tag",
TitleLocalized: {},
Version: null,
files: [
"somefile.lua",
],
xTags: {
"Duplicate-Tag": "Second"
},
AddonCompartmentFuncOnLeave: null,
Author: null,
DefaultState: true,
Dependencies: [],
IconAtlas: null,
IconTexture: null,
Interface: null,
LoadManagers: [],
LoadOnDemand: false,
LoadWith: [],
Notes: null,
NotesLocalized: {},
OptionalDeps: [],
SavedVariables: [],
SavedVariablesPerCharacter: [],
Title: 'Strict Check - Double Colon',
TitleLocalized: {},
Version: null,
files: ['somefile.lua'],
xTags: {},
})
})
})
})
describe('for unknown notes locale', () => {
it('fails with strict = true', async () => {
const file = await open(__dirname + '/../test/strict.locale.notes.toc', 'r');
expect(parse(file)).rejects.toThrowError('Locale not found: noNO')
})
it('succeeds with strict = false', async () => {
const file = await open(__dirname + '/../test/strict.locale.notes.toc', 'r');
describe('for duplicate tag', () => {
it('fails with strict = true', async () => {
const file = await open(path.join(__dirname, '../test/strict.duplicate.toc'), 'r')
await expect(parse(file)).rejects.toThrowError('Duplicate value: X-Duplicate-Tag')
})
it('succeeds with strict = false', async () => {
const file = await open(path.join(__dirname, '../test/strict.duplicate.toc'), 'r')
const toc = await parse(file, false)
expect(toc).toEqual({
AddonCompartmentFunc: null,
AddonCompartmentFuncOnEnter: null,
AddonCompartmentFuncOnLeave: null,
Author: null,
DefaultState: true,
Dependencies: [],
IconAtlas: null,
IconTexture: null,
Interface: null,
LoadManagers: [],
LoadOnDemand: false,
LoadWith: [],
Notes: null,
NotesLocalized: {},
OptionalDeps: [],
SavedVariables: [],
SavedVariablesPerCharacter: [],
Title: "Strict Check - Unknown Notes Locale",
TitleLocalized: {},
Version: null,
files: [
"somefile.lua",
],
xTags: {
},
AddonCompartmentFuncOnLeave: null,
Author: null,
DefaultState: true,
Dependencies: [],
IconAtlas: null,
IconTexture: null,
Interface: null,
LoadManagers: [],
LoadOnDemand: false,
LoadWith: [],
Notes: null,
NotesLocalized: {},
OptionalDeps: [],
SavedVariables: [],
SavedVariablesPerCharacter: [],
Title: 'Strict Check - Duplicate Tag',
TitleLocalized: {},
Version: null,
files: ['somefile.lua'],
xTags: {
'Duplicate-Tag': 'Second',
},
})
})
})
})
describe('for unknown title locale', () => {
it('fails with strict = true', async () => {
const file = await open(__dirname + '/../test/strict.locale.title.toc', 'r');
expect(parse(file)).rejects.toThrowError('Locale not found: noNO')
})
it('succeeds with strict = false', async () => {
const file = await open(__dirname + '/../test/strict.locale.title.toc', 'r');
describe('for unknown notes locale', () => {
it('fails with strict = true', async () => {
const file = await open(path.join(__dirname, '../test/strict.locale.notes.toc'), 'r')
await expect(parse(file)).rejects.toThrowError('Locale not found: noNO')
})
it('succeeds with strict = false', async () => {
const file = await open(path.join(__dirname, '../test/strict.locale.notes.toc'), 'r')
const toc = await parse(file, false)
expect(toc).toEqual({
AddonCompartmentFunc: null,
AddonCompartmentFuncOnEnter: null,
AddonCompartmentFuncOnLeave: null,
Author: null,
DefaultState: true,
Dependencies: [],
IconAtlas: null,
IconTexture: null,
Interface: null,
LoadManagers: [],
LoadOnDemand: false,
LoadWith: [],
Notes: null,
NotesLocalized: {},
OptionalDeps: [],
SavedVariables: [],
SavedVariablesPerCharacter: [],
Title: "Strict Check - Unknown Title Locale",
TitleLocalized: {},
Version: null,
files: [
"somefile.lua",
],
xTags: {
},
AddonCompartmentFuncOnLeave: null,
Author: null,
DefaultState: true,
Dependencies: [],
IconAtlas: null,
IconTexture: null,
Interface: null,
LoadManagers: [],
LoadOnDemand: false,
LoadWith: [],
Notes: null,
NotesLocalized: {},
OptionalDeps: [],
SavedVariables: [],
SavedVariablesPerCharacter: [],
Title: 'Strict Check - Unknown Notes Locale',
TitleLocalized: {},
Version: null,
files: ['somefile.lua'],
xTags: {},
})
})
})
})
describe('for unknown tag', () => {
it('fails with strict = true', async () => {
const file = await open(__dirname + '/../test/strict.unknowntag.toc', 'r');
expect(parse(file)).rejects.toThrowError('Unknown Tag name: SomeTag')
})
it('succeeds with strict = false', async () => {
const file = await open(__dirname + '/../test/strict.unknowntag.toc', 'r');
describe('for unknown title locale', () => {
it('fails with strict = true', async () => {
const file = await open(path.join(__dirname, '../test/strict.locale.title.toc'), 'r')
await expect(parse(file)).rejects.toThrowError('Locale not found: noNO')
})
it('succeeds with strict = false', async () => {
const file = await open(path.join(__dirname, '../test/strict.locale.title.toc'), 'r')
const toc = await parse(file, false)
expect(toc).toEqual({
AddonCompartmentFunc: null,
AddonCompartmentFuncOnEnter: null,
AddonCompartmentFuncOnLeave: null,
Author: null,
DefaultState: true,
Dependencies: [],
IconAtlas: null,
IconTexture: null,
Interface: null,
LoadManagers: [],
LoadOnDemand: false,
LoadWith: [],
Notes: null,
NotesLocalized: {},
OptionalDeps: [],
SavedVariables: [],
SavedVariablesPerCharacter: [],
Title: "Strict Check - Unknown Tag",
TitleLocalized: {},
Version: null,
files: [
"somefile.lua",
],
xTags: {
},
AddonCompartmentFuncOnLeave: null,
Author: null,
DefaultState: true,
Dependencies: [],
IconAtlas: null,
IconTexture: null,
Interface: null,
LoadManagers: [],
LoadOnDemand: false,
LoadWith: [],
Notes: null,
NotesLocalized: {},
OptionalDeps: [],
SavedVariables: [],
SavedVariablesPerCharacter: [],
Title: 'Strict Check - Unknown Title Locale',
TitleLocalized: {},
Version: null,
files: ['somefile.lua'],
xTags: {},
})
})
})
})
})
describe('for unknown tag', () => {
it('fails with strict = true', async () => {
const file = await open(path.join(__dirname, '../test/strict.unknowntag.toc'), 'r')
await expect(parse(file)).rejects.toThrowError('Unknown Tag name: SomeTag')
})
it('succeeds with strict = false', async () => {
const file = await open(path.join(__dirname, '../test/strict.unknowntag.toc'), 'r')
const toc = await parse(file, false)
expect(toc).toEqual({
AddonCompartmentFunc: null,
AddonCompartmentFuncOnEnter: null,
AddonCompartmentFuncOnLeave: null,
Author: null,
DefaultState: true,
Dependencies: [],
IconAtlas: null,
IconTexture: null,
Interface: null,
LoadManagers: [],
LoadOnDemand: false,
LoadWith: [],
Notes: null,
NotesLocalized: {},
OptionalDeps: [],
SavedVariables: [],
SavedVariablesPerCharacter: [],
Title: 'Strict Check - Unknown Tag',
TitleLocalized: {},
Version: null,
files: ['somefile.lua'],
xTags: {},
})
})
})
})
})

View File

@ -1,4 +1,4 @@
import { FileHandle } from 'fs/promises';
import { FileHandle } from 'fs/promises'
export const TOC_PREFIX_TAG = '## '
export const TOC_PREFIX_COMMENT = '#'
@ -51,20 +51,20 @@ export enum WOW_LOCALES {
export class TOC {
Interface: number | null = null
Title: string | null = null
TitleLocalized: Partial<Record<WOW_LOCALES,string>> = {}
TitleLocalized: Partial<Record<WOW_LOCALES, string>> = {}
Notes: string | null = null
NotesLocalized: Partial<Record<WOW_LOCALES,string>> = {}
NotesLocalized: Partial<Record<WOW_LOCALES, string>> = {}
IconTexture: string | null = null
IconAtlas: string | null = null
AddonCompartmentFunc: string | null = null
AddonCompartmentFuncOnEnter: string | null = null
AddonCompartmentFuncOnLeave: string | null = null
LoadOnDemand: boolean = false
LoadOnDemand = false
Dependencies: string[] = []
OptionalDeps: string[] = []
LoadWith: string[] = []
LoadManagers: string[] = []
DefaultState: boolean = true
DefaultState = true
SavedVariables: string[] = []
SavedVariablesPerCharacter: string[] = []
Author: string | null = null
@ -73,19 +73,19 @@ export class TOC {
files: string[] = []
}
export const parse = async function(file: FileHandle, strict = true) {
const result = new TOC();
export const parse = async function (file: FileHandle, strict = true) {
const result = new TOC()
const allTags: Record<string, string> = {}
for await (const line of file.readLines()) {
if (strict && line.length > TOC_LINE_MAX_LENGTH) {
throw new Error('Line is longer than TOC_LINE_MAX_LENGTH characters, WoW will truncate!');
throw new Error('Line is longer than TOC_LINE_MAX_LENGTH characters, WoW will truncate!')
}
if (line.slice(0, TOC_PREFIX_TAG.length) === TOC_PREFIX_TAG) {
if (line.startsWith(TOC_PREFIX_TAG)) {
const tagData = line.slice(TOC_PREFIX_TAG.length).split(TOC_TAG_DELIMITER)
if(tagData.length !== 2){
if(strict){
throw new Error(`Tag could not be parsed: ${tagData}`)
if (tagData.length !== 2) {
if (strict) {
throw new Error(`Tag could not be parsed: ${tagData.join(',')}`)
} else {
continue
}
@ -93,21 +93,21 @@ export const parse = async function(file: FileHandle, strict = true) {
const tagName = tagData[0].trim()
const tagValue = tagData[1].trim()
if(strict && allTags[tagName]){
if (strict && allTags[tagName]) {
throw new Error(`Duplicate value: ${tagName}`)
}
allTags[tagName] = tagValue;
allTags[tagName] = tagValue
switch(tagName.toLowerCase()){
switch (tagName.toLowerCase()) {
case TOC_TAG_NAME_INTERFACE.toLowerCase():
result.Interface = Number(tagValue)
break;
break
case TOC_TAG_NAME_TITLE.toLowerCase():
result.Title = tagValue
break;
result.Title = tagValue
break
case TOC_TAG_NAME_NOTES.toLocaleLowerCase():
result.Notes = tagValue
result.Notes = tagValue
break
case TOC_TAG_NAME_ICONTEXTURE.toLowerCase():
result.IconTexture = tagValue
@ -143,7 +143,9 @@ export const parse = async function(file: FileHandle, strict = true) {
result.SavedVariables = tagValue.split(TOC_TAG_LIST_DELIMITER).map((s) => s.trim())
break
case TOC_TAG_NAME_SAVEDVARIABESPERCHARACTER.toLowerCase():
result.SavedVariablesPerCharacter = tagValue.split(TOC_TAG_LIST_DELIMITER).map((s) => s.trim())
result.SavedVariablesPerCharacter = tagValue
.split(TOC_TAG_LIST_DELIMITER)
.map((s) => s.trim())
break
case TOC_TAG_NAME_AUTHOR.toLowerCase():
result.Author = tagValue
@ -152,31 +154,43 @@ export const parse = async function(file: FileHandle, strict = true) {
result.Version = tagValue
break
default:
if(tagName.toLowerCase().startsWith(TOC_TAG_PREFIX_TITLELOCALIZED) && tagName.length === TOC_TAG_PREFIX_TITLELOCALIZED.length+4){
const locale = tagName.slice(TOC_TAG_PREFIX_TITLELOCALIZED.length,TOC_TAG_PREFIX_TITLELOCALIZED.length+4)
if(Object.keys(WOW_LOCALES).includes(locale)){
if (
tagName.toLowerCase().startsWith(TOC_TAG_PREFIX_TITLELOCALIZED) &&
tagName.length === TOC_TAG_PREFIX_TITLELOCALIZED.length + 4
) {
const locale = tagName.slice(
TOC_TAG_PREFIX_TITLELOCALIZED.length,
TOC_TAG_PREFIX_TITLELOCALIZED.length + 4,
)
if (Object.keys(WOW_LOCALES).includes(locale)) {
result.TitleLocalized[locale as WOW_LOCALES] = tagValue
} else if(strict){
} else if (strict) {
throw new Error(`Locale not found: ${locale}`)
}
} else if(tagName.toLowerCase().startsWith(TOC_TAG_PREFIX_NOTESLOCALIZED) && tagName.length === TOC_TAG_PREFIX_NOTESLOCALIZED.length+4){
const locale = tagName.slice(TOC_TAG_PREFIX_NOTESLOCALIZED.length,TOC_TAG_PREFIX_NOTESLOCALIZED.length+4)
if(Object.keys(WOW_LOCALES).includes(locale)){
} else if (
tagName.toLowerCase().startsWith(TOC_TAG_PREFIX_NOTESLOCALIZED) &&
tagName.length === TOC_TAG_PREFIX_NOTESLOCALIZED.length + 4
) {
const locale = tagName.slice(
TOC_TAG_PREFIX_NOTESLOCALIZED.length,
TOC_TAG_PREFIX_NOTESLOCALIZED.length + 4,
)
if (Object.keys(WOW_LOCALES).includes(locale)) {
result.NotesLocalized[locale as WOW_LOCALES] = tagValue
} else if(strict){
} else if (strict) {
throw new Error(`Locale not found: ${locale}`)
}
} else if(tagName.toLowerCase().startsWith(TOC_TAG_PREFIX_DEP)){
} else if (tagName.toLowerCase().startsWith(TOC_TAG_PREFIX_DEP)) {
result.Dependencies = tagValue.split(TOC_TAG_LIST_DELIMITER).map((s) => s.trim())
} else if(tagName.toLowerCase().startsWith(TOC_TAG_PREFIX_X)){
} else if (tagName.toLowerCase().startsWith(TOC_TAG_PREFIX_X)) {
result.xTags[tagName.slice(TOC_TAG_PREFIX_X.length)] = tagValue
} else if(strict){
} else if (strict) {
throw new Error(`Unknown Tag name: ${tagName}`)
}
}
} else if (line.slice(0, 1) !== TOC_PREFIX_COMMENT && line.trim()) {
result.files.push(line.trimEnd());
}
} else if (!line.startsWith(TOC_PREFIX_COMMENT) && line.trim()) {
result.files.push(line.trimEnd())
}
}
return result;
};
return result
}

View File

@ -28,7 +28,7 @@
"module": "commonjs", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
"baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
@ -105,5 +105,5 @@
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
},
}