├── lerna.json
├── .gitignore
├── .prettierrc
├── packages
├── scripts
│ ├── tsconfig.json
│ ├── test
│ │ ├── helpers
│ │ │ ├── other components
│ │ │ │ ├── nested
│ │ │ │ │ └── Test2.vue
│ │ │ │ └── Test.vue
│ │ │ ├── alias
│ │ │ │ ├── no-nested-aliases.config.js
│ │ │ │ ├── other-nested-aliases.config.js
│ │ │ │ └── nested-aliases.config.js
│ │ │ ├── out
│ │ │ │ ├── tags.json
│ │ │ │ └── attributes.json
│ │ │ └── BlitzForm.vue
│ │ ├── aliasParser.test.ts
│ │ ├── vueFilePathToVeturJsonData.test.ts
│ │ └── test.test.ts
│ ├── package.json
│ └── src
│ │ ├── vueDocgenToVetur.ts
│ │ ├── listFiles.ts
│ │ ├── aliasUtils.ts
│ │ └── index.ts
└── vue-intellisense
│ ├── package.json
│ ├── README.md
│ └── cli.js
├── tsconfig.json
├── .github
└── FUNDING.yml
├── LICENSE
├── package.json
├── .vscode
└── settings.json
└── README.md
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "packages": [
3 | "packages/*"
4 | ],
5 | "version": "1.0.3"
6 | }
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .thumbs.db
3 | node_modules
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | dist
8 |
9 | # Editor directories and files
10 | .idea
11 | *.suo
12 | *.ntvs*
13 | *.njsproj
14 | *.sln
15 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "tabWidth": 2,
4 | "singleQuote": true,
5 | "trailingComma": "es5",
6 | "semi": false,
7 | "bracketSpacing": true,
8 | "arrowParens": "always",
9 | "vueIndentScriptAndStyle": false
10 | }
11 |
--------------------------------------------------------------------------------
/packages/scripts/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "allowSyntheticDefaultImports": true,
5 | "baseUrl": ".",
6 | "declaration": true,
7 | "declarationDir": "./dist/types/"
8 | },
9 | "include": ["src/**/*", "test/**/*"]
10 | }
11 |
--------------------------------------------------------------------------------
/packages/scripts/test/helpers/other components/nested/Test2.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
18 |
--------------------------------------------------------------------------------
/packages/scripts/test/helpers/other components/Test.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
20 |
--------------------------------------------------------------------------------
/packages/scripts/test/helpers/alias/no-nested-aliases.config.js:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 | import fs from 'fs'
3 |
4 | const aliases = {
5 | '@': '.',
6 | '@src': './src',
7 | '@components': './src/components',
8 | }
9 |
10 | module.exports = {}
11 |
12 | for (const alias in aliases) {
13 | const aliasTo = aliases[alias]
14 | module.exports[alias] = resolveSrc(aliasTo)
15 | }
16 |
17 | function resolveSrc(_path) {
18 | return path.resolve(__dirname, _path)
19 | }
20 |
--------------------------------------------------------------------------------
/packages/scripts/test/helpers/alias/other-nested-aliases.config.js:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 | import fs from 'fs'
3 |
4 | const aliases = {
5 | '@': '.',
6 | '@src': './src',
7 | '@models': './src/models',
8 | }
9 |
10 | module.exports = {
11 | config: {
12 | alias: {},
13 | },
14 | }
15 |
16 | for (const alias in aliases) {
17 | const aliasTo = aliases[alias]
18 | module.exports.config.alias[alias] = resolveSrc(aliasTo)
19 | }
20 |
21 | function resolveSrc(_path) {
22 | return path.resolve(__dirname, _path)
23 | }
24 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowSyntheticDefaultImports": true,
4 | "allowJs": true,
5 | "esModuleInterop": true,
6 | "isolatedModules": true,
7 | "jsx": "preserve",
8 | "lib": ["esnext", "dom"],
9 | "module": "esnext",
10 | "moduleResolution": "node",
11 | "resolveJsonModule": true,
12 | "skipLibCheck": true,
13 | "sourceMap": true,
14 | "strict": true,
15 | "target": "esnext",
16 | "types": ["node"],
17 | "useDefineForClassFields": true
18 | },
19 | "exclude": ["dist", "node_modules"]
20 | }
21 |
--------------------------------------------------------------------------------
/packages/scripts/test/helpers/alias/nested-aliases.config.js:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 | import fs from 'fs'
3 |
4 | const aliases = {
5 | '@': '.',
6 | '@src': './src',
7 | '@components': './src/components',
8 | }
9 |
10 | module.exports = {
11 | webpack: {
12 | resole: {
13 | alias: {},
14 | },
15 | },
16 | }
17 |
18 | for (const alias in aliases) {
19 | const aliasTo = aliases[alias]
20 | module.exports.webpack.resole.alias[alias] = resolveSrc(aliasTo)
21 | }
22 |
23 | function resolveSrc(_path) {
24 | return path.resolve(__dirname, _path)
25 | }
26 |
--------------------------------------------------------------------------------
/packages/scripts/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vue-intellisense/scripts",
3 | "version": "1.0.3",
4 | "type": "module",
5 | "main": "dist/index.esm.js",
6 | "module": "dist/index.esm.js",
7 | "types": "dist/types/index.d.ts",
8 | "scripts": {
9 | "test": "rimraf test/helpers/out && vitest --run"
10 | },
11 | "engines": {
12 | "node": ">=14.19"
13 | },
14 | "dependencies": {
15 | "case-anything": "^2.1.10",
16 | "chalk": "^5.0.0",
17 | "is-what": "^4.1.7",
18 | "log-symbols": "^5.1.0",
19 | "merge-anything": "^5.0.2",
20 | "path-to-prop": "^2.0.2",
21 | "vue-docgen-api": "^4.44.15"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: mesqueeb
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 CyCraft
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/vue-intellisense/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-intellisense",
3 | "version": "1.0.3",
4 | "type": "module",
5 | "description": "A CLI tool to help enabling IntelliSense on your Vue components",
6 | "author": "Luca Ban - Mesqueeb",
7 | "scripts": {},
8 | "keywords": [
9 | "vue-intellisense",
10 | "vue-autocomplete",
11 | "vue-styleguidist",
12 | "vetur",
13 | "node",
14 | "cli"
15 | ],
16 | "license": "MIT",
17 | "homepage": "https://github.com/CyCraft/vue-intellisense#readme",
18 | "repository": {
19 | "type": "git",
20 | "url": "git+https://github.com/CyCraft/vue-intellisense.git"
21 | },
22 | "bugs": {
23 | "url": "https://github.com/CyCraft/vue-intellisense/issues"
24 | },
25 | "gitHead": "7a52c73677fc5550368eb358a9775aea3fc69e0e",
26 | "dependencies": {
27 | "@vue-intellisense/scripts": "^1.0.3",
28 | "case-anything": "^2.1.10",
29 | "chalk": "^5.0.0",
30 | "is-what": "^4.1.7",
31 | "log-symbols": "^5.1.0",
32 | "meow": "^10.1.2",
33 | "ora": "^6.0.1",
34 | "vue-docgen-api": "^4.44.15"
35 | },
36 | "bin": {
37 | "vue-int": "cli.js"
38 | },
39 | "engines": {
40 | "node": ">=14.19"
41 | },
42 | "files": [
43 | "cli.js"
44 | ]
45 | }
46 |
--------------------------------------------------------------------------------
/packages/scripts/test/helpers/out/tags.json:
--------------------------------------------------------------------------------
1 | {
2 | "blitz-form": {
3 | "attributes": [
4 | "value",
5 | "id",
6 | "schema",
7 | "actionButtons",
8 | "actionButtonDefaults",
9 | "actionButtonsPosition",
10 | "validator",
11 | "columnCount",
12 | "gridGap",
13 | "lang",
14 | "mode",
15 | "labelPosition",
16 | "evaluatedProps",
17 | "internalLabels",
18 | "internalErrors",
19 | "internalErrorsFor"
20 | ],
21 | "description": "A BlitzForm"
22 | },
23 | "BlitzForm": {
24 | "attributes": [
25 | "value",
26 | "id",
27 | "schema",
28 | "actionButtons",
29 | "actionButtonDefaults",
30 | "actionButtonsPosition",
31 | "validator",
32 | "columnCount",
33 | "gridGap",
34 | "lang",
35 | "mode",
36 | "labelPosition",
37 | "evaluatedProps",
38 | "internalLabels",
39 | "internalErrors",
40 | "internalErrorsFor"
41 | ],
42 | "description": "A BlitzForm"
43 | },
44 | "test": {
45 | "attributes": [
46 | "value"
47 | ],
48 | "description": "A Test"
49 | },
50 | "Test": {
51 | "attributes": [
52 | "value"
53 | ],
54 | "description": "A Test"
55 | },
56 | "test-2": {
57 | "attributes": [
58 | "value"
59 | ],
60 | "description": "A Test"
61 | },
62 | "Test2": {
63 | "attributes": [
64 | "value"
65 | ],
66 | "description": "A Test"
67 | }
68 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-intellisense",
3 | "private": true,
4 | "workspaces": [
5 | "packages/*"
6 | ],
7 | "author": "Luca Ban - Mesqueeb",
8 | "funding": "https://github.com/sponsors/mesqueeb",
9 | "scripts": {
10 | "audit": "lerna-audit",
11 | "test": "lerna run test",
12 | "build": "rollup -c ./build/buildScripts.js",
13 | "build-and-commit": "npm run build && git add -A && git commit -m \"chore: build\"",
14 | "copy:readme": "copyfiles 'README.md' packages/vue-intellisense",
15 | "dep:update-all": "ncu -u && lerna exec 'ncu -u' && npm i",
16 | "dep:check-for-updates": "ncu --target minor && lerna exec 'ncu --target minor'",
17 | "dep:reinstall-all": "rimraf node_modules && lerna clean -y && npm i",
18 | "test:scripts": "cd packages/scripts && npm run test",
19 | "publish": "npm run copy:readme && npm run build-and-commit && lerna publish"
20 | },
21 | "devDependencies": {
22 | "@types/fs-extra": "^9.0.13",
23 | "copyfiles": "^2.4.1",
24 | "lerna": "^4.0.0",
25 | "lerna-audit": "^1.3.3",
26 | "npm-check-updates": "^12.2.1",
27 | "rimraf": "^3.0.2",
28 | "rollup": "^2.67.0",
29 | "rollup-plugin-typescript2": "^0.31.2",
30 | "typescript": "^4.5.5",
31 | "vitest": "^0.2.7"
32 | },
33 | "license": "MIT",
34 | "homepage": "https://github.com/cycraft/vue-intellisense#readme",
35 | "bugs": {
36 | "url": "https://github.com/cycraft/vue-intellisense/issues"
37 | },
38 | "repository": {
39 | "type": "git",
40 | "url": "git+https://github.com/cycraft/vue-intellisense.git"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true,
3 | "editor.defaultFormatter": "esbenp.prettier-vscode",
4 | "[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
5 | "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
6 | "[vue]": { "editor.defaultFormatter": "octref.vetur" },
7 | "[sass]": { "editor.defaultFormatter": "syler.sass-indented" },
8 | // Lerna monorepo related settings
9 | "eslint.workingDirectories": [{ "pattern": "./packages/*/" }],
10 |
11 | // make sure vetur is enabled
12 | "eslint.validate": ["javascript", "javascriptreact", "typescript", "vue"],
13 | "vetur.experimental.templateInterpolationService": false,
14 | "vetur.format.enable": true,
15 | "vetur.validation.style": true,
16 | "vetur.validation.template": true,
17 | "vetur.validation.script": true,
18 | // disable conflicting formatting
19 | "javascript.validate.enable": true,
20 | "typescript.format.enable": true,
21 | "standard.enable": false,
22 | // global important defaults
23 | "editor.tabSize": 2, // make sure this is the same as .prettierrc
24 | "editor.insertSpaces": true,
25 | "files.trimFinalNewlines": true,
26 | "files.insertFinalNewline": true,
27 | "files.trimTrailingWhitespace": true,
28 | // template/html
29 | "vetur.format.defaultFormatter.html": "prettier",
30 | // script
31 | "vetur.format.defaultFormatter.ts": "prettier",
32 | "vetur.format.defaultFormatter.js": "prettier",
33 | // sass
34 | "vetur.format.defaultFormatter.sass": "sass-formatter",
35 | "sass.format.debug": false,
36 | "sass.format.deleteEmptyRows": true,
37 | "sass.format.deleteWhitespace": true,
38 | "sass.format.convert": true,
39 | "sass.format.setPropertySpace": true,
40 | // typescript
41 | "typescript.tsdk": "node_modules/typescript/lib"
42 | }
43 |
--------------------------------------------------------------------------------
/packages/scripts/src/vueDocgenToVetur.ts:
--------------------------------------------------------------------------------
1 | import { kebabCase, pascalCase } from 'case-anything'
2 | import { isFullString } from 'is-what'
3 |
4 | export function vueDocgenToVetur(
5 | vueDocgen: Record,
6 | veturFile: 'attributes' | 'tags'
7 | ): Record {
8 | const componentName = vueDocgen.displayName
9 | if (!isFullString(componentName)) {
10 | throw new Error('[vue-intellisense] Component is missing a "name" property.')
11 | }
12 | const componentNameKebab = kebabCase(componentName)
13 | const componentNamePascal = pascalCase(componentName)
14 | if (veturFile === 'attributes') {
15 | const props = vueDocgen.props || []
16 | return props.reduce((carry: any, vueDocgenProp: any) => {
17 | const { name, description, type: _type, values = [], tags: customTags = {} } = vueDocgenProp
18 | const attributeName = `${componentNameKebab}/${name}`
19 | const attributePascal = `${componentNamePascal}/${name}`
20 | const t = _type?.name || ''
21 | const type = t.endsWith('[]') ? 'array' : t.replace('func', 'function')
22 |
23 | // get the "options" from string literals
24 | const _typeTags = customTags.type || []
25 | const typeTags = _typeTags.map((t: any) => t.type.name)
26 | const valuesCalculated = values.length
27 | ? values
28 | : typeTags.length
29 | ? typeTags[0]
30 | .split('|')
31 | .map((t: string) => t.trim())
32 | .filter((t: string) => t[0] === `'` && t[t.length - 1] === `'`)
33 | .map((t: string) => t.slice(1, -1))
34 | : []
35 | const options = valuesCalculated.length ? { options: valuesCalculated } : {}
36 |
37 | return {
38 | ...carry,
39 | [attributeName]: { type, description, ...options },
40 | [attributePascal]: { type, description, ...options },
41 | }
42 | }, {})
43 | }
44 | if (veturFile === 'tags') {
45 | const props = vueDocgen.props || []
46 | const attributes = props.map(({ name }: any) => name)
47 | return {
48 | [componentNameKebab]: { attributes, description: vueDocgen.description || '' },
49 | [componentNamePascal]: { attributes, description: vueDocgen.description || '' },
50 | }
51 | }
52 | throw new Error('[vue-intellisense] wrong args')
53 | }
54 |
--------------------------------------------------------------------------------
/packages/scripts/src/listFiles.ts:
--------------------------------------------------------------------------------
1 | import { resolve } from 'path'
2 | import { readdir } from 'fs/promises'
3 |
4 | async function listFilesNonRecursive(folderPath: string): Promise {
5 | const dirents = await readdir(folderPath, { withFileTypes: true })
6 | const files = await Promise.all(
7 | dirents.flatMap((dirent: any) => {
8 | const res = resolve(folderPath, dirent.name)
9 | return dirent.isDirectory() ? [] : res
10 | })
11 | )
12 | const allFiles = Array.prototype.concat(...files)
13 | return allFiles
14 | }
15 |
16 | async function listFilesRecursively(folderPath: string): Promise {
17 | const dirents = await readdir(folderPath, { withFileTypes: true })
18 | const files = await Promise.all(
19 | dirents.map((dirent) => {
20 | const res = resolve(folderPath, dirent.name)
21 | return dirent.isDirectory() ? listFilesRecursively(res) : [res]
22 | }) as Promise[]
23 | )
24 | const allFiles = Array.prototype.concat(...files)
25 | return allFiles
26 | }
27 |
28 | /**
29 | * @param {string} folderPath "resources/foo/goo"
30 | * @param {{
31 | * regexFilter?: RegExp,
32 | * resolvePaths?: boolean,
33 | * recursive?: boolean,
34 | * }} options
35 | * regexFilter: RegExp - eg. /\.txt/ for only .txt files
36 | *
37 | * resolvePaths: boolean - when true it will return the _full_ path from the file system's root. If false (default) it will return the relativePath based on the initial directory path passed
38 | *
39 | * recursive: boolean - when true it will return ALL paths recursively. If false (default) it will only return the paths from the folder
40 | * @return {Promise}
41 | */
42 | export async function listFiles(
43 | folderPath: string,
44 | options?: {
45 | regexFilter?: RegExp
46 | resolvePaths?: boolean
47 | recursive?: boolean
48 | }
49 | ): Promise {
50 | const { regexFilter, resolvePaths, recursive } = options || {}
51 | if (folderPath.endsWith('/')) folderPath = folderPath.slice(0, -1)
52 | const parentDirFullPath = resolve(folderPath).split(folderPath)[0]
53 | let allFiles = recursive
54 | ? await listFilesRecursively(folderPath)
55 | : await listFilesNonRecursive(folderPath)
56 | if (regexFilter === undefined && !resolvePaths) return allFiles
57 | return allFiles.flatMap((filePath) => {
58 | if (!resolvePaths) filePath = filePath.replace(parentDirFullPath, '')
59 | if (regexFilter === undefined) return filePath
60 | return filePath.match(regexFilter) ? filePath : []
61 | })
62 | }
63 |
--------------------------------------------------------------------------------
/packages/scripts/src/aliasUtils.ts:
--------------------------------------------------------------------------------
1 | import logSymbols from 'log-symbols'
2 | import chalk from 'chalk'
3 | import { isPlainObject } from 'is-what'
4 | import { merge } from 'merge-anything'
5 | import { getProp } from 'path-to-prop'
6 | import * as fs from 'fs'
7 | import * as path from 'path'
8 |
9 | /**
10 | * extract alias file config absolute path and nested property by dot
11 | * @param {string} alias
12 | */
13 | function extractAliasPath(alias: string) {
14 | const [configFilePath, ...aliasNested] = alias.replace(/^#|#$/g, '').split('#')
15 | const aliasAbsolutePath = path.isAbsolute(configFilePath)
16 | ? configFilePath
17 | : path.resolve(process.cwd(), configFilePath)
18 | if (!fs.existsSync(aliasAbsolutePath)) {
19 | throw new Error(`[vue-intellisense] ${aliasAbsolutePath} is not found`)
20 | }
21 | // not nested alias
22 | if (aliasNested.length === 0) {
23 | return { aliasAbsolutePath, nestedPropsByDot: '' }
24 | }
25 | // example: resolve.alias
26 | const nestedPropsByDot = aliasNested.join('.')
27 | return { aliasAbsolutePath, nestedPropsByDot }
28 | }
29 |
30 | /**
31 | *
32 | * @param aliasAbsolutePath
33 | * @param nestedPropsByDot like: resolve.alias
34 | * @returns
35 | */
36 | async function getAliasFromFilePath(aliasAbsolutePath: string, nestedPropsByDot: string) {
37 | const configFile = await import(aliasAbsolutePath)
38 | if (!nestedPropsByDot) return configFile
39 | return getProp(configFile, nestedPropsByDot) || null
40 | }
41 |
42 | async function readAndParseAlias(rawAliases: string[]) {
43 | let parsedAliase: Record = {}
44 | // contain merged aliase of all file config
45 | for (const rawAlias of rawAliases) {
46 | const { aliasAbsolutePath, nestedPropsByDot } = extractAliasPath(rawAlias)
47 |
48 | const extractedAliasObj = await getAliasFromFilePath(aliasAbsolutePath, nestedPropsByDot)
49 | if (!extractedAliasObj) {
50 | throw new Error(`[vue-intellisense] ${rawAlias} is not contain alias config object`)
51 | }
52 | if (isPlainObject(extractedAliasObj)) parsedAliase = merge(parsedAliase, extractedAliasObj)
53 | }
54 | return parsedAliase
55 | }
56 | /**
57 | * Make console.warn throw, so that we can check warning aliase config not correct
58 | */
59 | function handleWarningMissingAlias() {
60 | const warn = console.warn
61 | console.warn = function (message: string, ...args: any[]) {
62 | warn.apply(console, args)
63 | if (['Neither', 'nor', 'or', 'could be found in'].every((msg) => message.includes(msg))) {
64 | console.log(
65 | `${logSymbols.error} ${chalk.bold(
66 | '[vue-intellisense] Your aliases config is missing or wrong'
67 | )}!`
68 | )
69 | }
70 | }
71 | }
72 | export { handleWarningMissingAlias, readAndParseAlias }
73 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Vue IntelliSense
2 |
3 |
4 |
5 |
6 | > A CLI tool to help enabling IntelliSense on your Vue components
7 |
8 | ```shell
9 | npm i -D vue-intellisense
10 |
11 | # or globally
12 | npm i -g vue-intellisense
13 | ```
14 |
15 | ## Usage
16 |
17 | You'll need VSCode and Vetur installed.
18 |
19 | Vetur has a feature to get IntelliSense for your Vue components, however, you need to provide a bunch of config files for this.
20 |
21 | The Vue IntelliSense CLI generates the required files with zero-config required!
22 |
23 | ### TLDR;
24 |
25 | Generate the required Vetur files for all your Vue components:
26 |
27 | ```
28 | vue-int --input /src/components --output vetur --recursive
29 | ```
30 |
31 | Then add this to your package.json:
32 |
33 | ```json
34 | {
35 | "vetur": {
36 | "tags": "vetur/tags.json",
37 | "attributes": "vetur/attributes.json"
38 | }
39 | }
40 | ```
41 |
42 | That's it! 🎉
43 |
44 | ### Motivation
45 |
46 | Check out the [blog post](https://medium.com/@lucaban/vue-intellisense-in-vscode-33cf8860e092)!
47 |
48 | ### CLI Manual
49 |
50 | ```
51 | Usage
52 | $ vue-int --input --output
53 |
54 | Options
55 | --output, -o A folder to save the generated files.
56 | --input, -i Either a Vue file, or a folder with vue components. In case it's a folder, it will not care about nested folders.
57 | --recursive, -r consider all vue files in all nested folders as well.
58 | --alias, -a List files contain aliases config.
59 |
60 | Examples
61 | # target a specific Vue file to generate IntelliSense for
62 | $ vue-int --output 'vetur' --input 'src/components/MyButton.vue'
63 |
64 | # target all files in a folder - without nested folders
65 | $ vue-int --output 'vetur' --input 'src/components'
66 |
67 | # target all files in a folder - with nested folders
68 | $ vue-int --output 'vetur' --input 'src/components' --recursive
69 |
70 | # target all files in a folder - with nested folders and and using alias for import
71 | $ vue-int --output 'vetur' --input 'src/components' --recursive --alias alias.config.js other-alias.config.js
72 |
73 | # support nested object inside config file like: { resolve: { alias: { "@components": "/src/components" } } }
74 | $ vue-int --output 'vetur' --input 'src/components' --recursive --alias webpack.config.js#resolve#alias other-alias.config.js
75 |
76 | Exits with code 0 when done or with 1 when an error has occured.
77 | ```
78 |
79 | ### Contributing
80 |
81 | Any contribution welcome! Would love for this to work with other code editors as well!
82 |
--------------------------------------------------------------------------------
/packages/vue-intellisense/README.md:
--------------------------------------------------------------------------------
1 | # Vue IntelliSense
2 |
3 |
4 |
5 |
6 | > A CLI tool to help enabling IntelliSense on your Vue components
7 |
8 | ```shell
9 | npm i -D vue-intellisense
10 |
11 | # or globally
12 | npm i -g vue-intellisense
13 | ```
14 |
15 | ## Usage
16 |
17 | You'll need VSCode and Vetur installed.
18 |
19 | Vetur has a feature to get IntelliSense for your Vue components, however, you need to provide a bunch of config files for this.
20 |
21 | The Vue IntelliSense CLI generates the required files with zero-config required!
22 |
23 | ### TLDR;
24 |
25 | Generate the required Vetur files for all your Vue components:
26 |
27 | ```
28 | vue-int --input /src/components --output vetur --recursive
29 | ```
30 |
31 | Then add this to your package.json:
32 |
33 | ```json
34 | {
35 | "vetur": {
36 | "tags": "vetur/tags.json",
37 | "attributes": "vetur/attributes.json"
38 | }
39 | }
40 | ```
41 |
42 | That's it! 🎉
43 |
44 | ### Motivation
45 |
46 | Check out the [blog post](https://medium.com/@lucaban/vue-intellisense-in-vscode-33cf8860e092)!
47 |
48 | ### CLI Manual
49 |
50 | ```
51 | Usage
52 | $ vue-int --input --output
53 |
54 | Options
55 | --output, -o A folder to save the generated files.
56 | --input, -i Either a Vue file, or a folder with vue components. In case it's a folder, it will not care about nested folders.
57 | --recursive, -r consider all vue files in all nested folders as well.
58 | --alias, -a List files contain aliases config.
59 |
60 | Examples
61 | # target a specific Vue file to generate IntelliSense for
62 | $ vue-int --output 'vetur' --input 'src/components/MyButton.vue'
63 |
64 | # target all files in a folder - without nested folders
65 | $ vue-int --output 'vetur' --input 'src/components'
66 |
67 | # target all files in a folder - with nested folders
68 | $ vue-int --output 'vetur' --input 'src/components' --recursive
69 |
70 | # target all files in a folder - with nested folders and and using alias for import
71 | $ vue-int --output 'vetur' --input 'src/components' --recursive --alias alias.config.js other-alias.config.js
72 |
73 | # support nested object inside config file like: { resolve: { alias: { "@components": "/src/components" } } }
74 | $ vue-int --output 'vetur' --input 'src/components' --recursive --alias webpack.config.js#resolve#alias other-alias.config.js
75 |
76 | Exits with code 0 when done or with 1 when an error has occured.
77 | ```
78 |
79 | ### Contributing
80 |
81 | Any contribution welcome! Would love for this to work with other code editors as well!
82 |
--------------------------------------------------------------------------------
/packages/scripts/src/index.ts:
--------------------------------------------------------------------------------
1 | import { isFullArray, isPlainObject } from 'is-what'
2 | import { merge } from 'merge-anything'
3 | import { parse, DocGenOptions } from 'vue-docgen-api'
4 | import { readAndParseAlias, handleWarningMissingAlias } from './aliasUtils'
5 | import { listFiles } from './listFiles'
6 | import { vueDocgenToVetur } from './vueDocgenToVetur'
7 | import * as fs from 'fs'
8 |
9 | handleWarningMissingAlias()
10 |
11 | export async function vueFilePathToVeturJsonData(
12 | vueFilePath: string,
13 | veturFile: 'attributes' | 'tags',
14 | options: { alias?: { [alias in string]: string }; [key: string]: any } = {}
15 | ): Promise> {
16 | const { alias } = options
17 | const docGenOptions: DocGenOptions | undefined = isPlainObject(alias) ? { alias } : undefined
18 | const vueDocgen = await parse(vueFilePath, docGenOptions)
19 | if (!isPlainObject(vueDocgen)) return {}
20 | const jsonData = vueDocgenToVetur(vueDocgen, veturFile)
21 | return jsonData
22 | }
23 |
24 | async function vueFilePathsToVeturJsonData(
25 | inputPaths: string[],
26 | veturFile: 'attributes' | 'tags',
27 | options?: { alias?: { [alias in string]: string }; [key: string]: any }
28 | ): Promise> {
29 | const objects = await Promise.all(
30 | inputPaths.map((path) => vueFilePathToVeturJsonData(path, veturFile, options))
31 | )
32 | if (!objects.length) throw '[vue-intellisense] missing '
33 | return merge(objects[0], ...objects.slice(1))
34 | }
35 |
36 | async function writeVeturFiles(
37 | outputPath: string,
38 | attributes: Record,
39 | tags: Record
40 | ): Promise {
41 | const _out = outputPath.endsWith('/') ? outputPath : outputPath + '/'
42 | fs.mkdirSync(_out, { recursive: true })
43 | fs.writeFileSync(_out + 'attributes.json', JSON.stringify(attributes, undefined, 2))
44 | fs.writeFileSync(_out + 'tags.json', JSON.stringify(tags, undefined, 2))
45 | }
46 |
47 | export async function generateVeturFiles(
48 | inputPath: string,
49 | outputPath: string,
50 | options?: { recursive?: boolean; alias?: { [alias in string]: string } }
51 | ): Promise {
52 | const { recursive, alias } = options || {}
53 | const inputIsFile = ['.vue', '.jsx', '.tsx'].some((fileType) => inputPath.endsWith(fileType))
54 | const allFiles = inputIsFile
55 | ? [inputPath]
56 | : await listFiles(inputPath, {
57 | regexFilter: /\.vue$|\.jsx$|\.tsx$/,
58 | recursive,
59 | resolvePaths: true,
60 | })
61 | let parsedAliase = alias
62 | if (isFullArray(alias)) parsedAliase = await readAndParseAlias(alias)
63 | const attributes = await vueFilePathsToVeturJsonData(allFiles, 'attributes', {
64 | ...options,
65 | alias: parsedAliase,
66 | })
67 | const tags = await vueFilePathsToVeturJsonData(allFiles, 'tags', {
68 | ...options,
69 | alias: parsedAliase,
70 | })
71 | await writeVeturFiles(outputPath, attributes, tags)
72 | }
73 |
--------------------------------------------------------------------------------
/packages/vue-intellisense/cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import meow from 'meow'
3 | import logSymbols from 'log-symbols'
4 | import chalk from 'chalk'
5 | import ora from 'ora'
6 | import * as IsWhat from 'is-what'
7 | const { isFullString } = IsWhat
8 | import * as VueIntellisenseScripts from '@vue-intellisense/scripts'
9 | const { generateVeturFiles } = VueIntellisenseScripts
10 |
11 | const cli = meow(
12 | `
13 | Usage
14 | $ vue-int --input --output
15 |
16 | Options
17 | --output, -o A folder to save the generated files.
18 | --input, -i Either a Vue file, or a folder with vue components. In case it's a folder, it will not care about nested folders.
19 | --recursive, -r consider all vue files in all nested folders as well.
20 | --alias, -a List files contain aliases config.
21 |
22 | Examples
23 | # target a specific Vue file to generate IntelliSense for
24 | $ vue-int --output 'vetur' --input 'src/components/MyButton.vue'
25 |
26 | # target all files in a folder - without nested folders
27 | $ vue-int --output 'vetur' --input 'src/components'
28 |
29 | # target all files in a folder - with nested folders
30 | $ vue-int --output 'vetur' --input 'src/components' --recursive
31 |
32 | # target all files in a folder - with nested folders and and using alias for import
33 | $ vue-int --output 'vetur' --input 'src/components' --recursive --alias alias.config.js other-alias.config.js
34 |
35 | # support nested object inside config file like: { resolve: { alias: { "@components": "/src/components" } } }
36 | $ vue-int --output 'vetur' --input 'src/components' --recursive --alias webpack.config.js#resolve#alias other-alias.config.js
37 |
38 | Exits with code 0 when done or with 1 when an error has occured.
39 | `,
40 | {
41 | importMeta: import.meta,
42 | flags: {
43 | input: {
44 | alias: 'i',
45 | type: 'string',
46 | isRequired: true,
47 | },
48 | output: {
49 | alias: 'o',
50 | type: 'string',
51 | isRequired: true,
52 | },
53 | recursive: {
54 | alias: 'r',
55 | type: 'boolean',
56 | default: false,
57 | },
58 | alias: {
59 | alias: 'a',
60 | isMultiple: true,
61 | type: 'string',
62 | },
63 | },
64 | }
65 | )
66 |
67 | const { flags } = cli
68 | const { input, output, recursive, alias } = flags
69 |
70 | if (!isFullString(input)) {
71 | console.error('Specify an input: --input ')
72 | process.exit(1)
73 | }
74 | if (!isFullString(output)) {
75 | console.error('Specify an output: --output ')
76 | process.exit(1)
77 | }
78 |
79 | const spinner = ora(`Generating files`).start()
80 | ;(async () => {
81 | await generateVeturFiles(input, output, { recursive, alias })
82 |
83 | spinner.stop()
84 |
85 | console.log(`${logSymbols.success} ${chalk.bold('done')}!`)
86 |
87 | process.exit(0)
88 | })().catch((error) => {
89 | spinner.stop()
90 |
91 | console.error(error)
92 |
93 | process.exit(1)
94 | })
95 |
--------------------------------------------------------------------------------
/packages/scripts/test/aliasParser.test.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from 'vitest'
2 | import { readAndParseAlias } from '../src/aliasUtils'
3 | import * as path from 'path'
4 |
5 | function resolveSrc(relativePath: string) {
6 | return path.resolve(process.cwd(), 'test/helpers/alias/' + relativePath)
7 | }
8 |
9 | function getExpectValueForMapAliases(mapAliases: Record) {
10 | const expectValue: Record = {}
11 | for (const alias in mapAliases) {
12 | const aliasTo = mapAliases[alias]
13 | expectValue[alias] = resolveSrc(aliasTo)
14 | }
15 | return expectValue
16 | }
17 |
18 | // test('correctly get aliases in non-nested config file', async () => {
19 | // const input = ['test/helpers/alias/no-nested-aliases.config.js']
20 | // const mapAliases: Record = {
21 | // '@': '.',
22 | // '@src': './src',
23 | // '@components': './src/components',
24 | // }
25 | // const expectValue = getExpectValueForMapAliases(mapAliases)
26 | // const parsedAliases = await readAndParseAlias(input)
27 | // expect(parsedAliases).toEqual(expectValue)
28 | // })
29 |
30 | test('should throw error for non-exist file', async () => {
31 | const input = ['test/helpers/alias/some-dummy-file.js']
32 | await expect(readAndParseAlias(input)).rejects.toMatchObject({
33 | message: /^(\[vue-intellisense\]).+(is not found)$/,
34 | })
35 | })
36 |
37 | test('correctly get aliases in nested config file with object path', async () => {
38 | const input = ['test/helpers/alias/nested-aliases.config.js#webpack#resole#alias']
39 | const mapAliases: Record = {
40 | '@': '.',
41 | '@src': './src',
42 | '@components': './src/components',
43 | }
44 | const expectValue = getExpectValueForMapAliases(mapAliases)
45 | const parsedAliases = await readAndParseAlias(input)
46 | expect(parsedAliases).toEqual(expectValue)
47 | })
48 |
49 | test('correctly get aliases in nested config file without object path', async () => {
50 | const input = ['test/helpers/alias/nested-aliases.config.js']
51 | const mapAliases: Record = {
52 | '@': '.',
53 | '@src': './src',
54 | '@components': './src/components',
55 | }
56 | const expectValue = getExpectValueForMapAliases(mapAliases)
57 | const parsedAliases = await readAndParseAlias(input)
58 | expect(parsedAliases).not.toEqual(expectValue)
59 | })
60 |
61 | test('throw not when wrong nested object path', () => {
62 | const input = [
63 | 'test/helpers/alias/no-nested-aliases.config.js',
64 | 'test/helpers/alias/other-nested-aliases.config.js#webpack#resolve#alias', // wrong webpack
65 | ]
66 | expect(readAndParseAlias(input)).rejects.toMatchObject({
67 | message: /^(\[vue-intellisense\]).+(is not contain alias config object)$/,
68 | })
69 | })
70 |
71 | // test('correctly merged aliases in multiple file with some object path and without object path', async () => {
72 | // const input = [
73 | // 'test/helpers/alias/no-nested-aliases.config.js',
74 | // 'test/helpers/alias/other-nested-aliases.config.js#config#alias',
75 | // ]
76 | // const mapAliases: Record = {
77 | // '@': '.',
78 | // '@src': './src',
79 | // '@components': './src/components',
80 | // '@models': './src/models',
81 | // }
82 | // const expectValue = getExpectValueForMapAliases(mapAliases)
83 | // const parsedAliases = await readAndParseAlias(input)
84 | // expect(parsedAliases).toEqual(expectValue)
85 | // })
86 |
--------------------------------------------------------------------------------
/packages/scripts/test/helpers/BlitzForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
192 |
--------------------------------------------------------------------------------
/packages/scripts/test/helpers/out/attributes.json:
--------------------------------------------------------------------------------
1 | {
2 | "blitz-form/value": {
3 | "type": "object",
4 | "description": "An object with the data of the entire form. The object keys are the ids of the fields passed in the 'schema'.\n\nTo be used with `:value` or `v-model`."
5 | },
6 | "BlitzForm/value": {
7 | "type": "object",
8 | "description": "An object with the data of the entire form. The object keys are the ids of the fields passed in the 'schema'.\n\nTo be used with `:value` or `v-model`."
9 | },
10 | "blitz-form/id": {
11 | "type": "string",
12 | "description": "A manually set 'id' of the BlitzForm. This prop is accessible in the `context` (as `formId`) of any \"evaluated prop\" and event.\n\nRead more on Evaluated Props in its dedicated page."
13 | },
14 | "BlitzForm/id": {
15 | "type": "string",
16 | "description": "A manually set 'id' of the BlitzForm. This prop is accessible in the `context` (as `formId`) of any \"evaluated prop\" and event.\n\nRead more on Evaluated Props in its dedicated page."
17 | },
18 | "blitz-form/schema": {
19 | "type": "array",
20 | "description": "This is the heart of your BlitzForm. It's the schema that will defined what fields will be generated."
21 | },
22 | "BlitzForm/schema": {
23 | "type": "array",
24 | "description": "This is the heart of your BlitzForm. It's the schema that will defined what fields will be generated."
25 | },
26 | "blitz-form/actionButtons": {
27 | "type": "array",
28 | "description": "Buttons on top of the form that control the 'mode' of the form. The possible pre-made buttons are:\n- 'edit' a button which puts the form in 'edit' mode & does `emit('edit')`\n- 'cancel' a button which puts the form in 'view' mode & does `emit('cancel')`\n- 'save' a button which puts the form in 'edit' mode & does `emit('save', {newData, oldData})`\n- 'delete' a red button which does `emit('delete')`\n- 'archive' a red button which does `emit('archive')`\n\nYou can also pass custom buttons with the same schema to generate forms.\n\nSee the documentation on \"Action Buttons\" for more info.",
29 | "options": [
30 | "cancel",
31 | "save",
32 | "delete",
33 | "archive"
34 | ]
35 | },
36 | "BlitzForm/actionButtons": {
37 | "type": "array",
38 | "description": "Buttons on top of the form that control the 'mode' of the form. The possible pre-made buttons are:\n- 'edit' a button which puts the form in 'edit' mode & does `emit('edit')`\n- 'cancel' a button which puts the form in 'view' mode & does `emit('cancel')`\n- 'save' a button which puts the form in 'edit' mode & does `emit('save', {newData, oldData})`\n- 'delete' a red button which does `emit('delete')`\n- 'archive' a red button which does `emit('archive')`\n\nYou can also pass custom buttons with the same schema to generate forms.\n\nSee the documentation on \"Action Buttons\" for more info.",
39 | "options": [
40 | "cancel",
41 | "save",
42 | "delete",
43 | "archive"
44 | ]
45 | },
46 | "blitz-form/actionButtonDefaults": {
47 | "type": "object",
48 | "description": "You can overwrite the schema used for the default action buttons for edit, cancel, save, delete & archive."
49 | },
50 | "BlitzForm/actionButtonDefaults": {
51 | "type": "object",
52 | "description": "You can overwrite the schema used for the default action buttons for edit, cancel, save, delete & archive."
53 | },
54 | "blitz-form/actionButtonsPosition": {
55 | "type": "string",
56 | "description": "The position of the action buttons.",
57 | "options": [
58 | "top",
59 | "bottom",
60 | "right",
61 | "left"
62 | ]
63 | },
64 | "BlitzForm/actionButtonsPosition": {
65 | "type": "string",
66 | "description": "The position of the action buttons.",
67 | "options": [
68 | "top",
69 | "bottom",
70 | "right",
71 | "left"
72 | ]
73 | },
74 | "blitz-form/validator": {
75 | "type": "function",
76 | "description": "A function which serves as global validator for your form. It will receive the edited data as first param and the original data (before user edits) as second. It should return true if all is OK or a string with error message."
77 | },
78 | "BlitzForm/validator": {
79 | "type": "function",
80 | "description": "A function which serves as global validator for your form. It will receive the edited data as first param and the original data (before user edits) as second. It should return true if all is OK or a string with error message."
81 | },
82 | "blitz-form/columnCount": {
83 | "type": "number",
84 | "description": "The amount of columns the form should have.\n\nEach field can set a specific 'span' to be able to span multiple columns."
85 | },
86 | "BlitzForm/columnCount": {
87 | "type": "number",
88 | "description": "The amount of columns the form should have.\n\nEach field can set a specific 'span' to be able to span multiple columns."
89 | },
90 | "blitz-form/gridGap": {
91 | "type": "string",
92 | "description": "The gap between each field in the form."
93 | },
94 | "BlitzForm/gridGap": {
95 | "type": "string",
96 | "description": "The gap between each field in the form."
97 | },
98 | "blitz-form/lang": {
99 | "type": "object",
100 | "description": "The text used in the UI, eg. edit/save buttons etc... Pass an object with keys: archive, delete, cancel, edit, save."
101 | },
102 | "BlitzForm/lang": {
103 | "type": "object",
104 | "description": "The text used in the UI, eg. edit/save buttons etc... Pass an object with keys: archive, delete, cancel, edit, save."
105 | },
106 | "blitz-form/mode": {
107 | "type": "string",
108 | "description": "The mode represents how fields are rendered\n- \"edit\" or \"add\" means they can be interacted with\n- \"view\" means they can't\n- \"raw\" means the fields are not generated, just the raw value inside a div\n\nThis prop can be set on a BlitzField or on a BlitzForm (in which case it's applied to all fields).",
109 | "options": [
110 | "edit",
111 | "add",
112 | "view",
113 | "raw"
114 | ]
115 | },
116 | "BlitzForm/mode": {
117 | "type": "string",
118 | "description": "The mode represents how fields are rendered\n- \"edit\" or \"add\" means they can be interacted with\n- \"view\" means they can't\n- \"raw\" means the fields are not generated, just the raw value inside a div\n\nThis prop can be set on a BlitzField or on a BlitzForm (in which case it's applied to all fields).",
119 | "options": [
120 | "edit",
121 | "add",
122 | "view",
123 | "raw"
124 | ]
125 | },
126 | "blitz-form/labelPosition": {
127 | "type": "string|function",
128 | "description": "The position of the label in comparison to the field.\n\nThis prop can be set on a BlitzField or on a BlitzForm (in which case it's applied to all fields).",
129 | "options": [
130 | "top",
131 | "left"
132 | ]
133 | },
134 | "BlitzForm/labelPosition": {
135 | "type": "string|function",
136 | "description": "The position of the label in comparison to the field.\n\nThis prop can be set on a BlitzField or on a BlitzForm (in which case it's applied to all fields).",
137 | "options": [
138 | "top",
139 | "left"
140 | ]
141 | },
142 | "blitz-form/evaluatedProps": {
143 | "type": "array",
144 | "description": "An array with prop names that should be treated as \"Evaluated Props\" when passed a function.\n\nThis prop can be set on a BlitzField or on a BlitzForm (in which case it's applied to all fields)."
145 | },
146 | "BlitzForm/evaluatedProps": {
147 | "type": "array",
148 | "description": "An array with prop names that should be treated as \"Evaluated Props\" when passed a function.\n\nThis prop can be set on a BlitzField or on a BlitzForm (in which case it's applied to all fields)."
149 | },
150 | "blitz-form/internalLabels": {
151 | "type": "boolean|undefined",
152 | "description": "Set to true if the entire form has its own labels and you do not want the BlitzField to show a label.\n\nWhen `true` subLabels will be passed as a prop called 'hint'.\n\nThis prop can be set on a BlitzField or on a BlitzForm (in which case it's applied to all fields)."
153 | },
154 | "BlitzForm/internalLabels": {
155 | "type": "boolean|undefined",
156 | "description": "Set to true if the entire form has its own labels and you do not want the BlitzField to show a label.\n\nWhen `true` subLabels will be passed as a prop called 'hint'.\n\nThis prop can be set on a BlitzField or on a BlitzForm (in which case it's applied to all fields)."
157 | },
158 | "blitz-form/internalErrors": {
159 | "type": "boolean|undefined",
160 | "description": "Set to true if the entire form has its own error handling. This makes sure it passes on props like `rules` and does nothing with them in the BlitzField.\n\nThis prop can be set on a BlitzField or on a BlitzForm (in which case it's applied to all fields)."
161 | },
162 | "BlitzForm/internalErrors": {
163 | "type": "boolean|undefined",
164 | "description": "Set to true if the entire form has its own error handling. This makes sure it passes on props like `rules` and does nothing with them in the BlitzField.\n\nThis prop can be set on a BlitzField or on a BlitzForm (in which case it's applied to all fields)."
165 | },
166 | "blitz-form/internalErrorsFor": {
167 | "type": "array",
168 | "description": "Pass the component names (without `.vue`) that have internal error handling. This makes sure it passes on props like `rules` and does nothing with them in the BlitzField."
169 | },
170 | "BlitzForm/internalErrorsFor": {
171 | "type": "array",
172 | "description": "Pass the component names (without `.vue`) that have internal error handling. This makes sure it passes on props like `rules` and does nothing with them in the BlitzField."
173 | },
174 | "test/value": {
175 | "type": "Record",
176 | "description": "The value!"
177 | },
178 | "Test/value": {
179 | "type": "Record",
180 | "description": "The value!"
181 | },
182 | "test-2/value": {
183 | "type": "string",
184 | "description": "The value!",
185 | "options": [
186 | "a",
187 | "b"
188 | ]
189 | },
190 | "Test2/value": {
191 | "type": "string",
192 | "description": "The value!",
193 | "options": [
194 | "a",
195 | "b"
196 | ]
197 | }
198 | }
--------------------------------------------------------------------------------
/packages/scripts/test/vueFilePathToVeturJsonData.test.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from 'vitest'
2 | import { vueFilePathToVeturJsonData } from '../src/index'
3 |
4 | test('vetur attributes', async () => {
5 | const input = 'test/helpers/BlitzForm.vue'
6 | const result = await vueFilePathToVeturJsonData(input, 'attributes')
7 | expect(result).toEqual({
8 | 'blitz-form/value': {
9 | type: 'object',
10 | description:
11 | "An object with the data of the entire form. The object keys are the ids of the fields passed in the 'schema'.\n\nTo be used with `:value` or `v-model`.",
12 | },
13 | 'blitz-form/id': {
14 | type: 'string',
15 | description:
16 | 'A manually set \'id\' of the BlitzForm. This prop is accessible in the `context` (as `formId`) of any "evaluated prop" and event.\n\nRead more on Evaluated Props in its dedicated page.',
17 | },
18 | 'blitz-form/schema': {
19 | type: 'array',
20 | description:
21 | "This is the heart of your BlitzForm. It's the schema that will defined what fields will be generated.",
22 | },
23 | 'blitz-form/actionButtons': {
24 | type: 'array',
25 | description:
26 | "Buttons on top of the form that control the 'mode' of the form. The possible pre-made buttons are:\n- 'edit' a button which puts the form in 'edit' mode & does `emit('edit')`\n- 'cancel' a button which puts the form in 'view' mode & does `emit('cancel')`\n- 'save' a button which puts the form in 'edit' mode & does `emit('save', {newData, oldData})`\n- 'delete' a red button which does `emit('delete')`\n- 'archive' a red button which does `emit('archive')`\n\nYou can also pass custom buttons with the same schema to generate forms.\n\nSee the documentation on \"Action Buttons\" for more info.",
27 | options: ['cancel', 'save', 'delete', 'archive'],
28 | },
29 | 'blitz-form/actionButtonDefaults': {
30 | type: 'object',
31 | description:
32 | 'You can overwrite the schema used for the default action buttons for edit, cancel, save, delete & archive.',
33 | },
34 | 'blitz-form/actionButtonsPosition': {
35 | type: 'string',
36 | description: 'The position of the action buttons.',
37 | options: ['top', 'bottom', 'right', 'left'],
38 | },
39 | 'blitz-form/validator': {
40 | type: 'function',
41 | description:
42 | 'A function which serves as global validator for your form. It will receive the edited data as first param and the original data (before user edits) as second. It should return true if all is OK or a string with error message.',
43 | },
44 | 'blitz-form/columnCount': {
45 | type: 'number',
46 | description:
47 | "The amount of columns the form should have.\n\nEach field can set a specific 'span' to be able to span multiple columns.",
48 | },
49 | 'blitz-form/gridGap': {
50 | type: 'string',
51 | description: 'The gap between each field in the form.',
52 | },
53 | 'blitz-form/lang': {
54 | type: 'object',
55 | description:
56 | 'The text used in the UI, eg. edit/save buttons etc... Pass an object with keys: archive, delete, cancel, edit, save.',
57 | },
58 | 'blitz-form/mode': {
59 | type: 'string',
60 | description:
61 | 'The mode represents how fields are rendered\n- "edit" or "add" means they can be interacted with\n- "view" means they can\'t\n- "raw" means the fields are not generated, just the raw value inside a div\n\nThis prop can be set on a BlitzField or on a BlitzForm (in which case it\'s applied to all fields).',
62 | options: ['edit', 'add', 'view', 'raw'],
63 | },
64 | 'blitz-form/labelPosition': {
65 | type: 'string|function',
66 | description:
67 | "The position of the label in comparison to the field.\n\nThis prop can be set on a BlitzField or on a BlitzForm (in which case it's applied to all fields).",
68 | options: ['top', 'left'],
69 | },
70 | 'blitz-form/evaluatedProps': {
71 | type: 'array',
72 | description:
73 | 'An array with prop names that should be treated as "Evaluated Props" when passed a function.\n\nThis prop can be set on a BlitzField or on a BlitzForm (in which case it\'s applied to all fields).',
74 | },
75 | 'blitz-form/internalLabels': {
76 | type: 'boolean|undefined',
77 | description:
78 | "Set to true if the entire form has its own labels and you do not want the BlitzField to show a label.\n\nWhen `true` subLabels will be passed as a prop called 'hint'.\n\nThis prop can be set on a BlitzField or on a BlitzForm (in which case it's applied to all fields).",
79 | },
80 | 'blitz-form/internalErrors': {
81 | type: 'boolean|undefined',
82 | description:
83 | "Set to true if the entire form has its own error handling. This makes sure it passes on props like `rules` and does nothing with them in the BlitzField.\n\nThis prop can be set on a BlitzField or on a BlitzForm (in which case it's applied to all fields).",
84 | },
85 | 'blitz-form/internalErrorsFor': {
86 | type: 'array',
87 | description:
88 | 'Pass the component names (without `.vue`) that have internal error handling. This makes sure it passes on props like `rules` and does nothing with them in the BlitzField.',
89 | },
90 | 'BlitzForm/value': {
91 | type: 'object',
92 | description:
93 | "An object with the data of the entire form. The object keys are the ids of the fields passed in the 'schema'.\n\nTo be used with `:value` or `v-model`.",
94 | },
95 | 'BlitzForm/id': {
96 | type: 'string',
97 | description:
98 | 'A manually set \'id\' of the BlitzForm. This prop is accessible in the `context` (as `formId`) of any "evaluated prop" and event.\n\nRead more on Evaluated Props in its dedicated page.',
99 | },
100 | 'BlitzForm/schema': {
101 | type: 'array',
102 | description:
103 | "This is the heart of your BlitzForm. It's the schema that will defined what fields will be generated.",
104 | },
105 | 'BlitzForm/actionButtons': {
106 | type: 'array',
107 | description:
108 | "Buttons on top of the form that control the 'mode' of the form. The possible pre-made buttons are:\n- 'edit' a button which puts the form in 'edit' mode & does `emit('edit')`\n- 'cancel' a button which puts the form in 'view' mode & does `emit('cancel')`\n- 'save' a button which puts the form in 'edit' mode & does `emit('save', {newData, oldData})`\n- 'delete' a red button which does `emit('delete')`\n- 'archive' a red button which does `emit('archive')`\n\nYou can also pass custom buttons with the same schema to generate forms.\n\nSee the documentation on \"Action Buttons\" for more info.",
109 | options: ['cancel', 'save', 'delete', 'archive'],
110 | },
111 | 'BlitzForm/actionButtonDefaults': {
112 | type: 'object',
113 | description:
114 | 'You can overwrite the schema used for the default action buttons for edit, cancel, save, delete & archive.',
115 | },
116 | 'BlitzForm/actionButtonsPosition': {
117 | type: 'string',
118 | description: 'The position of the action buttons.',
119 | options: ['top', 'bottom', 'right', 'left'],
120 | },
121 | 'BlitzForm/validator': {
122 | type: 'function',
123 | description:
124 | 'A function which serves as global validator for your form. It will receive the edited data as first param and the original data (before user edits) as second. It should return true if all is OK or a string with error message.',
125 | },
126 | 'BlitzForm/columnCount': {
127 | type: 'number',
128 | description:
129 | "The amount of columns the form should have.\n\nEach field can set a specific 'span' to be able to span multiple columns.",
130 | },
131 | 'BlitzForm/gridGap': {
132 | type: 'string',
133 | description: 'The gap between each field in the form.',
134 | },
135 | 'BlitzForm/lang': {
136 | type: 'object',
137 | description:
138 | 'The text used in the UI, eg. edit/save buttons etc... Pass an object with keys: archive, delete, cancel, edit, save.',
139 | },
140 | 'BlitzForm/mode': {
141 | type: 'string',
142 | description:
143 | 'The mode represents how fields are rendered\n- "edit" or "add" means they can be interacted with\n- "view" means they can\'t\n- "raw" means the fields are not generated, just the raw value inside a div\n\nThis prop can be set on a BlitzField or on a BlitzForm (in which case it\'s applied to all fields).',
144 | options: ['edit', 'add', 'view', 'raw'],
145 | },
146 | 'BlitzForm/labelPosition': {
147 | type: 'string|function',
148 | description:
149 | "The position of the label in comparison to the field.\n\nThis prop can be set on a BlitzField or on a BlitzForm (in which case it's applied to all fields).",
150 | options: ['top', 'left'],
151 | },
152 | 'BlitzForm/evaluatedProps': {
153 | type: 'array',
154 | description:
155 | 'An array with prop names that should be treated as "Evaluated Props" when passed a function.\n\nThis prop can be set on a BlitzField or on a BlitzForm (in which case it\'s applied to all fields).',
156 | },
157 | 'BlitzForm/internalLabels': {
158 | type: 'boolean|undefined',
159 | description:
160 | "Set to true if the entire form has its own labels and you do not want the BlitzField to show a label.\n\nWhen `true` subLabels will be passed as a prop called 'hint'.\n\nThis prop can be set on a BlitzField or on a BlitzForm (in which case it's applied to all fields).",
161 | },
162 | 'BlitzForm/internalErrors': {
163 | type: 'boolean|undefined',
164 | description:
165 | "Set to true if the entire form has its own error handling. This makes sure it passes on props like `rules` and does nothing with them in the BlitzField.\n\nThis prop can be set on a BlitzField or on a BlitzForm (in which case it's applied to all fields).",
166 | },
167 | 'BlitzForm/internalErrorsFor': {
168 | type: 'array',
169 | description:
170 | 'Pass the component names (without `.vue`) that have internal error handling. This makes sure it passes on props like `rules` and does nothing with them in the BlitzField.',
171 | },
172 | })
173 | })
174 |
175 | test('vetur tags', async () => {
176 | const input = 'test/helpers/BlitzForm.vue'
177 | const result = await vueFilePathToVeturJsonData(input, 'tags')
178 | expect(result).toEqual({
179 | 'blitz-form': {
180 | attributes: [
181 | 'value',
182 | 'id',
183 | 'schema',
184 | 'actionButtons',
185 | 'actionButtonDefaults',
186 | 'actionButtonsPosition',
187 | 'validator',
188 | 'columnCount',
189 | 'gridGap',
190 | 'lang',
191 | 'mode',
192 | 'labelPosition',
193 | 'evaluatedProps',
194 | 'internalLabels',
195 | 'internalErrors',
196 | 'internalErrorsFor',
197 | ],
198 | description: 'A BlitzForm',
199 | },
200 | BlitzForm: {
201 | attributes: [
202 | 'value',
203 | 'id',
204 | 'schema',
205 | 'actionButtons',
206 | 'actionButtonDefaults',
207 | 'actionButtonsPosition',
208 | 'validator',
209 | 'columnCount',
210 | 'gridGap',
211 | 'lang',
212 | 'mode',
213 | 'labelPosition',
214 | 'evaluatedProps',
215 | 'internalLabels',
216 | 'internalErrors',
217 | 'internalErrorsFor',
218 | ],
219 | description: 'A BlitzForm',
220 | },
221 | })
222 | })
223 |
224 | test('vetur attributes - options', async () => {
225 | const input = 'test/helpers/other components/nested/Test2.vue'
226 | const result = await vueFilePathToVeturJsonData(input, 'attributes')
227 | expect(result).toEqual({
228 | 'test-2/value': {
229 | type: 'string',
230 | description: 'The value!',
231 | options: ['a', 'b'],
232 | },
233 | 'Test2/value': {
234 | type: 'string',
235 | description: 'The value!',
236 | options: ['a', 'b'],
237 | },
238 | })
239 | })
240 |
--------------------------------------------------------------------------------
/packages/scripts/test/test.test.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from 'vitest'
2 | import { generateVeturFiles } from '../src/index'
3 | import fs from 'fs'
4 |
5 | test('h', async () => {
6 | const input = 'test/helpers'
7 | const output = 'test/helpers/out'
8 | await generateVeturFiles(input, output, { recursive: true })
9 | const outFileContents = {
10 | attributes: fs.readFileSync('test/helpers/out/attributes.json', 'utf8'),
11 | tags: fs.readFileSync('test/helpers/out/tags.json', 'utf8'),
12 | }
13 | expect(outFileContents.attributes).toEqual(
14 | `{
15 | "blitz-form/value": {
16 | "type": "object",
17 | "description": "An object with the data of the entire form. The object keys are the ids of the fields passed in the 'schema'.\\n\\nTo be used with \`:value\` or \`v-model\`."
18 | },
19 | "BlitzForm/value": {
20 | "type": "object",
21 | "description": "An object with the data of the entire form. The object keys are the ids of the fields passed in the 'schema'.\\n\\nTo be used with \`:value\` or \`v-model\`."
22 | },
23 | "blitz-form/id": {
24 | "type": "string",
25 | "description": "A manually set 'id' of the BlitzForm. This prop is accessible in the \`context\` (as \`formId\`) of any \\"evaluated prop\\" and event.\\n\\nRead more on Evaluated Props in its dedicated page."
26 | },
27 | "BlitzForm/id": {
28 | "type": "string",
29 | "description": "A manually set 'id' of the BlitzForm. This prop is accessible in the \`context\` (as \`formId\`) of any \\"evaluated prop\\" and event.\\n\\nRead more on Evaluated Props in its dedicated page."
30 | },
31 | "blitz-form/schema": {
32 | "type": "array",
33 | "description": "This is the heart of your BlitzForm. It's the schema that will defined what fields will be generated."
34 | },
35 | "BlitzForm/schema": {
36 | "type": "array",
37 | "description": "This is the heart of your BlitzForm. It's the schema that will defined what fields will be generated."
38 | },
39 | "blitz-form/actionButtons": {
40 | "type": "array",
41 | "description": "Buttons on top of the form that control the 'mode' of the form. The possible pre-made buttons are:\\n- 'edit' a button which puts the form in 'edit' mode & does \`emit('edit')\`\\n- 'cancel' a button which puts the form in 'view' mode & does \`emit('cancel')\`\\n- 'save' a button which puts the form in 'edit' mode & does \`emit('save', {newData, oldData})\`\\n- 'delete' a red button which does \`emit('delete')\`\\n- 'archive' a red button which does \`emit('archive')\`\\n\\nYou can also pass custom buttons with the same schema to generate forms.\\n\\nSee the documentation on \\"Action Buttons\\" for more info.",
42 | "options": [
43 | "cancel",
44 | "save",
45 | "delete",
46 | "archive"
47 | ]
48 | },
49 | "BlitzForm/actionButtons": {
50 | "type": "array",
51 | "description": "Buttons on top of the form that control the 'mode' of the form. The possible pre-made buttons are:\\n- 'edit' a button which puts the form in 'edit' mode & does \`emit('edit')\`\\n- 'cancel' a button which puts the form in 'view' mode & does \`emit('cancel')\`\\n- 'save' a button which puts the form in 'edit' mode & does \`emit('save', {newData, oldData})\`\\n- 'delete' a red button which does \`emit('delete')\`\\n- 'archive' a red button which does \`emit('archive')\`\\n\\nYou can also pass custom buttons with the same schema to generate forms.\\n\\nSee the documentation on \\"Action Buttons\\" for more info.",
52 | "options": [
53 | "cancel",
54 | "save",
55 | "delete",
56 | "archive"
57 | ]
58 | },
59 | "blitz-form/actionButtonDefaults": {
60 | "type": "object",
61 | "description": "You can overwrite the schema used for the default action buttons for edit, cancel, save, delete & archive."
62 | },
63 | "BlitzForm/actionButtonDefaults": {
64 | "type": "object",
65 | "description": "You can overwrite the schema used for the default action buttons for edit, cancel, save, delete & archive."
66 | },
67 | "blitz-form/actionButtonsPosition": {
68 | "type": "string",
69 | "description": "The position of the action buttons.",
70 | "options": [
71 | "top",
72 | "bottom",
73 | "right",
74 | "left"
75 | ]
76 | },
77 | "BlitzForm/actionButtonsPosition": {
78 | "type": "string",
79 | "description": "The position of the action buttons.",
80 | "options": [
81 | "top",
82 | "bottom",
83 | "right",
84 | "left"
85 | ]
86 | },
87 | "blitz-form/validator": {
88 | "type": "function",
89 | "description": "A function which serves as global validator for your form. It will receive the edited data as first param and the original data (before user edits) as second. It should return true if all is OK or a string with error message."
90 | },
91 | "BlitzForm/validator": {
92 | "type": "function",
93 | "description": "A function which serves as global validator for your form. It will receive the edited data as first param and the original data (before user edits) as second. It should return true if all is OK or a string with error message."
94 | },
95 | "blitz-form/columnCount": {
96 | "type": "number",
97 | "description": "The amount of columns the form should have.\\n\\nEach field can set a specific 'span' to be able to span multiple columns."
98 | },
99 | "BlitzForm/columnCount": {
100 | "type": "number",
101 | "description": "The amount of columns the form should have.\\n\\nEach field can set a specific 'span' to be able to span multiple columns."
102 | },
103 | "blitz-form/gridGap": {
104 | "type": "string",
105 | "description": "The gap between each field in the form."
106 | },
107 | "BlitzForm/gridGap": {
108 | "type": "string",
109 | "description": "The gap between each field in the form."
110 | },
111 | "blitz-form/lang": {
112 | "type": "object",
113 | "description": "The text used in the UI, eg. edit/save buttons etc... Pass an object with keys: archive, delete, cancel, edit, save."
114 | },
115 | "BlitzForm/lang": {
116 | "type": "object",
117 | "description": "The text used in the UI, eg. edit/save buttons etc... Pass an object with keys: archive, delete, cancel, edit, save."
118 | },
119 | "blitz-form/mode": {
120 | "type": "string",
121 | "description": "The mode represents how fields are rendered\\n- \\"edit\\" or \\"add\\" means they can be interacted with\\n- \\"view\\" means they can't\\n- \\"raw\\" means the fields are not generated, just the raw value inside a div\\n\\nThis prop can be set on a BlitzField or on a BlitzForm (in which case it's applied to all fields).",
122 | "options": [
123 | "edit",
124 | "add",
125 | "view",
126 | "raw"
127 | ]
128 | },
129 | "BlitzForm/mode": {
130 | "type": "string",
131 | "description": "The mode represents how fields are rendered\\n- \\"edit\\" or \\"add\\" means they can be interacted with\\n- \\"view\\" means they can't\\n- \\"raw\\" means the fields are not generated, just the raw value inside a div\\n\\nThis prop can be set on a BlitzField or on a BlitzForm (in which case it's applied to all fields).",
132 | "options": [
133 | "edit",
134 | "add",
135 | "view",
136 | "raw"
137 | ]
138 | },
139 | "blitz-form/labelPosition": {
140 | "type": "string|function",
141 | "description": "The position of the label in comparison to the field.\\n\\nThis prop can be set on a BlitzField or on a BlitzForm (in which case it's applied to all fields).",
142 | "options": [
143 | "top",
144 | "left"
145 | ]
146 | },
147 | "BlitzForm/labelPosition": {
148 | "type": "string|function",
149 | "description": "The position of the label in comparison to the field.\\n\\nThis prop can be set on a BlitzField or on a BlitzForm (in which case it's applied to all fields).",
150 | "options": [
151 | "top",
152 | "left"
153 | ]
154 | },
155 | "blitz-form/evaluatedProps": {
156 | "type": "array",
157 | "description": "An array with prop names that should be treated as \\"Evaluated Props\\" when passed a function.\\n\\nThis prop can be set on a BlitzField or on a BlitzForm (in which case it's applied to all fields)."
158 | },
159 | "BlitzForm/evaluatedProps": {
160 | "type": "array",
161 | "description": "An array with prop names that should be treated as \\"Evaluated Props\\" when passed a function.\\n\\nThis prop can be set on a BlitzField or on a BlitzForm (in which case it's applied to all fields)."
162 | },
163 | "blitz-form/internalLabels": {
164 | "type": "boolean|undefined",
165 | "description": "Set to true if the entire form has its own labels and you do not want the BlitzField to show a label.\\n\\nWhen \`true\` subLabels will be passed as a prop called 'hint'.\\n\\nThis prop can be set on a BlitzField or on a BlitzForm (in which case it's applied to all fields)."
166 | },
167 | "BlitzForm/internalLabels": {
168 | "type": "boolean|undefined",
169 | "description": "Set to true if the entire form has its own labels and you do not want the BlitzField to show a label.\\n\\nWhen \`true\` subLabels will be passed as a prop called 'hint'.\\n\\nThis prop can be set on a BlitzField or on a BlitzForm (in which case it's applied to all fields)."
170 | },
171 | "blitz-form/internalErrors": {
172 | "type": "boolean|undefined",
173 | "description": "Set to true if the entire form has its own error handling. This makes sure it passes on props like \`rules\` and does nothing with them in the BlitzField.\\n\\nThis prop can be set on a BlitzField or on a BlitzForm (in which case it's applied to all fields)."
174 | },
175 | "BlitzForm/internalErrors": {
176 | "type": "boolean|undefined",
177 | "description": "Set to true if the entire form has its own error handling. This makes sure it passes on props like \`rules\` and does nothing with them in the BlitzField.\\n\\nThis prop can be set on a BlitzField or on a BlitzForm (in which case it's applied to all fields)."
178 | },
179 | "blitz-form/internalErrorsFor": {
180 | "type": "array",
181 | "description": "Pass the component names (without \`.vue\`) that have internal error handling. This makes sure it passes on props like \`rules\` and does nothing with them in the BlitzField."
182 | },
183 | "BlitzForm/internalErrorsFor": {
184 | "type": "array",
185 | "description": "Pass the component names (without \`.vue\`) that have internal error handling. This makes sure it passes on props like \`rules\` and does nothing with them in the BlitzField."
186 | },
187 | "test/value": {
188 | "type": "Record",
189 | "description": "The value!"
190 | },
191 | "Test/value": {
192 | "type": "Record",
193 | "description": "The value!"
194 | },
195 | "test-2/value": {
196 | "type": "string",
197 | "description": "The value!",
198 | "options": [
199 | "a",
200 | "b"
201 | ]
202 | },
203 | "Test2/value": {
204 | "type": "string",
205 | "description": "The value!",
206 | "options": [
207 | "a",
208 | "b"
209 | ]
210 | }
211 | }`
212 | )
213 |
214 | expect(outFileContents.tags).toEqual(
215 | `{
216 | "blitz-form": {
217 | "attributes": [
218 | "value",
219 | "id",
220 | "schema",
221 | "actionButtons",
222 | "actionButtonDefaults",
223 | "actionButtonsPosition",
224 | "validator",
225 | "columnCount",
226 | "gridGap",
227 | "lang",
228 | "mode",
229 | "labelPosition",
230 | "evaluatedProps",
231 | "internalLabels",
232 | "internalErrors",
233 | "internalErrorsFor"
234 | ],
235 | "description": "A BlitzForm"
236 | },
237 | "BlitzForm": {
238 | "attributes": [
239 | "value",
240 | "id",
241 | "schema",
242 | "actionButtons",
243 | "actionButtonDefaults",
244 | "actionButtonsPosition",
245 | "validator",
246 | "columnCount",
247 | "gridGap",
248 | "lang",
249 | "mode",
250 | "labelPosition",
251 | "evaluatedProps",
252 | "internalLabels",
253 | "internalErrors",
254 | "internalErrorsFor"
255 | ],
256 | "description": "A BlitzForm"
257 | },
258 | "test": {
259 | "attributes": [
260 | "value"
261 | ],
262 | "description": "A Test"
263 | },
264 | "Test": {
265 | "attributes": [
266 | "value"
267 | ],
268 | "description": "A Test"
269 | },
270 | "test-2": {
271 | "attributes": [
272 | "value"
273 | ],
274 | "description": "A Test"
275 | },
276 | "Test2": {
277 | "attributes": [
278 | "value"
279 | ],
280 | "description": "A Test"
281 | }
282 | }`
283 | )
284 | })
285 |
--------------------------------------------------------------------------------