├── src
├── utils
│ ├── helpers.js
│ ├── casing.js
│ ├── collectApis.js
│ └── comments.js
├── prompts.js
├── generators
│ ├── directive.js
│ ├── css.js
│ ├── injections.js
│ ├── idea-folder.js
│ ├── index.js
│ ├── fake-webpack-config.js
│ ├── common.js
│ ├── component.js
│ └── templates.js
├── install.js
├── uninstall.js
└── index.js
├── yarn.lock
├── package.json
├── .eslintrc.js
├── .stylintrc
├── LICENSE
├── .gitignore
└── README.md
/src/utils/helpers.js:
--------------------------------------------------------------------------------
1 | module.exports.flatter = function (flatten, val) {
2 | if (Array.isArray(val)) {
3 | return [...flatten, ...val]
4 | }
5 | return [...flatten, val]
6 | }
7 |
--------------------------------------------------------------------------------
/src/utils/casing.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | toKebab (i) {
3 | return i.replace(/([a-zA-Z])([A-Z])/g, '$1-$2').toLowerCase()
4 | },
5 | toCamel (i) {
6 | return i.replace(/-([a-z])/g, c => c[1].toUpperCase())
7 | }
8 | }
--------------------------------------------------------------------------------
/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | xml-escape@^1.1.0:
6 | version "1.1.0"
7 | resolved "https://registry.yarnpkg.com/xml-escape/-/xml-escape-1.1.0.tgz#3904c143fa8eb3a0030ec646d2902a2f1b706c44"
8 | integrity sha1-OQTBQ/qOs6ADDsZG0pAqLxtwbEQ=
9 |
--------------------------------------------------------------------------------
/src/utils/collectApis.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 |
3 | /**
4 | * @param {String} dir
5 | * @return {[{name:string, api:Object}]} Array of APis
6 | */
7 | module.exports = function (dir) {
8 | return fs
9 | .readdirSync(dir)
10 | .filter(name => name.endsWith('.json'))
11 | .map(name => ({
12 | name: name.substring(0, name.length - 5),
13 | api: JSON.parse(fs.readFileSync(`${dir}/${name}`))
14 | }))
15 | }
16 |
--------------------------------------------------------------------------------
/src/prompts.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Quasar App Extension prompts script
3 | *
4 | * Inquirer prompts
5 | * (answers are available as "api.prompts" in the other scripts)
6 | * https://www.npmjs.com/package/inquirer#question
7 | *
8 | */
9 |
10 | module.exports = function () {
11 | return [
12 | {
13 | name: 'addToGitIgnore',
14 | type: 'confirm',
15 | message: 'Add generated files to .gitignore?',
16 | default: true
17 | }
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "quasar-app-extension-ide-helper",
3 | "version": "1.0.2",
4 | "description": "Quasar framework extension which enables IDE features like autocomplete by generating helper files for IDE to index.",
5 | "author": "Matyáš Racek",
6 | "license": "MIT",
7 | "repository": "panstromek/quasar-ide-helper",
8 | "main": "src/index.js",
9 | "engines": {
10 | "node": ">= 8.9.0",
11 | "npm": ">= 5.6.0",
12 | "yarn": ">= 1.6.0"
13 | },
14 | "dependencies": {
15 | "xml-escape": "^1.1.0"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/generators/directive.js:
--------------------------------------------------------------------------------
1 | const { typeComment } = require('../utils/comments')
2 |
3 | function functionDirective (name, value) {
4 | return `
5 | /*
6 | * @param {${typeComment(value)}} ${value && value.desc}
7 | */
8 | Vue.directive('${name}="(${Object.keys(value.params).map(p => p.toLowerCase()).join(', ')}) => {}"', {})\n`
9 | }
10 |
11 | module.exports = {
12 | directive (name, { arg = {}, value = {}, modifiers = {} }) {
13 | return `
14 | /*
15 | * @param {${typeComment(value)}} ${value && value.desc}
16 | */
17 | Vue.directive('${name}', {})\n${value.type === 'Function' ?
18 | functionDirective(name, value) : ''}`
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | browser: false,
5 | 'es6': true
6 | },
7 |
8 | globals: {
9 | 'ga': true, // Google Analytics
10 | 'cordova': true,
11 | '__statics': true
12 | },
13 |
14 | // add your custom rules here
15 | rules: {
16 | // allow async-await
17 | 'generator-star-spacing': 'off',
18 | // allow paren-less arrow functions
19 | 'arrow-parens': 'off',
20 | 'one-var': 'off',
21 | 'prefer-promise-reject-errors': 'off',
22 |
23 | // allow console.log during development only
24 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
25 | // allow debugger during development only
26 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/.stylintrc:
--------------------------------------------------------------------------------
1 | {
2 | "blocks": "never",
3 | "brackets": "never",
4 | "colons": "never",
5 | "colors": "always",
6 | "commaSpace": "always",
7 | "commentSpace": "always",
8 | "cssLiteral": "never",
9 | "depthLimit": false,
10 | "duplicates": true,
11 | "efficient": "always",
12 | "extendPref": false,
13 | "globalDupe": true,
14 | "indentPref": 2,
15 | "leadingZero": "never",
16 | "maxErrors": false,
17 | "maxWarnings": false,
18 | "mixed": false,
19 | "namingConvention": false,
20 | "namingConventionStrict": false,
21 | "none": "never",
22 | "noImportant": false,
23 | "parenSpace": "never",
24 | "placeholder": false,
25 | "prefixVarsWithDollar": "always",
26 | "quotePref": "single",
27 | "semicolons": "never",
28 | "sortOrder": false,
29 | "stackedProperties": "never",
30 | "trailingWhitespace": "never",
31 | "universal": "never",
32 | "valid": true,
33 | "zeroUnits": "never",
34 | "zIndexNormalize": false
35 | }
36 |
--------------------------------------------------------------------------------
/src/install.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Quasar App Extension install script
3 | *
4 | * API: https://github.com/quasarframework/quasar/blob/master/app/lib/app-extension/InstallAPI.js
5 | */
6 |
7 | const { generateIfNeeded } = require('./generators/common')
8 | const fs = require('fs')
9 | /**
10 | *
11 | * @param {InstallAPI} api
12 | */
13 | module.exports = function (api) {
14 | generateIfNeeded(api, api.appDir)
15 | if (api.prompts.addToGitIgnore) {
16 | console.log('Adding generated files to .gitignore.')
17 |
18 | const path = api.appDir + '/.gitignore'
19 | let gitignore = fs.existsSync(path) ? fs.readFileSync(path).toString() : '\n'
20 |
21 | let append = ''
22 |
23 | if (!gitignore.match(/^.quasar-ide-helper/gm)) {
24 | append += '\n.quasar-ide-helper\n'
25 | }
26 | if (!gitignore.match(/^webpack\.config\.js/gm)) {
27 | append += '\nwebpack.config.js\n'
28 | }
29 | if (append) {
30 | fs.appendFileSync(path, append)
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Matyáš Racek
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 |
--------------------------------------------------------------------------------
/src/utils/comments.js:
--------------------------------------------------------------------------------
1 | const { flatter } = require('./helpers')
2 |
3 | const me = module.exports = {
4 | typeComment ({ type, desc, definition, values, examples }) {
5 | if (values) {
6 | return values.map(val => {
7 | switch (typeof val) {
8 | case 'string':
9 | return `'${val}'`
10 | case 'boolean':
11 | return `Boolean`
12 | default :
13 | return val
14 | }
15 | }).filter((t, i, arr) => arr.lastIndexOf(t) === i).join('|')
16 | }
17 | return [type]
18 | .reduce(flatter, [])
19 | .map(type => {
20 | if (type === 'Object' && definition) {
21 | return `{${Object.entries(definition)
22 | .map(([name, api]) => {
23 | return `${name} : ${me.typeComment(api)}`
24 | }).join(', ')}}`
25 | }
26 | return type
27 | })
28 | .join('|')
29 | .replace(/Any/g, '*')
30 | },
31 | propComment (prop) {
32 | return `
33 | /**
34 | * ${prop.desc}${prop.reactive ? ' (reactive)' : ''}
35 | * @type {${me.typeComment(prop)}}
36 | */`
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/uninstall.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 |
3 | /**
4 | * Quasar App Extension uninstall script
5 | *
6 | * API: https://github.com/quasarframework/quasar/blob/master/app/lib/app-extension/UninstallAPI.js
7 | */
8 | module.exports = function ({ appDir }) {
9 | try {
10 | if (fs.existsSync(`${appDir}/.QuasarLiveTemplates.xml`)) {
11 | fs.unlinkSync(`${appDir}/.QuasarLiveTemplates.xml`)
12 | }
13 |
14 | const configPath = `${appDir}/webpack.config.js`
15 | const configExists = fs.existsSync(configPath)
16 | if (configExists && !fs.readFileSync(configPath).toString().includes('generated-by-ide-helper')) {
17 | fs.unlinkSync(configPath)
18 | }
19 |
20 | if (fs.existsSync(`${appDir}/.quasar-ide-helper`)) {
21 |
22 | if (fs.statSync(`${appDir}/.quasar-ide-helper`).isDirectory()) {
23 | fs.readdirSync(`${appDir}/.quasar-ide-helper`).forEach(file => {
24 | fs.unlinkSync(`${appDir}/.quasar-ide-helper/${file}`)
25 | })
26 | }
27 |
28 | fs.unlinkSync(`${appDir}/.quasar-ide-helper`)
29 | }
30 | } catch (e) {
31 | console.log('Some generated files/folders were not deleted (Probably a permission error)')
32 | console.log('They should not cause any problems but you can delete them manually:')
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Example user template template
3 | ### Example user template
4 |
5 | # IntelliJ project files
6 | .idea
7 | *.iml
8 | out
9 | gen### Node template
10 | # Logs
11 | logs
12 | *.log
13 | npm-debug.log*
14 | yarn-debug.log*
15 | yarn-error.log*
16 |
17 | # Runtime data
18 | pids
19 | *.pid
20 | *.seed
21 | *.pid.lock
22 |
23 | # Directory for instrumented libs generated by jscoverage/JSCover
24 | lib-cov
25 |
26 | # Coverage directory used by tools like istanbul
27 | coverage
28 |
29 | # nyc test coverage
30 | .nyc_output
31 |
32 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
33 | .grunt
34 |
35 | # Bower dependency directory (https://bower.io/)
36 | bower_components
37 |
38 | # node-waf configuration
39 | .lock-wscript
40 |
41 | # Compiled binary addons (https://nodejs.org/api/addons.html)
42 | build/Release
43 |
44 | # Dependency directories
45 | node_modules/
46 | jspm_packages/
47 |
48 | # TypeScript v1 declaration files
49 | typings/
50 |
51 | # Optional npm cache directory
52 | .npm
53 |
54 | # Optional eslint cache
55 | .eslintcache
56 |
57 | # Optional REPL history
58 | .node_repl_history
59 |
60 | # Output of 'npm pack'
61 | *.tgz
62 |
63 | # Yarn Integrity file
64 | .yarn-integrity
65 |
66 | # dotenv environment variables file
67 | .env
68 |
69 | # next.js build output
70 | .next
71 |
72 |
--------------------------------------------------------------------------------
/src/generators/css.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | /**
3 | *
4 | * Generates css files from all CSS in Quasar repo.
5 | * Idea provide autocomplete for in CSS node_modules by default
6 | * and it's not intuitive how to even set it up properly (I couldn't at first:D ),
7 | * so this is convenient, but dirty workaround for getting autocomplete for Quasar CSS
8 | *
9 | * For now it just copies the files, so you could actually point Idea to these folders yourself
10 | * but it's kind of unreliable and not many people will know this setting.
11 | * More importantly - I also want to generate some docs and comments to these files eventually
12 | * so this is not totally pointless as you might think
13 | *
14 | * @param {String} appDir
15 | */
16 | module.exports = function (appDir) {
17 | const cssPath = `${appDir}/node_modules/quasar/dist`
18 | const files = fs.readdirSync(cssPath)
19 | const targetDir = `${appDir}/.quasar-ide-helper`
20 | files.forEach(filename => {
21 | if (filename.endsWith('.css') && !filename.endsWith('min.css')) {
22 | fs.copyFileSync(`${cssPath}/${filename}`, `${targetDir}/${filename}`)
23 | }
24 | })
25 |
26 | //Copy variable files
27 | if (fs.existsSync(`${appDir}/node_modules/quasar/src/css/variables.sass`)) {
28 | fs.copyFileSync(`${appDir}/node_modules/quasar/src/css/variables.sass`, `${targetDir}/variables.sass`)
29 | }
30 | if (fs.existsSync(`${appDir}/node_modules/quasar/src/css/variables.styl`)) {
31 | fs.copyFileSync(`${appDir}/node_modules/quasar/src/css/variables.styl`, `${targetDir}/variables.styl`)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/generators/injections.js:
--------------------------------------------------------------------------------
1 | const { propComment, typeComment } = require('../utils/comments')
2 |
3 | module.exports = {
4 | generateInjections (injections) {
5 | return `
6 | /**
7 | * Quasar plugins injected to prototype:
8 | * ${injectionListComment(injections)}
9 | */
10 | Vue.prototype.$q = {
11 | ${injections.map(generateInjection).join(',\n ')}
12 | }`
13 | }
14 | }
15 |
16 | function injectionListComment (plugins) {
17 | return plugins
18 | .map(({ name, api }) => `* ${api.injection}`).join('\n')
19 | }
20 |
21 | function generateInjection ({ name, api }) {
22 | return `${api.injection.substring(3)} : {
23 | ${objectProps(api.props)}
24 | ${pluginMethods(api.methods)}
25 | }`
26 | }
27 |
28 | function paramDoc (params = []) {
29 | return Object.entries(params)
30 | .map(([name, param]) => {
31 | const paramName = param.required ? name : `[${name}]`
32 | return `
33 | * @param {${typeComment(param)}} ${paramName} ${param.desc}`
34 | }).join('')
35 | }
36 |
37 | function returns (returns = { type: undefined, desc: '' }) {
38 | const returnType = typeComment(returns)
39 | return `@returns ${returnType && `{${returnType}}`} ${returns.desc}`
40 | }
41 |
42 | function pluginMethods (methods = []) {
43 | return Object.entries(methods)
44 | .map(([name, methodApi]) => {
45 | return `
46 | /**
47 | * ${methodApi.desc}${paramDoc(methodApi.params)}
48 | * ${returns(methodApi.returns)}
49 | */
50 | ${name} (${Object.keys(methodApi.params || {})}) {}`
51 | })
52 | }
53 |
54 | function objectProps (props) {
55 | if (!props) {
56 | return ``
57 | }
58 | return Object.entries(props)
59 | .map(([name, prop]) => {
60 | return `${propComment(prop)}
61 | ${name}: {}`
62 | }).toString() + ','
63 | }
64 |
--------------------------------------------------------------------------------
/src/generators/idea-folder.js:
--------------------------------------------------------------------------------
1 | module.exports.addVueCoreLibsToIdeaFolder = function (appDir) {
2 | const fs = require('fs')
3 | const ideaPath = `${appDir}/.idea`
4 | if (!fs.existsSync(ideaPath)) {
5 | fs.mkdirSync(ideaPath)
6 | }
7 | if (!fs.existsSync(`${ideaPath}/libraries`)) {
8 | fs.mkdirSync(`${ideaPath}/libraries`)
9 | }
10 |
11 | fs.writeFileSync(appDir + '/.idea/libraries/vue_generated_by_ide_helper.xml', `
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | `)
30 | const mappingsFile = ideaPath + '/jsLibraryMappings.xml'
31 | if (fs.existsSync(mappingsFile)) {
32 | const mappings = fs.readFileSync(mappingsFile).toString()
33 | if (!mappings.includes('vue-generated-by-ide-helper')) {
34 | fs.writeFileSync(mappingsFile, mappings.replace(/<\/project>/, `
35 |
36 |
37 |
38 | `))
39 | }
40 | } else {
41 | fs.writeFileSync(mappingsFile, `
42 |
43 |
44 |
45 |
46 |
47 | `)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/generators/index.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const directive = require('./directive').directive
3 | const generateInjections = require('./injections').generateInjections
4 | const { generateComponents } = require('./component')
5 |
6 | function writeComponents (directory, components) {
7 | components.forEach(({ api, name }) => {
8 | generateComponents(name, api)
9 | .forEach(([name, data]) => {
10 | return fs.writeFileSync(`${directory}/${name}.js`, data)
11 | })
12 |
13 | })
14 |
15 | fs.writeFileSync(`${directory}/imports.js`, `import Vue from 'vue';`)
16 |
17 | components.forEach(({ api, name }) => {
18 | fs.appendFileSync(`${directory}/imports.js`, `import ${name} from './${name}.js';`)
19 | })
20 |
21 | components.forEach(({ api, name }) => {
22 | fs.appendFileSync(`${directory}/imports.js`, `Vue.component(${name}.name, ${name});`)
23 | })
24 | }
25 |
26 | function writeDirectives (dir, directives) {
27 | const file = `${dir}/directives.js`
28 | fs.writeFileSync(file, 'import Vue from \'vue\'\n')
29 | directives.forEach(({ name, api }) => fs.appendFileSync(file, directive(name, api)))
30 | }
31 |
32 | function writeInjections (dir, injections) {
33 | const targetFile = `${dir}/injections.js`
34 | fs.writeFileSync(targetFile, `import Vue from 'vue';${generateInjections(injections)}`)
35 | }
36 |
37 | module.exports = function (appDir, apis) {
38 | const dir = `${appDir}/.quasar-ide-helper`
39 | if (!fs.existsSync(dir)) {
40 | fs.mkdirSync(dir)
41 | }
42 |
43 | const components = apis.filter(({ api }) => api.type === 'component')
44 | writeComponents(dir, components)
45 |
46 | const directives = apis.filter(({ api }) => api.type === 'directive')
47 | writeDirectives(dir, directives)
48 |
49 | const injections = apis.filter(({ api }) => api.type === 'plugin' && api.injection)
50 |
51 | writeInjections(dir, injections)
52 | }
53 |
54 |
--------------------------------------------------------------------------------
/src/generators/fake-webpack-config.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 |
3 | function generateIfNeeded (appDir, cfg) {
4 | if (!fs.existsSync(`${appDir}/webpack.config.js`)) {
5 | console.log('Generating fake webpack config')
6 | fs.writeFileSync(`${appDir}/webpack.config.js`, `
7 | /* eslint-disable */
8 | /**
9 | * DON'T EDIT THIS FILE!!
10 | *
11 | * This file is generated to help WebStorm resolve Webpack aliases. It is never run in the app.
12 | * If you need to extend your webpack config, put your code in quasar.conf.js into extendWebpack function
13 | *
14 | * If you changed your WebPack config, you can delete this file. It will be regenerated again with up to date config
15 | *
16 | * Next line is picked up by ide-helper, don't change it:
17 | * generated-by-ide-helper
18 | */
19 | module.exports = ${JSON.stringify(cfg)}
20 | `)
21 | }
22 | }
23 |
24 | module.exports.setupFakeWebPackConfig = function (appDir, api) {
25 | const configPath = `${appDir}/webpack.config.js`
26 | const configExists = fs.existsSync(configPath)
27 | // TODO detect if webpack config changed
28 | //TODO don't delete and don't regenerate this - it is run on every rebuild
29 | if (configExists && !fs.readFileSync(configPath).toString().includes('generated-by-ide-helper')) {
30 | console.log('Found WebPack config that was not generated by ide-helper. Skipping fake webpack config setup.')
31 | console.log(' (Delete the file and rerun build command again to set it up)')
32 | } else {
33 | if (configExists) {
34 | console.log('Deleting generated "fake" webpack config (it will be generated again on next build)')
35 | fs.unlinkSync(configPath)
36 | }
37 |
38 | let config = {}
39 | api.extendWebpack(cfg => {
40 | // TODO detect if webpack config changed
41 | config = cfg
42 | })
43 |
44 | api.beforeDev(() => {
45 | generateIfNeeded(appDir, config)
46 | })
47 | console.log('Registered hook to generate new "fake" webpack config on next build')
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/generators/common.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const generate = require('./')
3 | const generateCss = require('./css')
4 | const { addVueCoreLibsToIdeaFolder } = require('./idea-folder')
5 | const collectApis = require('./../utils/collectApis')
6 |
7 | module.exports.generateIfNeeded = generateIfNeeded
8 | module.exports.generateAll = generateAll
9 |
10 | function generateIfNeeded (api, appDir) {
11 | const quasarVersion = api.getPackageVersion('quasar')
12 | const ideHelperVersion = api.getPackageVersion('quasar-app-extension-ide-helper')
13 | let persistentConfig
14 | const configFilePath = `${appDir}/.quasar-ide-helper/ide-helper-config.json`
15 | if (fs.existsSync(configFilePath)) {
16 | try {
17 | persistentConfig = JSON.parse(fs.readFileSync(configFilePath).toString())
18 | } catch (e) {
19 | console.error('Failed to parse ide helper config file, regenerating everything from scratch.')
20 | }
21 | }
22 | if (
23 | !persistentConfig ||
24 | persistentConfig.lastQuasarVersion !== quasarVersion ||
25 | persistentConfig.lastIdeHelperVersion !== ideHelperVersion ||
26 | !fs.existsSync(`${appDir}/.quasar-ide-helper`)
27 | ) {
28 | generateAll(api)
29 | }
30 | }
31 |
32 | function generateAll (api) {
33 | const appDir = api.appDir
34 | const apiPath = `${appDir}/node_modules/quasar/dist/api`
35 | console.log('Collecting API files...')
36 | const apis = collectApis(apiPath)
37 | console.log('Generating components, directives and injections...')
38 | generate(appDir, apis)
39 | console.log('Generating CSS files...')
40 | generateCss(appDir)
41 | console.log('Adding Vue core libs as libraries in .idea folder')
42 | addVueCoreLibsToIdeaFolder(appDir)
43 | console.log('Generated - Enjoy ;)')
44 | console.log('')
45 | console.log('Note: You may need to close and reopen the project for some changes to take effect.')
46 | console.log('If you don\'t see changes even then, try to run the script without opened project.')
47 | fs.writeFileSync(`${appDir}/.quasar-ide-helper/ide-helper-config.json`, JSON.stringify({
48 | lastQuasarVersion: api.getPackageVersion('quasar'),
49 | lastIdeHelperVersion: api.getPackageVersion('quasar-app-extension-ide-helper')
50 | }))
51 | }
52 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | const collectApis = require('./utils/collectApis')
2 | const generateTemplates = require('./generators/templates')
3 | const { setupFakeWebPackConfig } = require('./generators/fake-webpack-config')
4 | const { generateAll, generateIfNeeded } = require('./generators/common')
5 |
6 | /**
7 | * Quasar App Extension index/runner script
8 | * (runs on each dev/build)
9 | *
10 | * API: https://github.com/quasarframework/quasar/blob/master/app/lib/app-extension/IndexAPI.js
11 | */
12 | /**
13 | *
14 | * @param {IndexAPI} api
15 | * @param ctx
16 | */
17 | module.exports = function (api, ctx) {
18 | if (api.ctx.prod) {
19 | return
20 | }
21 | const appDir = api.appDir
22 | const apiPath = `${appDir}/node_modules/quasar/dist/api`
23 |
24 | if (api.hasPackage('@quasar/app-webpack') || api.hasPackage('@quasar/app')) {
25 | setupFakeWebPackConfig(appDir, api)
26 | }
27 | generateIfNeeded(api, appDir)
28 |
29 | api.registerCommand('generate', () => generateAll(api))
30 |
31 | api.registerCommand('templates', () => {
32 | console.log('Collecting API files...')
33 | const apis = collectApis(apiPath)
34 | console.log('Generating template files...')
35 | generateTemplates(apis, appDir)
36 | console.log()
37 | console.log('Template files generated into .QuasarLiveTemplates.xml')
38 | console.log()
39 | console.log('..............................................................')
40 | console.log('IMPORTANT !!!')
41 | console.log(`To make live templates functional, you need to move the file into`)
42 | console.log(`'templates' in your IDE's global config directory.`)
43 | console.log(`For WebStorm it should be something like:`)
44 | console.log(`'..//.WebStorm2018.3/system/jba_config/templates'`)
45 | console.log()
46 | console.log(`After you do this, restart the IDE, it will pick up them.`)
47 | console.log()
48 | console.log(`For other JetBrains IDEs it should be similar. If you are not sure, look at:`)
49 | console.log('https://intellij-support.jetbrains.com/hc/en-us/articles/206544519-Directories-used-by-the-IDE-to-store-settings-caches-plugins-and-logs')
50 | console.log()
51 | console.log('..............................................................')
52 | })
53 | }
54 |
--------------------------------------------------------------------------------
/src/generators/component.js:
--------------------------------------------------------------------------------
1 | const { flatter } = require('../utils/helpers')
2 | const toKebab = require('../utils/casing').toKebab
3 | const { typeComment, propComment } = require('../utils/comments')
4 | const toCamel = require('../utils/casing').toCamel
5 | module.exports = {
6 | generateComponents (name, api) {
7 | return [[name, component(name, api)]]
8 | }
9 | }
10 |
11 | function eventParams (params = {}) {
12 | return Object.entries(params)
13 | .map(([name, api]) => {
14 | return ` * @param {${typeComment(api)}} ${name} ${api.desc} `
15 | }).join('\n')
16 | }
17 |
18 | function vueEvents (events = {}) {
19 | return Object.entries(events)
20 | .map(([name, event]) => {
21 | return `
22 | /**
23 | * ${event.desc}
24 | ${eventParams(event.params)}
25 | */
26 | '@${name}': function (${Object.keys(event.params || {})}) {},`
27 | }).join('\n')
28 | }
29 |
30 | function component (name, api) {
31 | return `
32 | /**
33 | * Quasar ${name} component
34 | *
35 | * @see {@link https://v1.quasar-framework.org/vue-components/${toKebab(name.substring(1))}|Quasar Docs} (Generated link, may not always work)
36 | */
37 | export default {
38 | name: '${name}',
39 | props: {${vueEvents(api.events)}${vueProps(api.props)}
40 | }
41 | }
42 | `
43 | }
44 |
45 | function vueType (type) {
46 | if (type === 'Any' || (Array.isArray(type) && type.includes('Any'))) {
47 | return ``
48 | }
49 | return `
50 | type: ${generateVueType(type)},`
51 | }
52 |
53 | function vueProps (props) {
54 | if (!props) {
55 | return ``
56 | }
57 | return Object.entries(props)
58 | // Duplicate prop for each enum value
59 | .map(([name, prop]) => {
60 |
61 | /// For now only for strings - We can try to handle numbers later
62 | if (prop.type === 'String' && prop.values) {
63 | return [[name, prop],
64 | ...prop.values.map(val => [`'${name}="${val}"'`, { ...prop, type:'Boolean' }])]
65 | }
66 | return [[name, prop]]
67 | })
68 | .reduce(flatter, [])
69 | .map(([name, prop]) => {
70 | const VueType = vueType(prop.type)
71 | const required = prop.required ? `
72 | required: true` : ``
73 |
74 | return `${propComment(prop)}
75 | ${toCamel(name)}: {${VueType}${required}
76 | }`
77 | })
78 | }
79 |
80 | function generateVueType (type) {
81 | if (Array.isArray(type)) {
82 | return `[${type.toString()}]`
83 | }
84 | return type
85 | }
86 |
--------------------------------------------------------------------------------
/src/generators/templates.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const escape = require('xml-escape')
3 | const { toCamel, toKebab } = require('../utils/casing')
4 | /**
5 | * Generate live templates
6 | * @param {[{name:String, api: Object}]} apis
7 | * @param {String} targetDir
8 | */
9 | module.exports = function (apis, targetDir) {
10 | const targetFile = targetDir + '/.QuasarLiveTemplates.xml'
11 | fs.writeFileSync(targetFile,
12 | '\n')
13 |
14 | // filter to components only
15 | apis
16 | .filter(({ api }) => api.type === 'component')
17 | .filter(({ api }) => !!api.props) // components without props are pointless to template
18 | .forEach(component => fs.appendFileSync(targetFile, createTemplates(component)))
19 |
20 | fs.appendFileSync(targetFile,
21 | '')
22 | }
23 |
24 | function propsTemplate (props) {
25 | return Object.entries(props)
26 | .filter(([name, prop]) => prop.type !== 'Boolean') // Don't need to generate boolean props
27 | .map(([name, prop]) => {
28 | const xmlVar = ``
29 | if (prop.type === 'String') {
30 | return {
31 | preTemplate: `[${toKebab(name)}=$${toCamel(name)}$]`,
32 | xml: xmlVar,
33 |
34 | postTemplate: `$${toCamel(name)}Binding$${toKebab(name)}="$${toCamel(name)}$"`,
35 | postXml: `${xmlVar}`
36 | }
37 | }
38 | return {
39 | preTemplate: `[${toKebab(name)}=$${toCamel(name)}$]`,
40 | xml: xmlVar,
41 |
42 | postTemplate: `:${toKebab(name)}="$${toCamel(name)}$"`,
43 | postXml: xmlVar
44 | }
45 | })
46 | }
47 |
48 | function createTemplate (name, template, description, variables) {
49 | return `
50 |
53 | ${variables}
54 |
55 |
56 |
57 |
58 | `
59 | }
60 |
61 | /**
62 | *
63 | * @param {String} name
64 | * @param {{type: String, props: Object}} api
65 | * @return {string}
66 | */
67 | function createTemplates ({ name: rawName, api }) {
68 | const kebabName = toKebab(rawName)
69 | const name = rawName.toLowerCase()
70 | const props = propsTemplate(api.props)
71 | const preProps = props.map(({ preTemplate }) => preTemplate).join('')
72 | const postProps = props.map(({ postTemplate }) => postTemplate).join(' ')
73 | const preVariables = props.map(({ xml }) => xml).join('\n')
74 | const postVariables = props.map(({ postXml }) => postXml).join('\n')
75 |
76 | return createTemplate(
77 | `${name}t`,
78 | `${kebabName}${preProps}`,
79 | `Scaffold ${rawName} CSS-like template (expands to HTML after TAB)`,
80 | preVariables)
81 | +
82 | createTemplate(
83 | name,
84 | `<${kebabName} ${postProps} />`,
85 | `Scaffold ${rawName}`,
86 | postVariables)
87 | }
88 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Quasar Ide Helper
2 |
3 | This extension enables autocomplete and quick doc for various features of Quasar Framework in WebStorm and other JetBrains IDEs by generating bunch of files that IDEA can index easily. It was inspired by [laravel-ide-helper](https://github.com/barryvdh/laravel-ide-helper), which does the similar thing for Laravel.
4 |
5 | ## Current status
6 |
7 | Note that this extension is mostly not necessary anymore, because WebStorm supports most of the features natively already. If you encounter something which is not yet supported by WebStorm but it's provided by this extension, please let us now so we can report it to JetBrains and make this extension obsolete sooner.
8 |
9 | ## Prerequisites
10 |
11 | You need to have some JetBrains IDE (WebStorm, IntelliJ etc.) with Vue plugin installed and Quasar project. Only versions >=1.0 are supported.
12 |
13 | ## Installation & Usage
14 |
15 | Run this in your Quasar project folder. This will install the extension and generate the helper files. It will also asks if you want to put generated files into `.gitignore`.
16 |
17 | ```bash
18 | quasar ext add ide-helper
19 | ```
20 |
21 | `ide-helper` also generates "fake" webpack config, which is not used by the project but IDEA can resolve imports based on its contents. This file can only be generated during `quasar dev` when we know full config, so expect it to appear after you run your dev server for the first time.
22 |
23 | **That's it!**
24 | This should be everything you need to do to get started.
25 |
26 | Now you should get autocomplete (`ctrl+space`) and quick-doc(`ctrl+q`) for Quasar components, their props, events, prototype injections, directives and CSS classes.
27 |
28 | ## Manual approach
29 |
30 | Since 1.0 of this extension, you don't need to generate anything by hand anymore. Nevertheless, you might want to generate ide-helper files manually at some point. You can still do that with some commands and maybe some other actions. See below.
31 |
32 | ### Autocomplete files in `.quasar-ide-helper` Folder
33 |
34 | You can regenerate these files at any point in time with this command:
35 |
36 | ```bash
37 | quasar run ide-helper generate
38 | ```
39 |
40 | Also, feel free to delete this folder anytime, it will get regenerated.
41 |
42 | ### IDEA project files
43 |
44 | One thing this plugin does is generating some files in your `.idea` folder (this is a hidden folder that contains various metadata about projects in JetBrains IDEs). To be more concrete, it will add `vue`,`vue-router` and `vuex` as custom libraries so you get autocomplete for things like `this.$nextTick`, `this.$router` or `this.$store` in component files. Since this is a bit shady thing to do, because these files are internal, it may have no desired effect - we can't really break anything with it, because IDEA doesn't trust this folder for anything critical, but IDE can delete and completely rewrite this folder at any point in time.
45 |
46 | If you don't have autocomplete for `vue`, `vuex` or `vue-router` instance methods and regenerating with the command above doesn't help, you can add them manually.
47 |
48 | Go to -> settings -> Languages & Frameworks -> JavaScript -> Libraries -> Add -> (name it) -> click `+` -> attach directories-> choose `vue`,`vue-router` and `vuex` in `node_modules`) -> OK away. Now IDEA should correctly resolve Vue methods and tags.
49 |
50 |
51 | ## Optional: Live Templates
52 |
53 | IDE-helper can also generate Live Templates for all components. We don't advocate for this feature too much, because we haven't seen many people using it and it didn't prove to be very useful, but it's still there.
54 |
55 | If you want to generate Live Templates (Snippets), use this command first:
56 |
57 | ```bash
58 | quasar run ide-helper templates
59 | ```
60 |
61 | **Important**: This will generate a `.QuasarLiveTemplates.xml` file with live templates. Now you need to place this file in your IDEA config in the `templates` folder and restart the IDE.
62 |
63 | This is because IDEA doesn't support project-scoped templates unfortunately, so you need to put them inside the global config. For WebStorm it should be something like `.//.WebStorm2018.3/config/jba_config/templates` where `` is your home directory. For other JetBrains IDEs it should be similar. If you are not sure, look where it is [here](https://intellij-support.jetbrains.com/hc/en-us/articles/206544519-Directories-used-by-the-IDE-to-store-settings-caches-plugins-and-logs)
64 |
65 | ### How templates work
66 |
67 | tl;dr: There are two types of templates for each component. You can check all of them out if you type `q` and `ctrl+space` in your template.
68 |
69 | First type of template is a lowercase component name eg. `qbtn` or `qitem`. Write that and hit `TAB`, it will expand into component tag with its props and lets you fill in the values. Template will also jump through places where you can add colon `:` to bind the prop.
70 |
71 | Second type of templates leverages a feature in IDEA (and other editors, too) which allows you to write html tag with class, id and attributes as a CSS selector which will expand into html after `TAB`. So If you use quasar templates that has `t` suffix (like `qbtnt` or `qicont`), they won't expand into HTML but into CSS selector, so you can append more attributes, or classes
72 |
73 | ## Contributing
74 |
75 | If you have an idea how to improve this extension or fix a bug, feel free to post an issue or submit PR. You can also look at open issues and implement some ideas for improvements that are listed there, there are few great improvements still possible.
76 |
77 | Just bear in mind that this is not an IDE plugin - it's a hack, we can't implement any IDE features, we only give it more food to consume in a clever way. Many feature requests belong more on [JetBrains YouTrack](https://youtrack.jetbrains.com/) and not here.
78 |
79 | ## v0.17 Support
80 |
81 | Supporting older versions would be a lot of work, but if there is a significant need for this, I might try to implement it in the future or more likely help potential contributors - I already built a bit a hacky generator for JSON API files from old Quasar docs. These can be used to generate helper files for ide-helper. It's an old code with some bugs, though and integrating it would probably be non-trivial. Given that users mostly moved to Quasar 1.0, I don't see a reason to burn more time on this.
82 |
83 | ## Thanks
84 | Thanks to [@jpgilchrist](https://github.com/jpgilchrist) for the research in [this](https://github.com/quasarframework/quasar/issues/2224) issue and useful insights. Very big thanks to @hwb who noticed and wrote [here](https://forum.quasar-framework.org/topic/2322/how-to-import-quasar-components-to-use-vue-code-completion-in-intellij-idea-webstorm/2) how to trick IDEA into indexing the component - I am a bit sad that I haven't found this before as I could do something like this earlier and save myself (and others) a lot of development time. Also big thanks to [Quasar](https://github.com/quasarframework/quasar/) and its contributors ;)
85 |
86 | ## License
87 |
88 | Copyright (c) 2019-present Matyáš Racek & contributors
89 |
90 | [MIT License](http://en.wikipedia.org/wiki/MIT_License)
91 |
--------------------------------------------------------------------------------