├── 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 | 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 | --------------------------------------------------------------------------------