├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ └── codeql-analysis.yml ├── .gitattributes ├── .prettierrc.js ├── images ├── flutter-stylizer-logo.png └── flutter-stylizer-logo-128x128.png ├── src ├── buttons │ ├── buttons.interface.ts │ └── buttons.ts ├── test │ ├── suite │ │ ├── testfiles │ │ │ ├── utf8_text.dart.txt │ │ │ ├── issue26_case2.want.txt │ │ │ ├── issue26_case2.dart.txt │ │ │ ├── issue6.dart.txt │ │ │ ├── issue6_want.txt │ │ │ ├── issue18_case3.txt │ │ │ ├── issue18_case4.txt │ │ │ ├── issue18_case1.txt │ │ │ ├── issue18_case2.txt │ │ │ ├── issue18.dart.txt │ │ │ ├── issue26_case1.dart.txt │ │ │ ├── issue26_case1.want.txt │ │ │ ├── basic_classes.dart.txt │ │ │ ├── basic_classes.dart.windz.txt │ │ │ ├── basic_classes_custom_order.txt │ │ │ ├── basic_classes_default_order.txt │ │ │ ├── issue31.dart.txt │ │ │ ├── issue31.want.txt │ │ │ ├── issue19.dart.txt │ │ │ ├── issue19_want.txt │ │ │ ├── block_kind.dart.txt │ │ │ ├── block_kind_want.txt │ │ │ ├── pubspec_want.txt │ │ │ ├── pubspec.dart.txt │ │ │ ├── scope.dart.txt │ │ │ ├── scope_want.txt │ │ │ └── issue20.dart.txt │ │ ├── mocks │ │ │ └── mockButtons.ts │ │ ├── buttons │ │ │ └── buttons.test.ts │ │ ├── utils │ │ │ ├── watch_editors.test.ts │ │ │ ├── create_buttons.test.ts │ │ │ └── update_statusbar.test.ts │ │ ├── index.ts │ │ └── dart │ │ │ ├── flow_analysis.test.ts │ │ │ ├── separate_private_methods.test.ts │ │ │ ├── issue6.test.ts │ │ │ ├── issue26.test.ts │ │ │ ├── block_kind.test.ts │ │ │ ├── issue31.test.ts │ │ │ ├── editor.test.ts │ │ │ └── pubspec.test.ts │ └── runTest.ts ├── constants │ └── buttons.ts ├── utils │ ├── watch_editors.ts │ ├── update_statusbar.ts │ └── create_buttons.ts ├── dart │ ├── pairs.ts │ ├── entity.ts │ ├── line.ts │ ├── editor.ts │ └── dart.ts └── extension.ts ├── .gitignore ├── .vscodeignore ├── .vscode ├── extensions.json ├── settings.json ├── tasks.json └── launch.json ├── tslint.json ├── .eslintrc.json ├── tsconfig.json ├── .eslintrc.js ├── vsc-extension-quickstart.md ├── package.json ├── CHANGELOG.md ├── LICENSE └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [gmlewis] 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set default behavior to automatically normalize line endings. 2 | * text=auto 3 | 4 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | semi: false, 4 | tabs: false, 5 | tabWidth: 2, 6 | } -------------------------------------------------------------------------------- /images/flutter-stylizer-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gmlewis/flutter-stylizer/HEAD/images/flutter-stylizer-logo.png -------------------------------------------------------------------------------- /src/buttons/buttons.interface.ts: -------------------------------------------------------------------------------- 1 | export interface Button { 2 | command: string, 3 | text: string, 4 | tooltip: string, 5 | } 6 | -------------------------------------------------------------------------------- /images/flutter-stylizer-logo-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gmlewis/flutter-stylizer/HEAD/images/flutter-stylizer-logo-128x128.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | .vscode-test/ 4 | *.vsix 5 | .nyc_output/ 6 | coverage/ 7 | *.code-workspace 8 | *.log 9 | *.sh 10 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | src/** 5 | .gitignore 6 | vsc-extension-quickstart.md 7 | **/tsconfig.json 8 | **/.eslintrc.json 9 | **/*.map 10 | **/*.ts 11 | -------------------------------------------------------------------------------- /src/test/suite/testfiles/utf8_text.dart.txt: -------------------------------------------------------------------------------- 1 | abstract class ElementImpl implements Element { 2 | /// An Unicode right arrow. 3 | @deprecated 4 | static final String RIGHT_ARROW = " \u2192 "; 5 | } -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dbaeumer.vscode-eslint" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /src/constants/buttons.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | 3 | export const BTN_ALIGNMENT: vscode.StatusBarAlignment = vscode.StatusBarAlignment.Left 4 | export const BTN_PRIORITY: number = 1 // The higher the number the more left the button appears 5 | -------------------------------------------------------------------------------- /src/test/suite/testfiles/issue26_case2.want.txt: -------------------------------------------------------------------------------- 1 | class Issue26 { 2 | late Completer? _dialogCompleter; 3 | late Function(DialogRequest) _dialogHandler; 4 | final Map _dialogs = {}; 5 | late final NavigatorService _navigatorService; 6 | } 7 | -------------------------------------------------------------------------------- /src/test/suite/testfiles/issue26_case2.dart.txt: -------------------------------------------------------------------------------- 1 | class Issue26 { 2 | final Map _dialogs = {}; 3 | late Function(DialogRequest) _dialogHandler; 4 | 5 | late Completer? _dialogCompleter; 6 | late final NavigatorService _navigatorService; 7 | } 8 | -------------------------------------------------------------------------------- /src/buttons/buttons.ts: -------------------------------------------------------------------------------- 1 | import { Button } from './buttons.interface' 2 | 3 | const buttons: Button[] = [ 4 | { 5 | command: 'extension.flutterStylizer', 6 | text: 'Flutter Stylizer', 7 | tooltip: 'Run Flutter Stylizer on the current file', 8 | }, 9 | ] 10 | 11 | export default buttons 12 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "out": false 4 | }, 5 | "search.exclude": { 6 | "out": true 7 | }, 8 | "typescript.tsc.autoDetect": "off", 9 | "dart.analysisExcludedFolders": [ 10 | "**/test/**", 11 | "**" 12 | ], 13 | "files.watcherExclude": { 14 | "**/target": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/watch_editors.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import updateStatusbar from './update_statusbar' 3 | 4 | const watchEditors = (buttons: vscode.StatusBarItem[]): void => { 5 | vscode.window.onDidChangeActiveTextEditor((editor: vscode.TextEditor | undefined) => { 6 | updateStatusbar(editor, buttons) 7 | }) 8 | } 9 | 10 | export default watchEditors 11 | -------------------------------------------------------------------------------- /src/test/suite/mocks/mockButtons.ts: -------------------------------------------------------------------------------- 1 | import { Button } from '../../../buttons/buttons.interface' 2 | 3 | const mockButtons: Button[] = [ 4 | { 5 | command: 'cmd-1', 6 | text: 'text-1', 7 | tooltip: 'toolti-1', 8 | }, 9 | { 10 | command: 'cmd-2', 11 | text: 'text-2', 12 | tooltip: 'toolti-2', 13 | }, 14 | ] 15 | 16 | export default mockButtons 17 | -------------------------------------------------------------------------------- /src/test/suite/testfiles/issue6.dart.txt: -------------------------------------------------------------------------------- 1 | class MyClass { 2 | static const kReleaseMode = true; 3 | final Map _initialValues = kReleaseMode 4 | ? {} 5 | : { 6 | 'hubType': 'test', 7 | 'ht.localIP': '192.168.1.1', 8 | 'ht.makerAppID': '2233', 9 | 'ht.accessToken': '7de3-yyyyyy-xxxxxxxxxxx', 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /src/test/suite/testfiles/issue6_want.txt: -------------------------------------------------------------------------------- 1 | class MyClass { 2 | static const kReleaseMode = true; 3 | 4 | final Map _initialValues = kReleaseMode 5 | ? {} 6 | : { 7 | 'hubType': 'test', 8 | 'ht.localIP': '192.168.1.1', 9 | 'ht.makerAppID': '2233', 10 | 'ht.accessToken': '7de3-yyyyyy-xxxxxxxxxxx', 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-string-throw": true, 4 | "no-unused-expression": true, 5 | "no-duplicate-variable": true, 6 | "curly": true, 7 | "class-name": true, 8 | "semicolon": [ 9 | true, 10 | "never" 11 | ], 12 | "triple-equals": true, 13 | "quotemark": [ 14 | true, 15 | "single" 16 | ] 17 | }, 18 | "defaultSeverity": "warning" 19 | } -------------------------------------------------------------------------------- /src/test/suite/testfiles/issue18_case3.txt: -------------------------------------------------------------------------------- 1 | class Global { 2 | Global(this._balance, this._aBalance) {} 3 | 4 | double _aBalance; 5 | double _balance; 6 | 7 | double get aBalance => _aBalance; 8 | double get balance => _balance; 9 | 10 | void utility() {} 11 | 12 | bool isValid() { 13 | return true; 14 | } 15 | 16 | void printer() {} 17 | 18 | bool isNotValid() { 19 | return false; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/suite/testfiles/issue18_case4.txt: -------------------------------------------------------------------------------- 1 | class Global { 2 | Global(this._balance, this._aBalance) {} 3 | 4 | double _aBalance; 5 | double _balance; 6 | 7 | double get aBalance => _aBalance; 8 | double get balance => _balance; 9 | 10 | bool isNotValid() { 11 | return false; 12 | } 13 | 14 | bool isValid() { 15 | return true; 16 | } 17 | 18 | void printer() {} 19 | 20 | void utility() {} 21 | } 22 | -------------------------------------------------------------------------------- /src/test/suite/buttons/buttons.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert' 2 | import buttons from '../../../buttons/buttons' 3 | 4 | suite('buttons:', function() { 5 | test('Contains 1 button', function() { 6 | assert.strictEqual(buttons.length, 1) 7 | }) 8 | 9 | test('Button 0.command is "extension.flutterStylizer"', function() { 10 | assert.strictEqual(buttons[0].command, 'extension.flutterStylizer') 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /src/test/suite/testfiles/issue18_case1.txt: -------------------------------------------------------------------------------- 1 | class Global { 2 | Global(this._balance, this._aBalance) {} 3 | 4 | double _aBalance; 5 | double _balance; 6 | 7 | double get aBalance => _aBalance; 8 | 9 | void utility() {} 10 | 11 | double get balance => _balance; 12 | 13 | bool isValid() { 14 | return true; 15 | } 16 | 17 | void printer() {} 18 | 19 | bool isNotValid() { 20 | return false; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/suite/testfiles/issue18_case2.txt: -------------------------------------------------------------------------------- 1 | class Global { 2 | Global(this._balance, this._aBalance) {} 3 | 4 | double _aBalance; 5 | double _balance; 6 | 7 | double get aBalance => _aBalance; 8 | 9 | double get balance => _balance; 10 | 11 | bool isNotValid() { 12 | return false; 13 | } 14 | 15 | bool isValid() { 16 | return true; 17 | } 18 | 19 | void printer() {} 20 | 21 | void utility() {} 22 | } 23 | -------------------------------------------------------------------------------- /src/test/suite/testfiles/issue18.dart.txt: -------------------------------------------------------------------------------- 1 | class Global { 2 | double _aBalance; 3 | 4 | Global(this._balance, this._aBalance) {} 5 | 6 | double get aBalance => _aBalance; 7 | 8 | double _balance; 9 | 10 | void utility() {} 11 | 12 | double get balance => _balance; 13 | 14 | bool isValid() { 15 | return true; 16 | } 17 | 18 | void printer() {} 19 | 20 | bool isNotValid() { 21 | return false; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module" 7 | }, 8 | "plugins": [ 9 | "@typescript-eslint" 10 | ], 11 | "rules": { 12 | "@typescript-eslint/class-name-casing": "warn", 13 | "@typescript-eslint/semi": "warn", 14 | "curly": "warn", 15 | "eqeqeq": "warn", 16 | "no-throw-literal": "warn", 17 | "semi": 2 18 | } 19 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ 7 | "es6" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "src", 11 | "strict": true, 12 | "noUnusedLocals": true, 13 | "noImplicitReturns": true, 14 | "noFallthroughCasesInSwitch": true, 15 | "noUnusedParameters": true 16 | }, 17 | "exclude": [ 18 | "node_modules", 19 | ".vscode-test" 20 | ] 21 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/utils/update_statusbar.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | 3 | const updateStatusbar = (editor: vscode.TextEditor | undefined, buttons: vscode.StatusBarItem[]): void => { 4 | let showButtons: boolean = false 5 | 6 | if (editor !== undefined) { 7 | const { 8 | document: { languageId }, 9 | } = editor 10 | 11 | if (languageId.indexOf('dart') === 0) { 12 | showButtons = true 13 | } 14 | } 15 | 16 | buttons.forEach((btn: vscode.StatusBarItem) => { 17 | if (showButtons) { 18 | btn.show() 19 | } else { 20 | btn.hide() 21 | } 22 | }) 23 | } 24 | 25 | export default updateStatusbar 26 | -------------------------------------------------------------------------------- /src/utils/create_buttons.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import { BTN_ALIGNMENT, BTN_PRIORITY } from '../constants/buttons' 3 | import { Button } from '../buttons/buttons.interface' 4 | 5 | const createButtons = (buttons: Button[]): vscode.StatusBarItem[] => { 6 | return buttons.map( 7 | (command: Button): vscode.StatusBarItem => { 8 | const newBtn: vscode.StatusBarItem = vscode.window.createStatusBarItem(BTN_ALIGNMENT, BTN_PRIORITY) 9 | 10 | newBtn.command = command.command 11 | newBtn.text = command.text 12 | newBtn.tooltip = command.tooltip 13 | 14 | return newBtn 15 | } 16 | ) 17 | } 18 | 19 | export default createButtons 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /src/test/suite/utils/watch_editors.test.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import * as assert from 'assert' 3 | import * as sinon from 'sinon' 4 | import createButtons from '../../../utils/create_buttons' 5 | import mockButtons from '../mocks/mockButtons' 6 | import watchEditors from '../../../utils/watch_editors' 7 | 8 | suite('watchEditors()', function() { 9 | test('updateStatusbar() called', function() { 10 | const spiedUpdateStatusbar: sinon.SinonSpy = sinon.spy(vscode.window, 'onDidChangeActiveTextEditor') 11 | const mockStatusButtons: vscode.StatusBarItem[] = createButtons(mockButtons) 12 | watchEditors(mockStatusButtons) 13 | assert(spiedUpdateStatusbar.called) 14 | spiedUpdateStatusbar.restore() 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path' 2 | 3 | import { runTests } from 'vscode-test' 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../') 10 | 11 | // The path to test runner 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, './suite/index') 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ extensionDevelopmentPath, extensionTestsPath }) 17 | } catch (err) { 18 | console.error('Failed to run tests') 19 | process.exit(1) 20 | } 21 | } 22 | 23 | main() 24 | -------------------------------------------------------------------------------- /src/test/suite/testfiles/issue26_case1.dart.txt: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:ocean/ocean.dart'; 3 | import 'snackbars.dart'; 4 | 5 | class SnackbarService { 6 | SnackbarService(); 7 | 8 | final Map _snackbars = {}; 9 | SnackBar Function(SnackBarConfigBase) getSnackbar(dynamic key) { 10 | return _snackbars[key]!; 11 | } 12 | 13 | final GlobalKey snackbarKey = GlobalKey(); 14 | 15 | bool containsKey(dynamic key) { 16 | return _snackbars.containsKey(key); 17 | } 18 | 19 | void registerSnackbar( 20 | {required dynamic key, required SnackBar Function(SnackBarConfigBase) snackbarBuilder}) { 21 | _snackbars[key] = snackbarBuilder; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/suite/testfiles/issue26_case1.want.txt: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:ocean/ocean.dart'; 3 | import 'snackbars.dart'; 4 | 5 | class SnackbarService { 6 | SnackbarService(); 7 | 8 | final GlobalKey snackbarKey = GlobalKey(); 9 | 10 | final Map _snackbars = {}; 11 | 12 | bool containsKey(dynamic key) { 13 | return _snackbars.containsKey(key); 14 | } 15 | 16 | SnackBar Function(SnackBarConfigBase) getSnackbar(dynamic key) { 17 | return _snackbars[key]!; 18 | } 19 | 20 | void registerSnackbar( 21 | {required dynamic key, required SnackBar Function(SnackBarConfigBase) snackbarBuilder}) { 22 | _snackbars[key] = snackbarBuilder; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/suite/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path' 2 | import * as Mocha from 'mocha' 3 | import * as glob from 'glob' 4 | 5 | export function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: 'tdd', 9 | color: true 10 | }) 11 | 12 | const testsRoot = path.resolve(__dirname, '..') 13 | 14 | return new Promise((c, e) => { 15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { 16 | if (err) { 17 | return e(err) 18 | } 19 | 20 | // Add files to the test suite 21 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))) 22 | 23 | try { 24 | // Run the mocha test 25 | mocha.run(failures => { 26 | if (failures > 0) { 27 | e(new Error(`${failures} tests failed.`)) 28 | } else { 29 | c() 30 | } 31 | }) 32 | } catch (err) { 33 | console.error(err) 34 | e(err) 35 | } 36 | }) 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /src/test/suite/utils/create_buttons.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert' 2 | import * as vscode from 'vscode' 3 | import createButtons from '../../../utils/create_buttons' 4 | import mockButtons from '../mocks/mockButtons' 5 | 6 | suite('createButtons()', function() { 7 | const result: vscode.StatusBarItem[] = createButtons(mockButtons) 8 | 9 | test('Returns the correct number of buttons', function() { 10 | assert.strictEqual(result.length, mockButtons.length) 11 | }) 12 | 13 | result.forEach((button: vscode.StatusBarItem, index: number) => { 14 | suite(`Button ${index}:`, function() { 15 | test(`command is "${button.command}"`, function() { 16 | assert.strictEqual(result[index].command, button.command) 17 | }) 18 | 19 | test(`text is "${button.text}"`, function() { 20 | assert.strictEqual(result[index].text, button.text) 21 | }) 22 | 23 | test(`tooltip is "${button.tooltip}"`, function() { 24 | assert.strictEqual(result[index].tooltip, button.tooltip) 25 | }) 26 | }) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "es6": true, 4 | "node": true 5 | }, 6 | "extends": [], 7 | "ignorePatterns": ["*.md"], 8 | "parser": "@typescript-eslint/parser", 9 | "parserOptions": { 10 | "project": "tsconfig.json", 11 | "sourceType": "module" 12 | }, 13 | "plugins": [ 14 | "@typescript-eslint" 15 | ], 16 | "rules": { 17 | "@typescript-eslint/class-name-casing": "warn", 18 | "@typescript-eslint/semi": [ 19 | "warn", 20 | "never" 21 | ], 22 | "curly": "warn", 23 | "eqeqeq": [ 24 | "warn", 25 | "always" 26 | ], 27 | "no-throw-literal": "warn", 28 | "semi": [ 29 | "error", 30 | "never" 31 | ], 32 | "@typescript-eslint/member-delimiter-style": [ 33 | "warn", 34 | { 35 | "multiline": { 36 | "delimiter": "comma", 37 | "requireLast": true 38 | }, 39 | "singleline": { 40 | "delimiter": "comma", 41 | "requireLast": true 42 | } 43 | } 44 | ], 45 | "no-redeclare": "warn", 46 | "no-unused-expressions": "warn", 47 | }, 48 | "settings": {} 49 | } 50 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": [ 14 | "--extensionDevelopmentPath=${workspaceFolder}" 15 | ], 16 | "outFiles": [ 17 | "${workspaceFolder}/out/**/*.js" 18 | ], 19 | "preLaunchTask": "${defaultBuildTask}" 20 | }, 21 | { 22 | "name": "Extension Tests", 23 | "type": "extensionHost", 24 | "request": "launch", 25 | "runtimeExecutable": "${execPath}", 26 | "args": [ 27 | "--extensionDevelopmentPath=${workspaceFolder}", 28 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 29 | ], 30 | "outFiles": [ 31 | "${workspaceFolder}/out/test/**/*.js" 32 | ], 33 | "preLaunchTask": "${defaultBuildTask}" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /src/dart/pairs.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 Glenn M. Lewis 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliee. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // MatchingPairsMap represents a map of matching pairs keyed by 18 | // the openAbsOffset value. 19 | export interface MatchingPairsMap { 20 | [key: number]: MatchingPair, 21 | } 22 | 23 | // MatchingPair represents a matching pair of features in the Dart 24 | // source code, such as r'''=>''', (=>), {=>}, etc. 25 | export interface MatchingPair { 26 | open: string, 27 | close: string, 28 | 29 | openAbsOffset: number, 30 | closeAbsOffset: number, 31 | 32 | openLineIndex: number, 33 | closeLineIndex: number, 34 | 35 | openRelStrippedOffset: number, 36 | closeRelStrippedOffset: number, 37 | 38 | pairStackDepth: number, 39 | parentPairOpenAbsOffset: number, 40 | } -------------------------------------------------------------------------------- /src/dart/entity.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 Glenn M. Lewis 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { Line } from './line' 18 | 19 | // Entity represents a single, independent feature of a Dart.Class. 20 | export interface Entity { 21 | entityType: EntityType, 22 | lines: Line[], 23 | name: string, // Used for sorting, but could be "". 24 | } 25 | 26 | export const isPrivate = (entity: Entity): boolean => { 27 | return entity.name.startsWith('_') 28 | } 29 | 30 | // EntityType represents a type of Dart Line. 31 | export enum EntityType { 32 | Unknown, 33 | BlankLine, 34 | SingleLineComment, 35 | MultiLineComment, 36 | MainConstructor, 37 | NamedConstructor, 38 | StaticVariable, 39 | InstanceVariable, 40 | OverrideVariable, 41 | StaticPrivateVariable, 42 | PrivateInstanceVariable, 43 | OverrideMethod, 44 | OtherMethod, 45 | BuildMethod, 46 | GetterMethod, 47 | LeaveUnmodified, 48 | } 49 | 50 | -------------------------------------------------------------------------------- /src/test/suite/testfiles/basic_classes.dart.txt: -------------------------------------------------------------------------------- 1 | // This is a test file. After running this through `Flutter Stylizer`, 2 | // the output should match the content of file `basic_classes_out.dart`. 3 | 4 | import 'dart:math'; 5 | 6 | // Class1 is the first class. 7 | class Class1 { 8 | // _pvi is a private instance variable. 9 | List _pvi = ['one', 'two']; 10 | @override 11 | build() {} // build method 12 | 13 | // This is a random single-line comment somewhere in the class. 14 | 15 | // _spv is a static private variable. 16 | static final String _spv = 'spv'; 17 | 18 | /* This is a 19 | * random multi- 20 | * line comment 21 | * somewhere in the middle 22 | * of the class */ 23 | 24 | // _spvni is a static private variable with no initializer. 25 | static double _spvni = 0; 26 | int _pvini = 1; 27 | static int sv = 0; 28 | int v = 2; 29 | final double fv = 42.0; 30 | Class1(); 31 | Class1.fromNum(); 32 | var myfunc = (int n) => n; 33 | get vv => v; // getter 34 | @override 35 | toString() { 36 | print('$_pvi, $_spv, $_spvni, $_pvini, ${sqrt(2)}'); 37 | return ''; 38 | } 39 | 40 | // "Here is 'where we add ${ text to "trip 'up' ''' the ${dart parser}. 41 | /* 42 | ''' 43 | """ 44 | // 45 | */ 46 | static const a = """; 47 | '${b}; 48 | ''' ; 49 | """; 50 | static const b = '''; 51 | { ( ))) """ {{{} )))); 52 | '''; 53 | static const c = {'{{{((... """ ${'((('};'}; 54 | } 55 | -------------------------------------------------------------------------------- /src/test/suite/testfiles/basic_classes.dart.windz.txt: -------------------------------------------------------------------------------- 1 | // This is a test file. After running this through `Flutter Stylizer`, 2 | // the output should match the content of file `basic_classes_out.dart`. 3 | 4 | import 'dart:math'; 5 | 6 | // Class1 is the first class. 7 | class Class1 { 8 | // _pvi is a private instance variable. 9 | List _pvi = ['one', 'two']; 10 | @override 11 | build() {} // build method 12 | 13 | // This is a random single-line comment somewhere in the class. 14 | 15 | // _spv is a static private variable. 16 | static final String _spv = 'spv'; 17 | 18 | /* This is a 19 | * random multi- 20 | * line comment 21 | * somewhere in the middle 22 | * of the class */ 23 | 24 | // _spvni is a static private variable with no initializer. 25 | static double _spvni = 0; 26 | int _pvini = 1; 27 | static int sv = 0; 28 | int v = 2; 29 | final double fv = 42.0; 30 | Class1(); 31 | Class1.fromNum(); 32 | var myfunc = (int n) => n; 33 | get vv => v; // getter 34 | @override 35 | toString() { 36 | print('$_pvi, $_spv, $_spvni, $_pvini, ${sqrt(2)}'); 37 | return ''; 38 | } 39 | 40 | // "Here is 'where we add ${ text to "trip 'up' ''' the ${dart parser}. 41 | /* 42 | ''' 43 | """ 44 | // 45 | */ 46 | static const a = """; 47 | '${b}; 48 | ''' ; 49 | """; 50 | static const b = '''; 51 | { ( ))) """ {{{} )))); 52 | '''; 53 | static const c = {'{{{((... """ ${'((('};'}; 54 | } 55 | -------------------------------------------------------------------------------- /src/test/suite/testfiles/basic_classes_custom_order.txt: -------------------------------------------------------------------------------- 1 | // This is a test file. After running this through `Flutter Stylizer`, 2 | // the output should match the content of file `basic_classes_out.dart`. 3 | 4 | import 'dart:math'; 5 | 6 | // Class1 is the first class. 7 | class Class1 { 8 | Class1(); 9 | 10 | Class1.fromNum(); 11 | 12 | // "Here is 'where we add ${ text to "trip 'up' ''' the ${dart parser}. 13 | /* 14 | ''' 15 | """ 16 | // 17 | */ 18 | static const a = """; 19 | '${b}; 20 | ''' ; 21 | """; 22 | 23 | static const b = '''; 24 | { ( ))) """ {{{} )))); 25 | '''; 26 | 27 | static const c = {'{{{((... """ ${'((('};'}; 28 | static int sv = 0; 29 | 30 | final double fv = 42.0; 31 | int v = 2; 32 | 33 | @override 34 | toString() { 35 | print('$_pvi, $_spv, $_spvni, $_pvini, ${sqrt(2)}'); 36 | return ''; 37 | } 38 | 39 | var myfunc = (int n) => n; 40 | 41 | get vv => v; // getter 42 | 43 | /* This is a 44 | * random multi- 45 | * line comment 46 | * somewhere in the middle 47 | * of the class */ 48 | 49 | // This is a random single-line comment somewhere in the class. 50 | 51 | // _spv is a static private variable. 52 | static final String _spv = 'spv'; 53 | 54 | // _spvni is a static private variable with no initializer. 55 | static double _spvni = 0; 56 | 57 | // _pvi is a private instance variable. 58 | List _pvi = ['one', 'two']; 59 | 60 | int _pvini = 1; 61 | 62 | @override 63 | build() {} // build method 64 | } 65 | -------------------------------------------------------------------------------- /src/test/suite/testfiles/basic_classes_default_order.txt: -------------------------------------------------------------------------------- 1 | // This is a test file. After running this through `Flutter Stylizer`, 2 | // the output should match the content of file `basic_classes_out.dart`. 3 | 4 | import 'dart:math'; 5 | 6 | // Class1 is the first class. 7 | class Class1 { 8 | Class1(); 9 | 10 | Class1.fromNum(); 11 | 12 | // "Here is 'where we add ${ text to "trip 'up' ''' the ${dart parser}. 13 | /* 14 | ''' 15 | """ 16 | // 17 | */ 18 | static const a = """; 19 | '${b}; 20 | ''' ; 21 | """; 22 | 23 | static const b = '''; 24 | { ( ))) """ {{{} )))); 25 | '''; 26 | 27 | static const c = {'{{{((... """ ${'((('};'}; 28 | static int sv = 0; 29 | 30 | final double fv = 42.0; 31 | int v = 2; 32 | 33 | // This is a random single-line comment somewhere in the class. 34 | 35 | // _spv is a static private variable. 36 | static final String _spv = 'spv'; 37 | 38 | // _spvni is a static private variable with no initializer. 39 | static double _spvni = 0; 40 | 41 | // _pvi is a private instance variable. 42 | List _pvi = ['one', 'two']; 43 | 44 | int _pvini = 1; 45 | 46 | @override 47 | toString() { 48 | print('$_pvi, $_spv, $_spvni, $_pvini, ${sqrt(2)}'); 49 | return ''; 50 | } 51 | 52 | var myfunc = (int n) => n; 53 | 54 | get vv => v; // getter 55 | 56 | /* This is a 57 | * random multi- 58 | * line comment 59 | * somewhere in the middle 60 | * of the class */ 61 | 62 | @override 63 | build() {} // build method 64 | } 65 | -------------------------------------------------------------------------------- /src/test/suite/dart/flow_analysis.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 Glenn M. Lewis 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import * as assert from 'assert' 18 | import * as vscode from 'vscode' 19 | 20 | import { Editor } from '../../../dart/editor' 21 | 22 | const fs = require('fs') 23 | const path = require('path') 24 | 25 | suite('Flow Analysis Tests', function() { 26 | var myExtDir = (vscode.extensions.getExtension('gmlewis-vscode.flutter-stylizer') || {}).extensionPath || process.env.VSCODE_CWD 27 | const testfilesDir = path.join(myExtDir, 'src', 'test', 'suite', 'testfiles') 28 | 29 | test('Flow analysis get classes', () => { 30 | const source = fs.readFileSync(path.join(testfilesDir, 'flow_analysis.dart.txt'), 'utf8') 31 | 32 | const e = new Editor(source, false, false) 33 | 34 | const [got, err] = e.getClasses(false, false) 35 | if (err !== null) { 36 | throw Error(err.message) // Make the compiler happy. 37 | } 38 | 39 | assert.strictEqual(got.length, 22, 'classes') 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /src/test/suite/testfiles/issue31.dart.txt: -------------------------------------------------------------------------------- 1 | class SnackbarService { 2 | SnackbarService(); 3 | 4 | final varA1 = 'varA1'; 5 | String? varA2; 6 | String varA3 = 'varA3'; 7 | final varB1 = 'varB1'; 8 | String? varB2; 9 | String varB3 = 'varB3'; 10 | final varC1 = 'varC1'; 11 | String? varC2; 12 | String varC3 = 'varC3'; 13 | } 14 | 15 | class User { 16 | User({ 17 | required this.firstNameFinal, 18 | required this.idFinal, 19 | required this.lastNameFinal, 20 | required this.middleNameFinal, 21 | required this.phoneNumberFinal, 22 | required this.usernameFinal, 23 | required this.firstNameRegular, 24 | required this.idRegular, 25 | required this.lastNameRegular, 26 | required this.usernameRegular, 27 | }); 28 | 29 | final String firstNameFinal; 30 | final int idFinal; 31 | final String lastNameFinal; 32 | final String? middleNameFinal; 33 | final String? phoneNumberFinal; 34 | final String usernameFinal; 35 | 36 | String firstNameRegular; 37 | int idRegular; 38 | String lastNameRegular; 39 | String usernameRegular; 40 | 41 | int? ageOptional; 42 | String? birthdateOptional; 43 | String? emailOptional; 44 | String? middleNameRegular; 45 | String? phoneNumberRegular; 46 | 47 | int? _agePrivate; 48 | String? _birthdatePrivate; 49 | String? _emailPrivate; 50 | final String _firstNamePrivate = 'Secret'; 51 | final int _idPrivate = 0; 52 | final String _lastNamePrivate = 'Secret'; 53 | String? _middleNamePrivate; 54 | final String _phoneNumberPrivate = 'Secret'; 55 | final String _usernamePrivate = 'Secret'; 56 | } 57 | -------------------------------------------------------------------------------- /src/test/suite/testfiles/issue31.want.txt: -------------------------------------------------------------------------------- 1 | class SnackbarService { 2 | SnackbarService(); 3 | 4 | final varA1 = 'varA1'; 5 | final varB1 = 'varB1'; 6 | final varC1 = 'varC1'; 7 | 8 | String varA3 = 'varA3'; 9 | String varB3 = 'varB3'; 10 | String varC3 = 'varC3'; 11 | 12 | String? varA2; 13 | String? varB2; 14 | String? varC2; 15 | } 16 | 17 | class User { 18 | User({ 19 | required this.firstNameFinal, 20 | required this.idFinal, 21 | required this.lastNameFinal, 22 | required this.middleNameFinal, 23 | required this.phoneNumberFinal, 24 | required this.usernameFinal, 25 | required this.firstNameRegular, 26 | required this.idRegular, 27 | required this.lastNameRegular, 28 | required this.usernameRegular, 29 | }); 30 | 31 | final String firstNameFinal; 32 | final int idFinal; 33 | final String lastNameFinal; 34 | final String? middleNameFinal; 35 | final String? phoneNumberFinal; 36 | final String usernameFinal; 37 | 38 | String firstNameRegular; 39 | int idRegular; 40 | String lastNameRegular; 41 | String usernameRegular; 42 | 43 | int? ageOptional; 44 | String? birthdateOptional; 45 | String? emailOptional; 46 | String? middleNameRegular; 47 | String? phoneNumberRegular; 48 | 49 | final String _firstNamePrivate = 'Secret'; 50 | final int _idPrivate = 0; 51 | final String _lastNamePrivate = 'Secret'; 52 | final String _phoneNumberPrivate = 'Secret'; 53 | final String _usernamePrivate = 'Secret'; 54 | 55 | int? _agePrivate; 56 | String? _birthdatePrivate; 57 | String? _emailPrivate; 58 | String? _middleNamePrivate; 59 | } 60 | -------------------------------------------------------------------------------- /src/test/suite/utils/update_statusbar.test.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | import * as assert from 'assert' 3 | import * as sinon from 'sinon' 4 | import { afterEach, beforeEach } from 'mocha' 5 | import updateStatusbar from '../../../utils/update_statusbar' 6 | 7 | const getMockEditor = (languageId: string) => { 8 | return { document: { languageId } } as vscode.TextEditor 9 | } 10 | 11 | suite('updateStatusbar()', function() { 12 | let mockButton: vscode.StatusBarItem 13 | let mockButtons: vscode.StatusBarItem[] 14 | let spiedHide: sinon.SinonSpy 15 | let spiedShow: sinon.SinonSpy 16 | 17 | beforeEach(() => { 18 | mockButton = { hide: () => { }, show: () => { } } as vscode.StatusBarItem 19 | mockButtons = [mockButton] 20 | spiedHide = sinon.spy(mockButton, 'hide') 21 | spiedShow = sinon.spy(mockButton, 'show') 22 | }) 23 | 24 | afterEach(() => { 25 | spiedHide.restore() 26 | spiedShow.restore() 27 | }) 28 | 29 | test('btn.hide() called if editor is undefined', function() { 30 | updateStatusbar(undefined, mockButtons) 31 | assert(spiedHide.calledOnce) 32 | assert(spiedShow.notCalled) 33 | }) 34 | 35 | test('btn.hide() called if editor is not "dart"', function() { 36 | const editor = getMockEditor('css') 37 | updateStatusbar(editor, mockButtons) 38 | assert(spiedHide.calledOnce) 39 | assert(spiedShow.notCalled) 40 | }) 41 | 42 | test('btn.show() called if editor is "dart"', function() { 43 | const editor = getMockEditor('dart') 44 | updateStatusbar(editor, mockButtons) 45 | assert(spiedHide.notCalled) 46 | assert(spiedShow.calledOnce) 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /src/test/suite/testfiles/issue19.dart.txt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:ffi'; 6 | import 'dart:typed_data'; 7 | 8 | import 'package:ffi/ffi.dart'; 9 | import 'package:win32/win32.dart'; 10 | 11 | import 'base.dart'; 12 | import 'com/IMetaDataImport2.dart'; 13 | import 'type_aliases.dart'; 14 | 15 | /// A custom (named) attribute. 16 | class CustomAttribute extends TokenObject { 17 | final int modifiedObjectToken; 18 | final int attributeType; 19 | final Uint8List signatureBlob; 20 | 21 | CustomAttribute(IMetaDataImport2 reader, int token, this.modifiedObjectToken, 22 | this.attributeType, this.signatureBlob) 23 | : super(reader, token); 24 | 25 | /// Creates a custom attribute object from its given token. 26 | factory CustomAttribute.fromToken(IMetaDataImport2 reader, int token) { 27 | final ptkObj = calloc(); 28 | final ptkType = calloc(); 29 | final ppBlob = calloc(); 30 | final pcbBlob = calloc(); 31 | 32 | try { 33 | final hr = reader.GetCustomAttributeProps( 34 | token, ptkObj, ptkType, ppBlob, pcbBlob); 35 | if (SUCCEEDED(hr)) { 36 | return CustomAttribute( 37 | reader, 38 | token, 39 | ptkObj.value, 40 | ptkType.value, 41 | Pointer.fromAddress(ppBlob.value) 42 | .asTypedList(pcbBlob.value)); 43 | } else { 44 | throw WindowsException(hr); 45 | } 46 | } finally { 47 | free(pcbBlob); 48 | free(ppBlob); 49 | free(ptkType); 50 | free(ptkObj); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/test/suite/testfiles/issue19_want.txt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:ffi'; 6 | import 'dart:typed_data'; 7 | 8 | import 'package:ffi/ffi.dart'; 9 | import 'package:win32/win32.dart'; 10 | 11 | import 'base.dart'; 12 | import 'com/IMetaDataImport2.dart'; 13 | import 'type_aliases.dart'; 14 | 15 | /// A custom (named) attribute. 16 | class CustomAttribute extends TokenObject { 17 | CustomAttribute(IMetaDataImport2 reader, int token, this.modifiedObjectToken, 18 | this.attributeType, this.signatureBlob) 19 | : super(reader, token); 20 | 21 | /// Creates a custom attribute object from its given token. 22 | factory CustomAttribute.fromToken(IMetaDataImport2 reader, int token) { 23 | final ptkObj = calloc(); 24 | final ptkType = calloc(); 25 | final ppBlob = calloc(); 26 | final pcbBlob = calloc(); 27 | 28 | try { 29 | final hr = reader.GetCustomAttributeProps( 30 | token, ptkObj, ptkType, ppBlob, pcbBlob); 31 | if (SUCCEEDED(hr)) { 32 | return CustomAttribute( 33 | reader, 34 | token, 35 | ptkObj.value, 36 | ptkType.value, 37 | Pointer.fromAddress(ppBlob.value) 38 | .asTypedList(pcbBlob.value)); 39 | } else { 40 | throw WindowsException(hr); 41 | } 42 | } finally { 43 | free(pcbBlob); 44 | free(ppBlob); 45 | free(ptkType); 46 | free(ptkObj); 47 | } 48 | } 49 | 50 | final int attributeType; 51 | final int modifiedObjectToken; 52 | final Uint8List signatureBlob; 53 | } -------------------------------------------------------------------------------- /src/test/suite/dart/separate_private_methods.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 Glenn M. Lewis 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { runFullStylizer, } from './class.test' 18 | 19 | suite('Separate Private Methods', function() { 20 | 21 | test('default behavior', () => { 22 | const source = ` 23 | class MyClass { 24 | myMethod(){}; 25 | 26 | _myMethod(){}; 27 | } 28 | ` 29 | 30 | runFullStylizer(null, source, source, null) 31 | }) 32 | 33 | 34 | test('place private methods before public', () => { 35 | const source = ` 36 | class MyClass { 37 | myMethod(){}; 38 | 39 | _myMethod(){}; 40 | } 41 | ` 42 | 43 | const wantSource = ` 44 | class MyClass { 45 | _myMethod(){}; 46 | 47 | myMethod(){}; 48 | } 49 | ` 50 | 51 | const opts = { 52 | SeparatePrivateMethods: true, 53 | MemberOrdering: [ 54 | 'public-constructor', 55 | 'named-constructors', 56 | 'public-static-variables', 57 | 'public-instance-variables', 58 | 'public-override-variables', 59 | 'public-override-methods', 60 | 'private-other-methods', 61 | 'public-other-methods', 62 | 'private-static-variables', 63 | 'private-instance-variables', 64 | 'build-method', 65 | ], 66 | } 67 | runFullStylizer(opts, source, wantSource, null) 68 | }) 69 | 70 | }) -------------------------------------------------------------------------------- /src/dart/line.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 Glenn M. Lewis 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { EntityType } from './entity' 18 | 19 | // Line represents a line of Dart code. 20 | export class Line { 21 | constructor(line: string, startOffset: number, originalIndex: number) { 22 | this.line = line 23 | if (/\r$/.test(this.line)) { // Process all lines in unix-style. 24 | this.line = this.line.substring(0, this.line.length - 1) 25 | } 26 | this.stripped = line.trim() 27 | this.entityType = this.stripped ? EntityType.Unknown : EntityType.BlankLine 28 | this.strippedOffset = line.indexOf(this.stripped) 29 | 30 | this.originalIndex = originalIndex 31 | 32 | this.startOffset = startOffset 33 | this.endOffset = startOffset + line.length 34 | } 35 | 36 | line: string // original, unmodified line 37 | stripped: string // removes comments and surrounding whitespace 38 | strippedOffset: number // offset to start of stripped line, compared to 'line' 39 | classLevelText = '' // preserved text at braceLevel==1 - Note that this is untrimmed. 40 | 41 | classLevelTextOffsets: number[] = [] // absolute offsets for each character within classLevelText. 42 | 43 | originalIndex: number 44 | 45 | startOffset: number 46 | endOffset: number 47 | entityType: EntityType 48 | 49 | isCommentOrString = false // used when searching for new classes 50 | } 51 | 52 | export const isComment = (line: Line): boolean => 53 | line.entityType === EntityType.SingleLineComment 54 | || line.entityType === EntityType.MultiLineComment 55 | -------------------------------------------------------------------------------- /src/test/suite/testfiles/block_kind.dart.txt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | class BlockKind { 6 | final String name; 7 | 8 | final bool useNameForMissingBlock; 9 | 10 | const BlockKind._(this.name, this.useNameForMissingBlock); 11 | 12 | /// Returns the name to use for this block if it is missing in 13 | /// [templateExpectedClassOrMixinBody]. 14 | /// 15 | /// If `null` the generic [templateExpectedButGot] is used instead. 16 | String get missingBlockName => useNameForMissingBlock ? name : null; 17 | 18 | String toString() => 'BlockKind($name)'; 19 | 20 | static const BlockKind catchClause = 21 | const BlockKind._('catch clause', /* useNameForMissingBlock = */ true); 22 | static const BlockKind classDeclaration = const BlockKind._( 23 | 'class declaration', /* useNameForMissingBlock = */ false); 24 | static const BlockKind enumDeclaration = const BlockKind._( 25 | 'enum declaration', /* useNameForMissingBlock = */ false); 26 | static const BlockKind extensionDeclaration = const BlockKind._( 27 | 'extension declaration', /* useNameForMissingBlock = */ false); 28 | static const BlockKind finallyClause = 29 | const BlockKind._('finally clause', /* useNameForMissingBlock = */ true); 30 | static const BlockKind functionBody = 31 | const BlockKind._('function body', /* useNameForMissingBlock = */ false); 32 | static const BlockKind invalid = 33 | const BlockKind._('invalid', /* useNameForMissingBlock = */ false); 34 | static const BlockKind mixinDeclaration = const BlockKind._( 35 | 'mixin declaration', /* useNameForMissingBlock = */ false); 36 | static const BlockKind statement = 37 | const BlockKind._('statement', /* useNameForMissingBlock = */ false); 38 | static const BlockKind switchStatement = const BlockKind._( 39 | 'switch statement', /* useNameForMissingBlock = */ false); 40 | static const BlockKind tryStatement = 41 | const BlockKind._('try statement', /* useNameForMissingBlock = */ true); 42 | } 43 | -------------------------------------------------------------------------------- /vsc-extension-quickstart.md: -------------------------------------------------------------------------------- 1 | # Welcome to your VS Code Extension 2 | 3 | ## What's in the folder 4 | * This folder contains all of the files necessary for your extension. 5 | * `package.json` - this is the manifest file in which you declare your extension and command. 6 | The sample plugin registers a command and defines its title and command name. With this information 7 | VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. 8 | * `src/extension.ts` - this is the main file where you will provide the implementation of your command. 9 | The file exports one function, `activate`, which is called the very first time your extension is 10 | activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. 11 | We pass the function containing the implementation of the command as the second parameter to 12 | `registerCommand`. 13 | 14 | ## Get up and running straight away 15 | * Press `F5` to open a new window with your extension loaded. 16 | * Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`. 17 | * Set breakpoints in your code inside `src/extension.ts` to debug your extension. 18 | * Find output from your extension in the debug console. 19 | 20 | ## Make changes 21 | * You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`. 22 | * You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. 23 | 24 | ## Explore the API 25 | * You can open the full set of our API when you open the file `node_modules/vscode/vscode.d.ts`. 26 | 27 | ## Run tests 28 | * Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Launch Tests`. 29 | * Press `F5` to run the tests in a new window with your extension loaded. 30 | * See the output of the test result in the debug console. 31 | * Make changes to `test/extension.test.ts` or create new test files inside the `test` folder. 32 | * By convention, the test runner will only consider files matching the name pattern `**.test.ts`. 33 | * You can create folders inside the `test` folder to structure your tests any way you want. 34 | -------------------------------------------------------------------------------- /src/test/suite/testfiles/block_kind_want.txt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | class BlockKind { 6 | const BlockKind._(this.name, this.useNameForMissingBlock); 7 | 8 | static const BlockKind catchClause = 9 | const BlockKind._('catch clause', /* useNameForMissingBlock = */ true); 10 | 11 | static const BlockKind classDeclaration = const BlockKind._( 12 | 'class declaration', /* useNameForMissingBlock = */ false); 13 | 14 | static const BlockKind enumDeclaration = const BlockKind._( 15 | 'enum declaration', /* useNameForMissingBlock = */ false); 16 | 17 | static const BlockKind extensionDeclaration = const BlockKind._( 18 | 'extension declaration', /* useNameForMissingBlock = */ false); 19 | 20 | static const BlockKind finallyClause = 21 | const BlockKind._('finally clause', /* useNameForMissingBlock = */ true); 22 | 23 | static const BlockKind functionBody = 24 | const BlockKind._('function body', /* useNameForMissingBlock = */ false); 25 | 26 | static const BlockKind invalid = 27 | const BlockKind._('invalid', /* useNameForMissingBlock = */ false); 28 | 29 | static const BlockKind mixinDeclaration = const BlockKind._( 30 | 'mixin declaration', /* useNameForMissingBlock = */ false); 31 | 32 | static const BlockKind statement = 33 | const BlockKind._('statement', /* useNameForMissingBlock = */ false); 34 | 35 | static const BlockKind switchStatement = const BlockKind._( 36 | 'switch statement', /* useNameForMissingBlock = */ false); 37 | 38 | static const BlockKind tryStatement = 39 | const BlockKind._('try statement', /* useNameForMissingBlock = */ true); 40 | 41 | final String name; 42 | final bool useNameForMissingBlock; 43 | 44 | /// Returns the name to use for this block if it is missing in 45 | /// [templateExpectedClassOrMixinBody]. 46 | /// 47 | /// If `null` the generic [templateExpectedButGot] is used instead. 48 | String get missingBlockName => useNameForMissingBlock ? name : null; 49 | 50 | String toString() => 'BlockKind($name)'; 51 | } 52 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '19 14 * * 3' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v2 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v1 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flutter-stylizer", 3 | "license": "SEE LICENSE IN LICENSE", 4 | "displayName": "flutter-stylizer", 5 | "description": "Flutter Stylizer organizes your Flutter classes in an opinionated and consistent manner.", 6 | "icon": "images/flutter-stylizer-logo-128x128.png", 7 | "version": "0.1.16", 8 | "publisher": "gmlewis-vscode", 9 | "repository": "https://github.com/gmlewis/flutter-stylizer", 10 | "engines": { 11 | "vscode": "^1.60.0" 12 | }, 13 | "categories": [ 14 | "Other" 15 | ], 16 | "activationEvents": [ 17 | "onCommand:extension.flutterStylizer", 18 | "onLanguage:dart" 19 | ], 20 | "main": "./out/extension", 21 | "contributes": { 22 | "commands": [ 23 | { 24 | "command": "extension.flutterStylizer", 25 | "title": "Flutter Stylizer" 26 | } 27 | ], 28 | "configuration": { 29 | "title": "Flutter Stylizer", 30 | "properties": { 31 | "flutterStylizer": { 32 | "groupAndSortGetterMethods": { 33 | "type": "boolean", 34 | "default": false, 35 | "description": "Whether to group getters separately (before 'public-other-methods') and sort them within their group. (EXPERIMENTAL - use with caution.)" 36 | }, 37 | "groupAndSortVariableTypes": { 38 | "type": "boolean", 39 | "default": false, 40 | "description": " Whether to group public variables separately by type and sort them within their groups. Types are: `final`, `optional` (`?`), and `normal`. (EXPERIMENTAL - use with caution.)" 41 | }, 42 | "memberOrdering": { 43 | "type": "array", 44 | "default": [ 45 | "public-constructor", 46 | "named-constructors", 47 | "public-static-variables", 48 | "public-instance-variables", 49 | "private-static-variables", 50 | "private-instance-variables", 51 | "public-override-methods", 52 | "public-other-methods", 53 | "build-method" 54 | ], 55 | "description": "Ordered list of members to control reordering of source code." 56 | }, 57 | "processEnumsLikeClasses": { 58 | "type": "boolean", 59 | "default": false, 60 | "description": "Whether to process enums identically to how classes are processed. (EXPERIMENTAL - use with caution.)" 61 | }, 62 | "sortClassesWithinFile": { 63 | "type": "boolean", 64 | "default": false, 65 | "description": "Whether to sort multiple classes within each file. (EXPERIMENTAL - use with caution.)" 66 | }, 67 | "sortOtherMethods": { 68 | "type": "boolean", 69 | "default": false, 70 | "description": "Whether to sort the 'public-other-methods' within their group. (EXPERIMENTAL - use with caution.)" 71 | } 72 | } 73 | } 74 | } 75 | }, 76 | "scripts": { 77 | "vscode:prepublish": "npm run compile", 78 | "compile": "tsc -p ./", 79 | "lint": "eslint src --ext ts", 80 | "watch": "tsc -watch -p ./", 81 | "pretest": "npm run compile && npm run lint", 82 | "test": "node ./out/test/runTest.js" 83 | }, 84 | "devDependencies": { 85 | "@types/glob": "^7.1.4", 86 | "@types/mocha": "^9.0.0", 87 | "@types/node": "^16.10.2", 88 | "@types/sinon": "^10.0.4", 89 | "@types/vscode": "^1.60.0", 90 | "@typescript-eslint/eslint-plugin": "^4.33.0", 91 | "@typescript-eslint/parser": "^4.33.0", 92 | "eslint": "^7.32.0", 93 | "glob": "^7.2.0", 94 | "mocha": "^9.1.2", 95 | "nanoid": ">=3.1.31", 96 | "sinon": "^11.1.2", 97 | "tslint": "^5.20.1", 98 | "typescript": "^4.4.3", 99 | "vscode-test": "^1.6.1" 100 | }, 101 | "extensionDependency": [ 102 | "dart-code.dart-code" 103 | ], 104 | "sponsor": { 105 | "url": "https://github.com/sponsors/gmlewis" 106 | } 107 | } -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | import * as vscode from 'vscode' 3 | import buttons from './buttons/buttons' 4 | import createButtons from './utils/create_buttons' 5 | import updateStatusbar from './utils/update_statusbar' 6 | import watchEditors from './utils/watch_editors' 7 | import { defaultMemberOrdering, Client, Options } from './dart/dart' 8 | import { Editor } from './dart/editor' 9 | 10 | const validateMemberOrdering = (config: vscode.WorkspaceConfiguration): string[] => { 11 | const memberOrdering = config.get('memberOrdering') 12 | if (memberOrdering === null || memberOrdering === undefined || 13 | memberOrdering.length < defaultMemberOrdering.length - 1 || 14 | memberOrdering.length > defaultMemberOrdering.length) { 15 | console.log(`flutterStylizer.memberOrdering must have ${defaultMemberOrdering.length - 1} or ${defaultMemberOrdering.length} values. Ignoring and using defaults.`) 16 | return defaultMemberOrdering 17 | } 18 | 19 | const lookup = new Map(defaultMemberOrdering.map((el: string) => [el, true])) 20 | const seen = new Map() 21 | for (let i = 0; i < memberOrdering.length; i++) { 22 | const el = memberOrdering[i] 23 | if (!lookup.get(el)) { 24 | console.log(`Unknown member ${el} in flutterStylizer.memberOrdering. Ignoring and using defaults.`) 25 | return defaultMemberOrdering 26 | } 27 | if (seen.get(el)) { 28 | console.log(`Duplicate member ${el} in flutterStylizer.memberOrdering. Ignoring and using defaults.`) 29 | return defaultMemberOrdering 30 | } 31 | seen.set(el, true) 32 | } 33 | 34 | return memberOrdering 35 | } 36 | 37 | export function activate(context: vscode.ExtensionContext) { 38 | console.log('Congratulations, "Flutter Stylizer" is now active!') 39 | 40 | const disposable = vscode.commands.registerCommand('extension.flutterStylizer', async () => { 41 | const editor = vscode.window.activeTextEditor 42 | if (!editor) { 43 | return // No open text editor 44 | } 45 | const saveSelection = editor.selection 46 | 47 | const config = vscode.workspace.getConfiguration('flutterStylizer') 48 | const memberOrdering = validateMemberOrdering(config) 49 | 50 | const groupAndSortGetterMethods = config.get('groupAndSortGetterMethods') || false 51 | const groupAndSortVariableTypes = config.get('groupAndSortVariableTypes') || false 52 | const processEnumsLikeClasses = config.get('processEnumsLikeClasses') || false 53 | const sortClassesWithinFile = config.get('sortClassesWithinFile') || false 54 | const sortOtherMethods = config.get('sortOtherMethods') || false 55 | 56 | const separatePrivateMethods = memberOrdering.includes('private-other-methods') 57 | 58 | const document = editor.document 59 | const source = document.getText() 60 | 61 | const opts: Options = { 62 | GroupAndSortGetterMethods: groupAndSortGetterMethods, 63 | GroupAndSortVariableTypes: groupAndSortVariableTypes, 64 | MemberOrdering: memberOrdering, 65 | ProcessEnumsLikeClasses: processEnumsLikeClasses, 66 | SortClassesWithinFile: sortClassesWithinFile, 67 | SortOtherMethods: sortOtherMethods, 68 | SeparatePrivateMethods: separatePrivateMethods, 69 | } 70 | 71 | const e = new Editor(source, processEnumsLikeClasses, false) 72 | const c = new Client(e, opts) 73 | const [got, err] = e.getClasses(groupAndSortGetterMethods, separatePrivateMethods) 74 | if (err !== null) { 75 | throw Error(err.message) // Make the compiler happy. 76 | } 77 | 78 | const edits = c.generateEdits(got) 79 | const newBuf = c.rewriteClasses(source, edits) 80 | 81 | const startPos = editor.document.positionAt(0) 82 | const endPos = editor.document.positionAt(source.length) 83 | editor.selection = new vscode.Selection(startPos, endPos) 84 | await editor.edit((editBuilder: vscode.TextEditorEdit) => { 85 | editBuilder.replace(editor.selection, newBuf) 86 | }) 87 | 88 | console.log(`Found ${got.length} classes and stylized ${edits.length}.`) 89 | 90 | editor.selection = saveSelection 91 | }) 92 | 93 | context.subscriptions.push(disposable) 94 | 95 | if (vscode.extensions.getExtension('dart-code.dart-code') !== undefined) { 96 | const statusButtons: vscode.StatusBarItem[] = createButtons(buttons) 97 | watchEditors(statusButtons) 98 | updateStatusbar(vscode.window.activeTextEditor, statusButtons) 99 | } 100 | } 101 | 102 | export function deactivate() { 103 | } 104 | -------------------------------------------------------------------------------- /src/test/suite/dart/issue6.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 Glenn M. Lewis 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import * as vscode from 'vscode' 18 | 19 | import { EntityType } from '../../../dart/entity' 20 | import { runFullStylizer, runParsePhase } from './class.test' 21 | 22 | const fs = require('fs') 23 | const path = require('path') 24 | 25 | suite('Issue#6 Tests', function() { 26 | var myExtDir = (vscode.extensions.getExtension('gmlewis-vscode.flutter-stylizer') || {}).extensionPath || process.env.VSCODE_CWD 27 | const testfilesDir = path.join(myExtDir, 'src', 'test', 'suite', 'testfiles') 28 | 29 | test('Issue#6: case 1', () => { 30 | const groupAndSortGetterMethods = true 31 | const sortOtherMethods = true 32 | 33 | const source = fs.readFileSync(path.join(testfilesDir, 'issue6.dart.txt'), 'utf8') 34 | 35 | const want = [ 36 | EntityType.Unknown, // line #1: { 37 | EntityType.StaticVariable, // line #2: static const kReleaseMode = true; 38 | EntityType.PrivateInstanceVariable, // line #3: final Map _initialValues = kReleaseMode 39 | EntityType.PrivateInstanceVariable, // line #4: ? {} 40 | EntityType.PrivateInstanceVariable, // line #5: : { 41 | EntityType.PrivateInstanceVariable, // line #6: 'hubType': 'test', 42 | EntityType.PrivateInstanceVariable, // line #7: 'ht.localIP': '192.168.1.1', 43 | EntityType.PrivateInstanceVariable, // line #8: 'ht.makerAppID': '2233', 44 | EntityType.PrivateInstanceVariable, // line #9: 'ht.accessToken': '7de3-yyyyyy-xxxxxxxxxxx', 45 | EntityType.PrivateInstanceVariable, // line #10: }; 46 | EntityType.BlankLine, // line #11: 47 | ] 48 | 49 | const memberOrdering = [ 50 | 'public-constructor', 51 | 'named-constructors', 52 | 'public-static-variables', 53 | 'private-static-variables', 54 | 'public-instance-variables', 55 | 'public-override-variables', 56 | 'private-instance-variables', 57 | 'public-override-methods', 58 | 'public-other-methods', 59 | 'private-other-methods', 60 | 'build-method', 61 | ] 62 | 63 | const opts = { 64 | GroupAndSortGetterMethods: groupAndSortGetterMethods, 65 | MemberOrdering: memberOrdering, 66 | SortOtherMethods: sortOtherMethods, 67 | } 68 | 69 | runParsePhase(opts, source, [want]) 70 | }) 71 | 72 | test('Issue#6: case 2', () => { 73 | const groupAndSortGetterMethods = true 74 | const groupAndSortVariableTypes = true 75 | const sortOtherMethods = true 76 | 77 | const source = fs.readFileSync(path.join(testfilesDir, 'issue6.dart.txt'), 'utf8') 78 | const wantSource = fs.readFileSync(path.join(testfilesDir, 'issue6_want.txt'), 'utf8') 79 | 80 | const want = [ 81 | EntityType.Unknown, // line #1: { 82 | EntityType.StaticVariable, // line #2: static const kReleaseMode = true; 83 | EntityType.PrivateInstanceVariable, // line #3: final Map _initialValues = kReleaseMode 84 | EntityType.PrivateInstanceVariable, // line #4: ? {} 85 | EntityType.PrivateInstanceVariable, // line #5: : { 86 | EntityType.PrivateInstanceVariable, // line #6: 'hubType': 'test', 87 | EntityType.PrivateInstanceVariable, // line #7: 'ht.localIP': '192.168.1.1', 88 | EntityType.PrivateInstanceVariable, // line #8: 'ht.makerAppID': '2233', 89 | EntityType.PrivateInstanceVariable, // line #9: 'ht.accessToken': '7de3-yyyyyy-xxxxxxxxxxx', 90 | EntityType.PrivateInstanceVariable, // line #10: }; 91 | EntityType.BlankLine, // line #11: 92 | ] 93 | 94 | const memberOrdering = [ 95 | 'public-constructor', 96 | 'named-constructors', 97 | 'public-static-variables', 98 | 'private-static-variables', 99 | 'public-instance-variables', 100 | 'public-override-variables', 101 | 'private-instance-variables', 102 | 'public-override-methods', 103 | 'public-other-methods', 104 | 'private-other-methods', 105 | 'build-method', 106 | ] 107 | 108 | const opts = { 109 | GroupAndSortGetterMethods: groupAndSortGetterMethods, 110 | GroupAndSortVariableTypes: groupAndSortVariableTypes, 111 | MemberOrdering: memberOrdering, 112 | SortOtherMethods: sortOtherMethods, 113 | } 114 | 115 | runFullStylizer(opts, source, wantSource, [want]) 116 | }) 117 | }) 118 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "flutter-stylizer" extension will be documented in 4 | this file. 5 | 6 | Please make sure you have a backup (preferably in git) of your code before running 7 | "Flutter Stylizer" in case it doesn't handle your code properly. 8 | 9 | ## [0.1.16] - 2022-06-28 10 | 11 | - Fix [issue #31](https://github.com/gmlewis/flutter-stylizer/issues/31) for private vars. 12 | 13 | ## [0.1.15] - 2022-06-27 14 | 15 | - Add new option: 16 | - `processEnumsLikeClasses` (default: `false`) 17 | 18 | ## [0.1.14] - 2022-06-23 19 | 20 | - Fix sort order of class names when `sortClassesWithinFile: true`. 21 | 22 | ## [0.1.13] - 2022-06-23 23 | 24 | - Fix [issue #8](https://github.com/gmlewis/go-flutter-stylizer/issues/8). 25 | 26 | ## [0.1.12] - 2022-06-22 27 | 28 | - Add new option [issue #8](https://github.com/gmlewis/go-flutter-stylizer/issues/8): 29 | - `sortClassesWithinFile` (default: `false`) 30 | 31 | ## [0.1.11] - 2022-06-22 32 | 33 | - Fix [issue #6](https://github.com/gmlewis/go-flutter-stylizer/issues/6). 34 | 35 | ## [0.1.10] - 2022-06-17 36 | 37 | - Update vsce version to 2.9.1. 38 | 39 | ## [0.1.9] - 2022-06-17 40 | 41 | - Add sponsorship ability. 42 | 43 | ## [0.1.8] - 2022-06-14 44 | 45 | - Add new option [issue #31](https://github.com/gmlewis/flutter-stylizer/issues/31): 46 | - `groupAndSortVariableTypes` (default: `false`) 47 | 48 | ## [0.1.7] - 2022-01-12 49 | 50 | - Fix [issue #26](https://github.com/gmlewis/flutter-stylizer/issues/26) caused by `Function()`. 51 | 52 | ## [0.1.6] - 2022-01-08 53 | 54 | - Process all `mixin` blocks in addition to all `class` blocks. 55 | 56 | ## [0.1.5] - 2021-12-12 57 | 58 | - `private-other-methods` can optionally be added to the member ordering. 59 | 60 | ## [0.1.3] - 2021-10-06 61 | 62 | - Add plugin icon image. 63 | 64 | ## [0.1.2] - 2021-10-05 65 | 66 | - Update dependencies. 67 | 68 | ## [0.1.1] - 2021-04-29 69 | 70 | - Complete rewrite of Dart parser to identically match output from 71 | standalone [Go flutter-stylizer](https://github.com/gmlewis/go-flutter-stylizer). 72 | This VSCode plugin can now be used in the same CI/CD projects with 73 | the standalone `flutter-stylizer`. 74 | 75 | ## [0.0.21] - 2021-04-17 76 | 77 | - Fix incorrectly identified NamedConstructor bug reported in #20. 78 | 79 | ## [0.0.20] - 2021-04-17 80 | 81 | - Fix plugin broken on Windows bug reported in #19. 82 | 83 | ## [0.0.19] - 2021-04-15 84 | 85 | - Add two new configuration booleans for experimental features, 86 | requested in #18. Please use these features with caution and 87 | file any bugs you find on GitHub. 88 | - `groupAndSortGetterMethods` (default: `false`) 89 | - `sortOtherMethods` (default: `false`) 90 | 91 | ## [0.0.18] - 2021-04-15 92 | 93 | - Fix incorrectly-identified Function-type variable reported in #17. 94 | 95 | ## [0.0.17] - 2021-04-14 96 | 97 | - Breaking change: 98 | Add `"public-override-variables"` configuration property to allow 99 | customization of `@override` variables ordering, requested in #16. 100 | You will need to add this new property to your `flutterStylizer.memberOrdering`, 101 | otherwise it will use the default built-in ordering. 102 | 103 | ## [0.0.16] - 2020-04-05 104 | 105 | - Add `flutterStylizer.memberOrdering` configuration property to allow 106 | customization of member ordering, requested in #11. 107 | 108 | ## [0.0.15] - 2020-02-07 109 | 110 | - Fix incorrectly-identified named constructor reported in #9. 111 | 112 | ## [0.0.14] - 2019-07-14 113 | 114 | - Upgrade lodash to fix security vulnerability in #8. 115 | 116 | ## [0.0.13] - 2019-07-09 117 | - Adds a statusbar button (on the lower left) to run the stylizer command on the current file. 118 | The button appears whenever an editor with the language type `dart` is the active editor. 119 | This is accomplished with a language-based "activation event" for "flutter-stylizer". 120 | - Adds an extension dependency to the Dart extension (this adds `dart` as an editor language). 121 | - This feature was generously added by @sketchbuch in #7. 122 | 123 | ## [0.0.12] - 2019-07-02 124 | - Incorporate vulnerability fixes from #3, #4, and #5. 125 | 126 | ## [0.0.11] - 2019-03-22 127 | - Fix bugs running on flutter package. 128 | 129 | ## [0.0.10] - 2019-03-22 130 | - Fix await bug. 131 | 132 | ## [0.0.9] - 2018-12-08 133 | - npm update vscode. 134 | 135 | ## [0.0.8] - 2018-11-22 136 | - Improve spacing for single-line variables. 137 | 138 | ## [0.0.7] - 2018-11-20 139 | - Fix instance variable bug. 140 | 141 | ## [0.0.6] - 2018-11-20 142 | - Keep groups of comments associated with following entity. 143 | 144 | ## [0.0.5] - 2018-11-18 145 | - Fix bugs found with abstract classes, getters, and @overrides. 146 | 147 | ## [0.0.4] - 2018-11-10 148 | - Add support for missing getters. 149 | 150 | ## [0.0.3] - 2018-11-10 151 | - Fix placement of getters with other methods. 152 | 153 | ## [0.0.2] - 2018-11-10 154 | - Preserve solitary single- and multi-line comments. 155 | 156 | ## [0.0.1] - 2018-11-10 157 | - Initial release, "Flutter Stylizer" is the provided command. 158 | -------------------------------------------------------------------------------- /src/test/suite/dart/issue26.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 Glenn M. Lewis 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import * as vscode from 'vscode' 18 | 19 | import { EntityType } from '../../../dart/entity' 20 | import { runFullStylizer } from './class.test' 21 | 22 | const fs = require('fs') 23 | const path = require('path') 24 | 25 | suite('Issue#26 Tests', function() { 26 | var myExtDir = (vscode.extensions.getExtension('gmlewis-vscode.flutter-stylizer') || {}).extensionPath || process.env.VSCODE_CWD 27 | const testfilesDir = path.join(myExtDir, 'src', 'test', 'suite', 'testfiles') 28 | 29 | test('Issue#26: case 1', () => { 30 | const groupAndSortGetterMethods = true 31 | const sortOtherMethods = true 32 | 33 | const source = fs.readFileSync(path.join(testfilesDir, 'issue26_case1.dart.txt'), 'utf8') 34 | const wantSource = fs.readFileSync(path.join(testfilesDir, 'issue26_case1.want.txt'), 'utf8') 35 | 36 | const want = [ 37 | EntityType.Unknown, // line #5: { 38 | EntityType.MainConstructor, // line #6: SnackbarService(); 39 | EntityType.BlankLine, // line #7: 40 | EntityType.PrivateInstanceVariable, // line #8: final Map _snackbars = {}; 41 | EntityType.OtherMethod, // line #9: SnackBar Function(SnackBarConfigBase) getSnackbar(dynamic key) { 42 | EntityType.OtherMethod, // line #10: return _snackbars[key]!; 43 | EntityType.OtherMethod, // line #11: } 44 | EntityType.BlankLine, // line #12: 45 | EntityType.InstanceVariable, // line #13: final GlobalKey snackbarKey = GlobalKey(); 46 | EntityType.BlankLine, // line #14: 47 | EntityType.OtherMethod, // line #15: bool containsKey(dynamic key) { 48 | EntityType.OtherMethod, // line #16: return _snackbars.containsKey(key); 49 | EntityType.OtherMethod, // line #17: } 50 | EntityType.BlankLine, // line #18: 51 | EntityType.OtherMethod, // line #19: void registerSnackbar( 52 | EntityType.OtherMethod, // line #20: {required dynamic key, required SnackBar Function(SnackBarConfigBase) snackbarBuilder}) { 53 | EntityType.OtherMethod, // line #21: _snackbars[key] = snackbarBuilder; 54 | EntityType.OtherMethod, // line #22: } 55 | EntityType.BlankLine, // line #23: 56 | ] 57 | 58 | const memberOrdering = [ 59 | 'public-constructor', 60 | 'named-constructors', 61 | 'public-static-variables', 62 | 'private-static-variables', 63 | 'public-instance-variables', 64 | 'public-override-variables', 65 | 'private-instance-variables', 66 | 'public-override-methods', 67 | 'public-other-methods', 68 | 'private-other-methods', 69 | 'build-method', 70 | ] 71 | 72 | const opts = { 73 | GroupAndSortGetterMethods: groupAndSortGetterMethods, 74 | MemberOrdering: memberOrdering, 75 | SortOtherMethods: sortOtherMethods, 76 | } 77 | 78 | runFullStylizer(opts, source, wantSource, [want]) 79 | }) 80 | 81 | test('Issue#26: case 2', () => { 82 | const groupAndSortGetterMethods = true 83 | const sortOtherMethods = true 84 | 85 | const source = fs.readFileSync(path.join(testfilesDir, 'issue26_case2.dart.txt'), 'utf8') 86 | const wantSource = fs.readFileSync(path.join(testfilesDir, 'issue26_case2.want.txt'), 'utf8') 87 | 88 | const want = [ 89 | EntityType.Unknown, // line #1: { 90 | EntityType.PrivateInstanceVariable, // line #2: final Map _dialogs = {}; 91 | EntityType.PrivateInstanceVariable, // line #3: late Function(DialogRequest) _dialogHandler; 92 | EntityType.BlankLine, // line #4: 93 | EntityType.PrivateInstanceVariable, // line #5: late Completer? _dialogCompleter; 94 | EntityType.PrivateInstanceVariable, // line #6: late final NavigatorService _navigatorService; 95 | EntityType.BlankLine, // line #7: 96 | ] 97 | 98 | const memberOrdering = [ 99 | 'public-constructor', 100 | 'named-constructors', 101 | 'public-static-variables', 102 | 'private-static-variables', 103 | 'public-instance-variables', 104 | 'public-override-variables', 105 | 'private-instance-variables', 106 | 'public-override-methods', 107 | 'public-other-methods', 108 | 'private-other-methods', 109 | 'build-method', 110 | ] 111 | 112 | const opts = { 113 | GroupAndSortGetterMethods: groupAndSortGetterMethods, 114 | MemberOrdering: memberOrdering, 115 | SortOtherMethods: sortOtherMethods, 116 | } 117 | 118 | runFullStylizer(opts, source, wantSource, [want]) 119 | }) 120 | }) 121 | -------------------------------------------------------------------------------- /src/test/suite/dart/block_kind.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 Glenn M. Lewis 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import * as vscode from 'vscode' 18 | 19 | import { EntityType } from '../../../dart/entity' 20 | import { runFullStylizer } from './class.test' 21 | 22 | const fs = require('fs') 23 | const path = require('path') 24 | 25 | suite('Block Kind Tests', function() { 26 | var myExtDir = (vscode.extensions.getExtension('gmlewis-vscode.flutter-stylizer') || {}).extensionPath || process.env.VSCODE_CWD 27 | const testfilesDir = path.join(myExtDir, 'src', 'test', 'suite', 'testfiles') 28 | 29 | test('Block kind example', () => { 30 | const source = fs.readFileSync(path.join(testfilesDir, 'block_kind.dart.txt'), 'utf8') 31 | const wantSource = fs.readFileSync(path.join(testfilesDir, 'block_kind_want.txt'), 'utf8') 32 | 33 | const want = [ 34 | EntityType.Unknown, // line #5: { 35 | EntityType.InstanceVariable, // line #6: final String name; 36 | EntityType.BlankLine, // line #7: 37 | EntityType.InstanceVariable, // line #8: final bool useNameForMissingBlock; 38 | EntityType.BlankLine, // line #9: 39 | EntityType.NamedConstructor, // line #10: const BlockKind._(this.name, this.useNameForMissingBlock); 40 | EntityType.BlankLine, // line #11: 41 | EntityType.OtherMethod, // line #12: /// Returns the name to use for this block if it is missing in 42 | EntityType.OtherMethod, // line #13: /// [templateExpectedClassOrMixinBody]. 43 | EntityType.OtherMethod, // line #14: /// 44 | EntityType.OtherMethod, // line #15: /// If `null` the generic [templateExpectedButGot] is used instead. 45 | EntityType.OtherMethod, // line #16: String get missingBlockName => useNameForMissingBlock ? name : null; 46 | EntityType.BlankLine, // line #17: 47 | EntityType.OtherMethod, // line #18: String toString() => 'BlockKind($name)'; 48 | EntityType.BlankLine, // line #19: 49 | EntityType.StaticVariable, // line #20: static const BlockKind catchClause = 50 | EntityType.StaticVariable, // line #21: const BlockKind._('catch clause', /* useNameForMissingBlock = */ true); 51 | EntityType.StaticVariable, // line #22: static const BlockKind classDeclaration = const BlockKind._( 52 | EntityType.StaticVariable, // line #23: 'class declaration', /* useNameForMissingBlock = */ false); 53 | EntityType.StaticVariable, // line #24: static const BlockKind enumDeclaration = const BlockKind._( 54 | EntityType.StaticVariable, // line #25: 'enum declaration', /* useNameForMissingBlock = */ false); 55 | EntityType.StaticVariable, // line #26: static const BlockKind extensionDeclaration = const BlockKind._( 56 | EntityType.StaticVariable, // line #27: 'extension declaration', /* useNameForMissingBlock = */ false); 57 | EntityType.StaticVariable, // line #28: static const BlockKind finallyClause = 58 | EntityType.StaticVariable, // line #29: const BlockKind._('finally clause', /* useNameForMissingBlock = */ true); 59 | EntityType.StaticVariable, // line #30: static const BlockKind functionBody = 60 | EntityType.StaticVariable, // line #31: const BlockKind._('function body', /* useNameForMissingBlock = */ false); 61 | EntityType.StaticVariable, // line #32: static const BlockKind invalid = 62 | EntityType.StaticVariable, // line #33: const BlockKind._('invalid', /* useNameForMissingBlock = */ false); 63 | EntityType.StaticVariable, // line #34: static const BlockKind mixinDeclaration = const BlockKind._( 64 | EntityType.StaticVariable, // line #35: 'mixin declaration', /* useNameForMissingBlock = */ false); 65 | EntityType.StaticVariable, // line #36: static const BlockKind statement = 66 | EntityType.StaticVariable, // line #37: const BlockKind._('statement', /* useNameForMissingBlock = */ false); 67 | EntityType.StaticVariable, // line #38: static const BlockKind switchStatement = const BlockKind._( 68 | EntityType.StaticVariable, // line #39: 'switch statement', /* useNameForMissingBlock = */ false); 69 | EntityType.StaticVariable, // line #40: static const BlockKind tryStatement = 70 | EntityType.StaticVariable, // line #41: const BlockKind._('try statement', /* useNameForMissingBlock = */ true); 71 | EntityType.BlankLine, // line #42: 72 | ] 73 | 74 | runFullStylizer(null, source, wantSource, [want]) 75 | }) 76 | 77 | test('Block kind example stays the same', () => { 78 | const wantSource = fs.readFileSync(path.join(testfilesDir, 'block_kind_want.txt'), 'utf8') 79 | 80 | runFullStylizer(null, wantSource, wantSource, null) 81 | }) 82 | }) 83 | -------------------------------------------------------------------------------- /src/dart/editor.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 Glenn M. Lewis 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliee. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { Class } from './class' 18 | import { Cursor } from './cursor' 19 | import { Line } from './line' 20 | import { MatchingPairsMap } from './pairs' 21 | 22 | 23 | // Editor represents a text editor that understands Dart syntax. 24 | export class Editor { 25 | constructor(buf: string, processEnumsLikeClasses: boolean, verbose: boolean) { 26 | this.fullBuf = buf 27 | this.verbose = verbose 28 | this.classMatcher = processEnumsLikeClasses ? Class.matchClassOrMixinOrEnumRE : Class.matchClassOrMixinRE 29 | 30 | const lines = this.fullBuf.split('\n') 31 | let lineStartOffset = 0 32 | lines.forEach((line, i) => { 33 | this.lines.push(new Line(line, lineStartOffset, i)) 34 | lineStartOffset += line.length + 1 35 | }) 36 | 37 | const cursor = new Cursor(this) 38 | const err = cursor.parse(this.matchingPairs) 39 | if (err) { 40 | throw new Error(`parse: ${err}`) 41 | } 42 | 43 | this.eofOffset = cursor.absOffset 44 | this.classLineIndices = cursor.classLineIndices 45 | } 46 | 47 | fullBuf: string 48 | lines: Line[] = [] 49 | eofOffset: number 50 | 51 | matchingPairs: MatchingPairsMap = {} 52 | // classLineIndices contains line indices where a class or abstract class starts. 53 | classLineIndices: number[] = [] 54 | 55 | classMatcher: RegExp 56 | 57 | verbose: boolean 58 | 59 | getClasses(groupAndSortGetterMethods: boolean, separatePrivateMethods: boolean): [Class[], Error | null] { 60 | const classes: Class[] = [] 61 | 62 | for (let i = 0; i < this.classLineIndices.length; i++) { 63 | const lineIndex = this.classLineIndices[i] 64 | const line = this.lines[lineIndex] 65 | const mm = this.classMatcher.exec(line.line) 66 | if (!mm || mm.length !== 3) { 67 | return [[], Error(`programming error: expected class on line #${lineIndex + 1}, got '${line.line}'`)] 68 | } 69 | 70 | const classType = mm[1] 71 | const className = mm[2] 72 | const classOffset = line.startOffset 73 | const openCurlyOffset = this.findStartOfClass(classOffset) 74 | if (this.fullBuf[openCurlyOffset] === ';') { // this is valid and can be ignored: class D = Object with Function; 75 | continue 76 | } 77 | 78 | this.logf(`\n\nFound new ${classType} '${className}' at classOffset=${classOffset}, openCurlyOffset=${openCurlyOffset}, line=${line.line}`) 79 | const pair = this.matchingPairs[openCurlyOffset] 80 | if (!pair) { 81 | return [[], Error(`programming error: no matching pair found at openCurlyOffset ${openCurlyOffset}`)] 82 | } 83 | 84 | const closeCurlyOffset = pair.closeAbsOffset 85 | this.logf(`\n\nFound end of ${classType} '${className}' at closeCurlyOffset=${closeCurlyOffset}`) 86 | 87 | const dartClass = new Class(this, classType, className, openCurlyOffset, closeCurlyOffset, groupAndSortGetterMethods, separatePrivateMethods) 88 | const err = dartClass.findFeatures() 89 | if (err !== null) { 90 | return [[], err] 91 | } 92 | 93 | classes.push(dartClass) 94 | } 95 | 96 | return [classes, null] 97 | } 98 | 99 | // findStartOfClass returns the absolute offset of the next top-level '{' or ';' 100 | // starting at offset startOffset. 101 | // 102 | // Note that this class definition is valid: "class D = Object with Function;" 103 | findStartOfClass(startOffset: number) { 104 | while (startOffset < this.eofOffset) { 105 | if (this.fullBuf[startOffset] === '{' || this.fullBuf[startOffset] === ';') { 106 | return startOffset 107 | } 108 | const pair = this.matchingPairs[startOffset] 109 | if (pair) { 110 | startOffset = pair.closeAbsOffset + 1 111 | continue 112 | } 113 | startOffset++ 114 | } 115 | 116 | throw new Error(`programming error: findStartOfClass(${startOffset}) should not reach here`) 117 | } 118 | 119 | // findLineIndexAtOffset finds the line index and relative offset for the 120 | // absolute offset within the text buffer. Note that the returned 121 | // relStrippedOffset may be greater than the length of the stripped string 122 | // or even negative, before the stripped string. 123 | findLineIndexAtOffset(absOffset: number) { 124 | for (let i = 0; i < this.lines.length; i++) { 125 | const line = this.lines[i] 126 | if (absOffset <= line.endOffset) { 127 | const relStrippedOffset = absOffset - line.startOffset - line.strippedOffset 128 | return [i, relStrippedOffset] 129 | } 130 | } 131 | return [this.lines.length, 0] 132 | } 133 | 134 | // findClassAbsoluteStart finds the very first absolute offset either at the 135 | // beginning of the buffer or after the last blank line before a class is defined. 136 | findClassAbsoluteStart(dc: Class) { 137 | let result = dc.openCurlyOffset 138 | for (; result > 0; result--) { 139 | if (result > 1 && this.fullBuf.substring(result - 2, result) === "\n\n") { break } 140 | if (result > 3 && this.fullBuf.substring(result - 4, result) === "\r\n\r\n") { break } 141 | } 142 | return result 143 | } 144 | 145 | // logf logs the line if verbose is true. 146 | logf(s: string) { 147 | if (this.verbose) { 148 | console.log(s) 149 | } 150 | } 151 | } -------------------------------------------------------------------------------- /src/test/suite/testfiles/pubspec_want.txt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:checked_yaml/checked_yaml.dart'; 6 | import 'package:json_annotation/json_annotation.dart'; 7 | import 'package:pub_semver/pub_semver.dart'; 8 | 9 | import 'dependency.dart'; 10 | 11 | part 'pubspec.g.dart'; 12 | 13 | @JsonSerializable() 14 | class Pubspec { 15 | /// If [author] and [authors] are both provided, their values are combined 16 | /// with duplicates eliminated. 17 | Pubspec( 18 | this.name, { 19 | this.version, 20 | this.publishTo, 21 | String author, 22 | List authors, 23 | Map environment, 24 | this.homepage, 25 | this.repository, 26 | this.issueTracker, 27 | this.documentation, 28 | this.description, 29 | Map dependencies, 30 | Map devDependencies, 31 | Map dependencyOverrides, 32 | this.flutter, 33 | }) : authors = _normalizeAuthors(author, authors), 34 | environment = environment ?? const {}, 35 | dependencies = dependencies ?? const {}, 36 | devDependencies = devDependencies ?? const {}, 37 | dependencyOverrides = dependencyOverrides ?? const {} { 38 | if (name == null || name.isEmpty) { 39 | throw ArgumentError.value(name, 'name', '"name" cannot be empty.'); 40 | } 41 | 42 | if (publishTo != null && publishTo != 'none') { 43 | try { 44 | final targetUri = Uri.parse(publishTo); 45 | if (!(targetUri.isScheme('http') || targetUri.isScheme('https'))) { 46 | throw const FormatException('Must be an http or https URL.'); 47 | } 48 | } on FormatException catch (e) { 49 | throw ArgumentError.value(publishTo, 'publishTo', e.message); 50 | } 51 | } 52 | } 53 | 54 | factory Pubspec.fromJson(Map json, {bool lenient = false}) { 55 | lenient ??= false; 56 | 57 | if (lenient) { 58 | while (json.isNotEmpty) { 59 | // Attempting to remove top-level properties that cause parsing errors. 60 | try { 61 | return _$PubspecFromJson(json); 62 | } on CheckedFromJsonException catch (e) { 63 | if (e.map == json && json.containsKey(e.key)) { 64 | json = Map.from(json)..remove(e.key); 65 | continue; 66 | } 67 | rethrow; 68 | } 69 | } 70 | } 71 | 72 | return _$PubspecFromJson(json); 73 | } 74 | 75 | /// Parses source [yaml] into [Pubspec]. 76 | /// 77 | /// When [lenient] is set, top-level property-parsing or type cast errors are 78 | /// ignored and `null` values are returned. 79 | factory Pubspec.parse(String yaml, {sourceUrl, bool lenient = false}) { 80 | lenient ??= false; 81 | 82 | return checkedYamlDecode( 83 | yaml, (map) => Pubspec.fromJson(map, lenient: lenient), 84 | sourceUrl: sourceUrl); 85 | } 86 | 87 | final List authors; 88 | @JsonKey(fromJson: parseDeps, nullable: false) 89 | final Map dependencies; 90 | 91 | @JsonKey(fromJson: parseDeps, nullable: false) 92 | final Map dependencyOverrides; 93 | 94 | final String description; 95 | @JsonKey(fromJson: parseDeps, nullable: false) 96 | final Map devDependencies; 97 | 98 | final String documentation; 99 | @JsonKey(fromJson: _environmentMap) 100 | final Map environment; 101 | 102 | /// Optional configuration specific to [Flutter](https://flutter.io/) 103 | /// packages. 104 | /// 105 | /// May include 106 | /// [assets](https://flutter.io/docs/development/ui/assets-and-images) 107 | /// and other settings. 108 | final Map flutter; 109 | 110 | /// This should be a URL pointing to the website for the package. 111 | final String homepage; 112 | 113 | /// Optional field to a web page where developers can report new issues or 114 | /// view existing ones. 115 | final Uri issueTracker; 116 | 117 | // TODO: executables 118 | 119 | final String name; 120 | 121 | /// Specifies where to publish this package. 122 | /// 123 | /// Accepted values: `null`, `'none'` or an `http` or `https` URL. 124 | /// 125 | /// [More information](https://dart.dev/tools/pub/pubspec#publish_to). 126 | final String publishTo; 127 | 128 | /// Optional field to specify the source code repository of the package. 129 | /// Useful when a package has both a home page and a repository. 130 | final Uri repository; 131 | 132 | @JsonKey(fromJson: _versionFromString) 133 | final Version version; 134 | 135 | /// If there is exactly 1 value in [authors], returns it. 136 | /// 137 | /// If there are 0 or more than 1, returns `null`. 138 | @Deprecated( 139 | 'Here for completeness, but not recommended. Use `authors` instead.') 140 | String get author { 141 | if (authors.length == 1) { 142 | return authors.single; 143 | } 144 | return null; 145 | } 146 | 147 | static List _normalizeAuthors(String author, List authors) { 148 | final value = {}; 149 | if (author != null) { 150 | value.add(author); 151 | } 152 | if (authors != null) { 153 | value.addAll(authors); 154 | } 155 | return value.toList(); 156 | } 157 | } 158 | 159 | Version _versionFromString(String input) => 160 | input == null ? null : Version.parse(input); 161 | 162 | Map _environmentMap(Map source) => 163 | source?.map((k, value) { 164 | final key = k as String; 165 | if (key == 'dart') { 166 | // github.com/dart-lang/pub/blob/d84173eeb03c3/lib/src/pubspec.dart#L342 167 | // 'dart' is not allowed as a key! 168 | throw CheckedFromJsonException( 169 | source, 170 | 'dart', 171 | 'VersionConstraint', 172 | 'Use "sdk" to for Dart SDK constraints.', 173 | badKey: true, 174 | ); 175 | } 176 | 177 | VersionConstraint constraint; 178 | if (value == null) { 179 | constraint = null; 180 | } else if (value is String) { 181 | try { 182 | constraint = VersionConstraint.parse(value); 183 | } on FormatException catch (e) { 184 | throw CheckedFromJsonException(source, key, 'Pubspec', e.message); 185 | } 186 | 187 | return MapEntry(key, constraint); 188 | } else { 189 | throw CheckedFromJsonException( 190 | source, key, 'VersionConstraint', '`$value` is not a String.'); 191 | } 192 | 193 | return MapEntry(key, constraint); 194 | }); 195 | -------------------------------------------------------------------------------- /src/test/suite/testfiles/pubspec.dart.txt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:checked_yaml/checked_yaml.dart'; 6 | import 'package:json_annotation/json_annotation.dart'; 7 | import 'package:pub_semver/pub_semver.dart'; 8 | 9 | import 'dependency.dart'; 10 | 11 | part 'pubspec.g.dart'; 12 | 13 | @JsonSerializable() 14 | class Pubspec { 15 | // TODO: executables 16 | 17 | final String name; 18 | 19 | @JsonKey(fromJson: _versionFromString) 20 | final Version version; 21 | 22 | final String description; 23 | 24 | /// This should be a URL pointing to the website for the package. 25 | final String homepage; 26 | 27 | /// Specifies where to publish this package. 28 | /// 29 | /// Accepted values: `null`, `'none'` or an `http` or `https` URL. 30 | /// 31 | /// [More information](https://dart.dev/tools/pub/pubspec#publish_to). 32 | final String publishTo; 33 | 34 | /// Optional field to specify the source code repository of the package. 35 | /// Useful when a package has both a home page and a repository. 36 | final Uri repository; 37 | 38 | /// Optional field to a web page where developers can report new issues or 39 | /// view existing ones. 40 | final Uri issueTracker; 41 | 42 | /// If there is exactly 1 value in [authors], returns it. 43 | /// 44 | /// If there are 0 or more than 1, returns `null`. 45 | @Deprecated( 46 | 'Here for completeness, but not recommended. Use `authors` instead.') 47 | String get author { 48 | if (authors.length == 1) { 49 | return authors.single; 50 | } 51 | return null; 52 | } 53 | 54 | final List authors; 55 | final String documentation; 56 | 57 | @JsonKey(fromJson: _environmentMap) 58 | final Map environment; 59 | 60 | @JsonKey(fromJson: parseDeps, nullable: false) 61 | final Map dependencies; 62 | 63 | @JsonKey(fromJson: parseDeps, nullable: false) 64 | final Map devDependencies; 65 | 66 | @JsonKey(fromJson: parseDeps, nullable: false) 67 | final Map dependencyOverrides; 68 | 69 | /// Optional configuration specific to [Flutter](https://flutter.io/) 70 | /// packages. 71 | /// 72 | /// May include 73 | /// [assets](https://flutter.io/docs/development/ui/assets-and-images) 74 | /// and other settings. 75 | final Map flutter; 76 | 77 | /// If [author] and [authors] are both provided, their values are combined 78 | /// with duplicates eliminated. 79 | Pubspec( 80 | this.name, { 81 | this.version, 82 | this.publishTo, 83 | String author, 84 | List authors, 85 | Map environment, 86 | this.homepage, 87 | this.repository, 88 | this.issueTracker, 89 | this.documentation, 90 | this.description, 91 | Map dependencies, 92 | Map devDependencies, 93 | Map dependencyOverrides, 94 | this.flutter, 95 | }) : authors = _normalizeAuthors(author, authors), 96 | environment = environment ?? const {}, 97 | dependencies = dependencies ?? const {}, 98 | devDependencies = devDependencies ?? const {}, 99 | dependencyOverrides = dependencyOverrides ?? const {} { 100 | if (name == null || name.isEmpty) { 101 | throw ArgumentError.value(name, 'name', '"name" cannot be empty.'); 102 | } 103 | 104 | if (publishTo != null && publishTo != 'none') { 105 | try { 106 | final targetUri = Uri.parse(publishTo); 107 | if (!(targetUri.isScheme('http') || targetUri.isScheme('https'))) { 108 | throw const FormatException('Must be an http or https URL.'); 109 | } 110 | } on FormatException catch (e) { 111 | throw ArgumentError.value(publishTo, 'publishTo', e.message); 112 | } 113 | } 114 | } 115 | 116 | factory Pubspec.fromJson(Map json, {bool lenient = false}) { 117 | lenient ??= false; 118 | 119 | if (lenient) { 120 | while (json.isNotEmpty) { 121 | // Attempting to remove top-level properties that cause parsing errors. 122 | try { 123 | return _$PubspecFromJson(json); 124 | } on CheckedFromJsonException catch (e) { 125 | if (e.map == json && json.containsKey(e.key)) { 126 | json = Map.from(json)..remove(e.key); 127 | continue; 128 | } 129 | rethrow; 130 | } 131 | } 132 | } 133 | 134 | return _$PubspecFromJson(json); 135 | } 136 | 137 | /// Parses source [yaml] into [Pubspec]. 138 | /// 139 | /// When [lenient] is set, top-level property-parsing or type cast errors are 140 | /// ignored and `null` values are returned. 141 | factory Pubspec.parse(String yaml, {sourceUrl, bool lenient = false}) { 142 | lenient ??= false; 143 | 144 | return checkedYamlDecode( 145 | yaml, (map) => Pubspec.fromJson(map, lenient: lenient), 146 | sourceUrl: sourceUrl); 147 | } 148 | 149 | static List _normalizeAuthors(String author, List authors) { 150 | final value = {}; 151 | if (author != null) { 152 | value.add(author); 153 | } 154 | if (authors != null) { 155 | value.addAll(authors); 156 | } 157 | return value.toList(); 158 | } 159 | } 160 | 161 | Version _versionFromString(String input) => 162 | input == null ? null : Version.parse(input); 163 | 164 | Map _environmentMap(Map source) => 165 | source?.map((k, value) { 166 | final key = k as String; 167 | if (key == 'dart') { 168 | // github.com/dart-lang/pub/blob/d84173eeb03c3/lib/src/pubspec.dart#L342 169 | // 'dart' is not allowed as a key! 170 | throw CheckedFromJsonException( 171 | source, 172 | 'dart', 173 | 'VersionConstraint', 174 | 'Use "sdk" to for Dart SDK constraints.', 175 | badKey: true, 176 | ); 177 | } 178 | 179 | VersionConstraint constraint; 180 | if (value == null) { 181 | constraint = null; 182 | } else if (value is String) { 183 | try { 184 | constraint = VersionConstraint.parse(value); 185 | } on FormatException catch (e) { 186 | throw CheckedFromJsonException(source, key, 'Pubspec', e.message); 187 | } 188 | 189 | return MapEntry(key, constraint); 190 | } else { 191 | throw CheckedFromJsonException( 192 | source, key, 'VersionConstraint', '`$value` is not a String.'); 193 | } 194 | 195 | return MapEntry(key, constraint); 196 | }); 197 | -------------------------------------------------------------------------------- /src/test/suite/testfiles/scope.dart.txt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:analyzer/dart/element/element.dart'; 6 | import 'package:analyzer/dart/element/scope.dart'; 7 | import 'package:analyzer/src/context/source.dart'; 8 | import 'package:analyzer/src/dart/element/element.dart'; 9 | import 'package:analyzer/src/dart/resolver/scope.dart' as impl; 10 | import 'package:meta/meta.dart'; 11 | 12 | /// The scope defined by a class. 13 | class ClassScope extends EnclosedScope { 14 | ClassScope(Scope parent, ClassElement element) : super(parent) { 15 | element.accessors.forEach(_addPropertyAccessor); 16 | element.methods.forEach(_addGetter); 17 | } 18 | } 19 | 20 | /// The scope for the initializers in a constructor. 21 | class ConstructorInitializerScope extends EnclosedScope { 22 | ConstructorInitializerScope(Scope parent, ConstructorElement element) 23 | : super(parent) { 24 | element.parameters.forEach(_addGetter); 25 | } 26 | } 27 | 28 | /// A scope that is lexically enclosed in another scope. 29 | class EnclosedScope implements Scope { 30 | final Scope _parent; 31 | final Map _getters = {}; 32 | final Map _setters = {}; 33 | 34 | EnclosedScope(Scope parent) : _parent = parent; 35 | 36 | Scope get parent => _parent; 37 | 38 | @Deprecated('Use lookup2() that is closer to the language specification') 39 | @override 40 | Element lookup({@required String id, @required bool setter}) { 41 | var result = lookup2(id); 42 | return setter ? result.setter : result.getter; 43 | } 44 | 45 | @override 46 | ScopeLookupResult lookup2(String id) { 47 | var getter = _getters[id]; 48 | var setter = _setters[id]; 49 | if (getter != null || setter != null) { 50 | return ScopeLookupResult(getter, setter); 51 | } 52 | 53 | return _parent.lookup2(id); 54 | } 55 | 56 | void _addGetter(Element element) { 57 | _addTo(_getters, element); 58 | } 59 | 60 | void _addPropertyAccessor(PropertyAccessorElement element) { 61 | if (element.isGetter) { 62 | _addGetter(element); 63 | } else { 64 | _addSetter(element); 65 | } 66 | } 67 | 68 | void _addSetter(Element element) { 69 | _addTo(_setters, element); 70 | } 71 | 72 | void _addTo(Map map, Element element) { 73 | var id = element.displayName; 74 | map[id] ??= element; 75 | } 76 | } 77 | 78 | /// The scope defined by an extension. 79 | class ExtensionScope extends EnclosedScope { 80 | ExtensionScope( 81 | Scope parent, 82 | ExtensionElement element, 83 | ) : super(parent) { 84 | element.accessors.forEach(_addPropertyAccessor); 85 | element.methods.forEach(_addGetter); 86 | } 87 | } 88 | 89 | class FormalParameterScope extends EnclosedScope { 90 | FormalParameterScope( 91 | Scope parent, 92 | List elements, 93 | ) : super(parent) { 94 | for (var parameter in elements) { 95 | if (parameter is! FieldFormalParameterElement) { 96 | _addGetter(parameter); 97 | } 98 | } 99 | } 100 | } 101 | 102 | class LibraryScope extends EnclosedScope { 103 | final LibraryElement _element; 104 | final List extensions = []; 105 | 106 | LibraryScope(LibraryElement element) 107 | : _element = element, 108 | super(_LibraryImportScope(element)) { 109 | extensions.addAll((_parent as _LibraryImportScope).extensions); 110 | 111 | _element.prefixes.forEach(_addGetter); 112 | _element.units.forEach(_addUnitElements); 113 | } 114 | 115 | bool shouldIgnoreUndefined({ 116 | @required String prefix, 117 | @required String name, 118 | }) { 119 | Iterable getShowCombinators( 120 | ImportElement importElement) { 121 | return importElement.combinators.whereType(); 122 | } 123 | 124 | if (prefix != null) { 125 | for (var importElement in _element.imports) { 126 | if (importElement.prefix?.name == prefix && 127 | importElement.importedLibrary?.isSynthetic != false) { 128 | var showCombinators = getShowCombinators(importElement); 129 | if (showCombinators.isEmpty) { 130 | return true; 131 | } 132 | for (ShowElementCombinator combinator in showCombinators) { 133 | if (combinator.shownNames.contains(name)) { 134 | return true; 135 | } 136 | } 137 | } 138 | } 139 | } else { 140 | // TODO(scheglov) merge for(s). 141 | for (var importElement in _element.imports) { 142 | if (importElement.prefix == null && 143 | importElement.importedLibrary?.isSynthetic != false) { 144 | for (ShowElementCombinator combinator 145 | in getShowCombinators(importElement)) { 146 | if (combinator.shownNames.contains(name)) { 147 | return true; 148 | } 149 | } 150 | } 151 | } 152 | 153 | if (name.startsWith(r'_$')) { 154 | for (var partElement in _element.parts) { 155 | if (partElement.isSynthetic && 156 | isGeneratedSource(partElement.source)) { 157 | return true; 158 | } 159 | } 160 | } 161 | } 162 | 163 | return false; 164 | } 165 | 166 | void _addExtension(ExtensionElement element) { 167 | _addGetter(element); 168 | if (!extensions.contains(element)) { 169 | extensions.add(element); 170 | } 171 | } 172 | 173 | void _addUnitElements(CompilationUnitElement compilationUnit) { 174 | compilationUnit.accessors.forEach(_addPropertyAccessor); 175 | compilationUnit.enums.forEach(_addGetter); 176 | compilationUnit.extensions.forEach(_addExtension); 177 | compilationUnit.functions.forEach(_addGetter); 178 | compilationUnit.functionTypeAliases.forEach(_addGetter); 179 | compilationUnit.mixins.forEach(_addGetter); 180 | compilationUnit.types.forEach(_addGetter); 181 | } 182 | } 183 | 184 | class LocalScope extends EnclosedScope { 185 | LocalScope(Scope parent) : super(parent); 186 | 187 | void add(Element element) { 188 | _addGetter(element); 189 | } 190 | } 191 | 192 | class PrefixScope implements Scope { 193 | final LibraryElement _library; 194 | final Map _getters = {}; 195 | final Map _setters = {}; 196 | final Set _extensions = {}; 197 | LibraryElement _deferredLibrary; 198 | 199 | PrefixScope(this._library, PrefixElement prefix) { 200 | for (var import in _library.imports) { 201 | if (import.prefix == prefix) { 202 | var elements = impl.NamespaceBuilder().getImportedElements(import); 203 | elements.forEach(_add); 204 | if (import.isDeferred) { 205 | _deferredLibrary ??= import.importedLibrary; 206 | } 207 | } 208 | } 209 | } 210 | 211 | @Deprecated('Use lookup2() that is closer to the language specification') 212 | @override 213 | Element lookup({@required String id, @required bool setter}) { 214 | var result = lookup2(id); 215 | return setter ? result.setter : result.getter; 216 | } 217 | 218 | @override 219 | ScopeLookupResult lookup2(String id) { 220 | if (_deferredLibrary != null && id == FunctionElement.LOAD_LIBRARY_NAME) { 221 | return ScopeLookupResult(_deferredLibrary.loadLibraryFunction, null); 222 | } 223 | 224 | var getter = _getters[id]; 225 | var setter = _setters[id]; 226 | return ScopeLookupResult(getter, setter); 227 | } 228 | 229 | void _add(Element element) { 230 | if (element is PropertyAccessorElement && element.isSetter) { 231 | _addTo(map: _setters, element: element); 232 | } else { 233 | _addTo(map: _getters, element: element); 234 | if (element is ExtensionElement) { 235 | _extensions.add(element); 236 | } 237 | } 238 | } 239 | 240 | void _addTo({ 241 | @required Map map, 242 | @required Element element, 243 | }) { 244 | var id = element.displayName; 245 | 246 | var existing = map[id]; 247 | if (existing != null && existing != element) { 248 | map[id] = _merge(existing, element); 249 | return; 250 | } 251 | 252 | map[id] = element; 253 | } 254 | 255 | Element _merge(Element existing, Element other) { 256 | if (_isSdkElement(existing)) { 257 | if (!_isSdkElement(other)) { 258 | return other; 259 | } 260 | } else { 261 | if (_isSdkElement(other)) { 262 | return existing; 263 | } 264 | } 265 | 266 | var conflictingElements = {}; 267 | _addElement(conflictingElements, existing); 268 | _addElement(conflictingElements, other); 269 | 270 | return MultiplyDefinedElementImpl( 271 | _library.context, 272 | _library.session, 273 | conflictingElements.first.name, 274 | conflictingElements.toList(), 275 | ); 276 | } 277 | 278 | static void _addElement( 279 | Set conflictingElements, 280 | Element element, 281 | ) { 282 | if (element is MultiplyDefinedElementImpl) { 283 | conflictingElements.addAll(element.conflictingElements); 284 | } else { 285 | conflictingElements.add(element); 286 | } 287 | } 288 | 289 | static bool _isSdkElement(Element element) { 290 | if (element is DynamicElementImpl || element is NeverElementImpl) { 291 | return true; 292 | } 293 | if (element is MultiplyDefinedElement) { 294 | return false; 295 | } 296 | return element.library.isInSdk; 297 | } 298 | } 299 | 300 | class TypeParameterScope extends EnclosedScope { 301 | TypeParameterScope( 302 | Scope parent, 303 | List elements, 304 | ) : super(parent) { 305 | elements.forEach(_addGetter); 306 | } 307 | } 308 | 309 | class _LibraryImportScope implements Scope { 310 | final LibraryElement _library; 311 | final PrefixScope _nullPrefixScope; 312 | List _extensions; 313 | 314 | _LibraryImportScope(LibraryElement library) 315 | : _library = library, 316 | _nullPrefixScope = PrefixScope(library, null); 317 | 318 | List get extensions { 319 | return _extensions ??= { 320 | ..._nullPrefixScope._extensions, 321 | for (var prefix in _library.prefixes) 322 | ...(prefix.scope as PrefixScope)._extensions, 323 | }.toList(); 324 | } 325 | 326 | @Deprecated('Use lookup2() that is closer to the language specification') 327 | @override 328 | Element lookup({@required String id, @required bool setter}) { 329 | throw UnimplementedError(); 330 | } 331 | 332 | @override 333 | ScopeLookupResult lookup2(String id) { 334 | return _nullPrefixScope.lookup2(id); 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /src/test/suite/testfiles/scope_want.txt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:analyzer/dart/element/element.dart'; 6 | import 'package:analyzer/dart/element/scope.dart'; 7 | import 'package:analyzer/src/context/source.dart'; 8 | import 'package:analyzer/src/dart/element/element.dart'; 9 | import 'package:analyzer/src/dart/resolver/scope.dart' as impl; 10 | import 'package:meta/meta.dart'; 11 | 12 | /// The scope defined by a class. 13 | class ClassScope extends EnclosedScope { 14 | ClassScope(Scope parent, ClassElement element) : super(parent) { 15 | element.accessors.forEach(_addPropertyAccessor); 16 | element.methods.forEach(_addGetter); 17 | } 18 | } 19 | 20 | /// The scope for the initializers in a constructor. 21 | class ConstructorInitializerScope extends EnclosedScope { 22 | ConstructorInitializerScope(Scope parent, ConstructorElement element) 23 | : super(parent) { 24 | element.parameters.forEach(_addGetter); 25 | } 26 | } 27 | 28 | /// A scope that is lexically enclosed in another scope. 29 | class EnclosedScope implements Scope { 30 | EnclosedScope(Scope parent) : _parent = parent; 31 | 32 | final Map _getters = {}; 33 | final Scope _parent; 34 | final Map _setters = {}; 35 | 36 | @Deprecated('Use lookup2() that is closer to the language specification') 37 | @override 38 | Element lookup({@required String id, @required bool setter}) { 39 | var result = lookup2(id); 40 | return setter ? result.setter : result.getter; 41 | } 42 | 43 | @override 44 | ScopeLookupResult lookup2(String id) { 45 | var getter = _getters[id]; 46 | var setter = _setters[id]; 47 | if (getter != null || setter != null) { 48 | return ScopeLookupResult(getter, setter); 49 | } 50 | 51 | return _parent.lookup2(id); 52 | } 53 | 54 | Scope get parent => _parent; 55 | 56 | void _addGetter(Element element) { 57 | _addTo(_getters, element); 58 | } 59 | 60 | void _addPropertyAccessor(PropertyAccessorElement element) { 61 | if (element.isGetter) { 62 | _addGetter(element); 63 | } else { 64 | _addSetter(element); 65 | } 66 | } 67 | 68 | void _addSetter(Element element) { 69 | _addTo(_setters, element); 70 | } 71 | 72 | void _addTo(Map map, Element element) { 73 | var id = element.displayName; 74 | map[id] ??= element; 75 | } 76 | } 77 | 78 | /// The scope defined by an extension. 79 | class ExtensionScope extends EnclosedScope { 80 | ExtensionScope( 81 | Scope parent, 82 | ExtensionElement element, 83 | ) : super(parent) { 84 | element.accessors.forEach(_addPropertyAccessor); 85 | element.methods.forEach(_addGetter); 86 | } 87 | } 88 | 89 | class FormalParameterScope extends EnclosedScope { 90 | FormalParameterScope( 91 | Scope parent, 92 | List elements, 93 | ) : super(parent) { 94 | for (var parameter in elements) { 95 | if (parameter is! FieldFormalParameterElement) { 96 | _addGetter(parameter); 97 | } 98 | } 99 | } 100 | } 101 | 102 | class LibraryScope extends EnclosedScope { 103 | final LibraryElement _element; 104 | final List extensions = []; 105 | 106 | LibraryScope(LibraryElement element) 107 | : _element = element, 108 | super(_LibraryImportScope(element)) { 109 | extensions.addAll((_parent as _LibraryImportScope).extensions); 110 | 111 | _element.prefixes.forEach(_addGetter); 112 | _element.units.forEach(_addUnitElements); 113 | } 114 | 115 | bool shouldIgnoreUndefined({ 116 | @required String prefix, 117 | @required String name, 118 | }) { 119 | Iterable getShowCombinators( 120 | ImportElement importElement) { 121 | return importElement.combinators.whereType(); 122 | } 123 | 124 | if (prefix != null) { 125 | for (var importElement in _element.imports) { 126 | if (importElement.prefix?.name == prefix && 127 | importElement.importedLibrary?.isSynthetic != false) { 128 | var showCombinators = getShowCombinators(importElement); 129 | if (showCombinators.isEmpty) { 130 | return true; 131 | } 132 | for (ShowElementCombinator combinator in showCombinators) { 133 | if (combinator.shownNames.contains(name)) { 134 | return true; 135 | } 136 | } 137 | } 138 | } 139 | } else { 140 | // TODO(scheglov) merge for(s). 141 | for (var importElement in _element.imports) { 142 | if (importElement.prefix == null && 143 | importElement.importedLibrary?.isSynthetic != false) { 144 | for (ShowElementCombinator combinator 145 | in getShowCombinators(importElement)) { 146 | if (combinator.shownNames.contains(name)) { 147 | return true; 148 | } 149 | } 150 | } 151 | } 152 | 153 | if (name.startsWith(r'_$')) { 154 | for (var partElement in _element.parts) { 155 | if (partElement.isSynthetic && 156 | isGeneratedSource(partElement.source)) { 157 | return true; 158 | } 159 | } 160 | } 161 | } 162 | 163 | return false; 164 | } 165 | 166 | void _addExtension(ExtensionElement element) { 167 | _addGetter(element); 168 | if (!extensions.contains(element)) { 169 | extensions.add(element); 170 | } 171 | } 172 | 173 | void _addUnitElements(CompilationUnitElement compilationUnit) { 174 | compilationUnit.accessors.forEach(_addPropertyAccessor); 175 | compilationUnit.enums.forEach(_addGetter); 176 | compilationUnit.extensions.forEach(_addExtension); 177 | compilationUnit.functions.forEach(_addGetter); 178 | compilationUnit.functionTypeAliases.forEach(_addGetter); 179 | compilationUnit.mixins.forEach(_addGetter); 180 | compilationUnit.types.forEach(_addGetter); 181 | } 182 | } 183 | 184 | class LocalScope extends EnclosedScope { 185 | LocalScope(Scope parent) : super(parent); 186 | 187 | void add(Element element) { 188 | _addGetter(element); 189 | } 190 | } 191 | 192 | class PrefixScope implements Scope { 193 | final LibraryElement _library; 194 | final Map _getters = {}; 195 | final Map _setters = {}; 196 | final Set _extensions = {}; 197 | LibraryElement _deferredLibrary; 198 | 199 | PrefixScope(this._library, PrefixElement prefix) { 200 | for (var import in _library.imports) { 201 | if (import.prefix == prefix) { 202 | var elements = impl.NamespaceBuilder().getImportedElements(import); 203 | elements.forEach(_add); 204 | if (import.isDeferred) { 205 | _deferredLibrary ??= import.importedLibrary; 206 | } 207 | } 208 | } 209 | } 210 | 211 | @Deprecated('Use lookup2() that is closer to the language specification') 212 | @override 213 | Element lookup({@required String id, @required bool setter}) { 214 | var result = lookup2(id); 215 | return setter ? result.setter : result.getter; 216 | } 217 | 218 | @override 219 | ScopeLookupResult lookup2(String id) { 220 | if (_deferredLibrary != null && id == FunctionElement.LOAD_LIBRARY_NAME) { 221 | return ScopeLookupResult(_deferredLibrary.loadLibraryFunction, null); 222 | } 223 | 224 | var getter = _getters[id]; 225 | var setter = _setters[id]; 226 | return ScopeLookupResult(getter, setter); 227 | } 228 | 229 | void _add(Element element) { 230 | if (element is PropertyAccessorElement && element.isSetter) { 231 | _addTo(map: _setters, element: element); 232 | } else { 233 | _addTo(map: _getters, element: element); 234 | if (element is ExtensionElement) { 235 | _extensions.add(element); 236 | } 237 | } 238 | } 239 | 240 | void _addTo({ 241 | @required Map map, 242 | @required Element element, 243 | }) { 244 | var id = element.displayName; 245 | 246 | var existing = map[id]; 247 | if (existing != null && existing != element) { 248 | map[id] = _merge(existing, element); 249 | return; 250 | } 251 | 252 | map[id] = element; 253 | } 254 | 255 | Element _merge(Element existing, Element other) { 256 | if (_isSdkElement(existing)) { 257 | if (!_isSdkElement(other)) { 258 | return other; 259 | } 260 | } else { 261 | if (_isSdkElement(other)) { 262 | return existing; 263 | } 264 | } 265 | 266 | var conflictingElements = {}; 267 | _addElement(conflictingElements, existing); 268 | _addElement(conflictingElements, other); 269 | 270 | return MultiplyDefinedElementImpl( 271 | _library.context, 272 | _library.session, 273 | conflictingElements.first.name, 274 | conflictingElements.toList(), 275 | ); 276 | } 277 | 278 | static void _addElement( 279 | Set conflictingElements, 280 | Element element, 281 | ) { 282 | if (element is MultiplyDefinedElementImpl) { 283 | conflictingElements.addAll(element.conflictingElements); 284 | } else { 285 | conflictingElements.add(element); 286 | } 287 | } 288 | 289 | static bool _isSdkElement(Element element) { 290 | if (element is DynamicElementImpl || element is NeverElementImpl) { 291 | return true; 292 | } 293 | if (element is MultiplyDefinedElement) { 294 | return false; 295 | } 296 | return element.library.isInSdk; 297 | } 298 | } 299 | 300 | class TypeParameterScope extends EnclosedScope { 301 | TypeParameterScope( 302 | Scope parent, 303 | List elements, 304 | ) : super(parent) { 305 | elements.forEach(_addGetter); 306 | } 307 | } 308 | 309 | class _LibraryImportScope implements Scope { 310 | final LibraryElement _library; 311 | final PrefixScope _nullPrefixScope; 312 | List _extensions; 313 | 314 | _LibraryImportScope(LibraryElement library) 315 | : _library = library, 316 | _nullPrefixScope = PrefixScope(library, null); 317 | 318 | List get extensions { 319 | return _extensions ??= { 320 | ..._nullPrefixScope._extensions, 321 | for (var prefix in _library.prefixes) 322 | ...(prefix.scope as PrefixScope)._extensions, 323 | }.toList(); 324 | } 325 | 326 | @Deprecated('Use lookup2() that is closer to the language specification') 327 | @override 328 | Element lookup({@required String id, @required bool setter}) { 329 | throw UnimplementedError(); 330 | } 331 | 332 | @override 333 | ScopeLookupResult lookup2(String id) { 334 | return _nullPrefixScope.lookup2(id); 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /src/dart/dart.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 Glenn M. Lewis 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { Class } from './class' 18 | import { Editor } from './editor' 19 | import { Entity, EntityType } from './entity' 20 | import { isComment } from './line' 21 | 22 | // Edit represents an edit of an editor buffer. 23 | export interface Edit { 24 | dc: Class, 25 | // sortName is the class name with the leading underscore removed. 26 | // See: https://github.com/gmlewis/go-flutter-stylizer/issues/8#issuecomment-1165024520 27 | sortName: string, 28 | startPos: number, 29 | endPos: number, 30 | text: string, 31 | } 32 | 33 | export interface Options { 34 | GroupAndSortGetterMethods?: boolean, 35 | GroupAndSortVariableTypes?: boolean, 36 | MemberOrdering?: string[], 37 | ProcessEnumsLikeClasses?: boolean, 38 | SortClassesWithinFile?: boolean, 39 | SortOtherMethods?: boolean, 40 | SeparatePrivateMethods?: boolean, 41 | Verbose?: boolean, 42 | } 43 | 44 | export const defaultMemberOrdering = [ 45 | 'public-constructor', 46 | 'named-constructors', 47 | 'public-static-variables', 48 | 'public-instance-variables', 49 | 'public-override-variables', 50 | 'private-static-variables', 51 | 'private-instance-variables', 52 | 'public-override-methods', 53 | 'public-other-methods', 54 | 'private-other-methods', 55 | 'build-method' 56 | ] 57 | 58 | export class Client { 59 | constructor(editor: Editor, opts: Options | null) { 60 | this.editor = editor 61 | this.opts = opts || { 62 | GroupAndSortGetterMethods: false, 63 | GroupAndSortVariableTypes: false, 64 | MemberOrdering: defaultMemberOrdering, 65 | ProcessEnumsLikeClasses: false, 66 | SortClassesWithinFile: false, 67 | SortOtherMethods: false, 68 | SeparatePrivateMethods: false, 69 | Verbose: false, 70 | } 71 | } 72 | editor: Editor 73 | opts: Options 74 | 75 | generateEdits(classes: Class[]): Edit[] { 76 | const edits: Edit[] = [] 77 | const allClasses: Edit[] = [] 78 | 79 | for (let i = classes.length - 1; i >= 0; i--) { 80 | const dc = classes[i] 81 | const startPos = dc.openCurlyOffset 82 | const endPos = dc.closeCurlyOffset 83 | 84 | const [lines, changesMade] = this.reorderClass(dc) 85 | 86 | let sortName = dc.className.replace(/^_/, '') 87 | if (this.opts.ProcessEnumsLikeClasses) { 88 | sortName = (dc.classType === 'enum' ? '0-enum-' : '1-class-') + sortName 89 | } 90 | 91 | const edit: Edit = { 92 | dc: dc, 93 | sortName: sortName, 94 | startPos: startPos, 95 | endPos: endPos, 96 | text: lines.join('\n'), 97 | } 98 | 99 | allClasses.push(edit) 100 | if (changesMade) { edits.push(edit) } 101 | } 102 | 103 | if (this.opts.SortClassesWithinFile) { 104 | return this.sortClassesWithinFile(edits, allClasses) 105 | } 106 | 107 | return edits 108 | } 109 | 110 | sortClassesWithinFile(edits: Edit[], origClasses: Edit[]): Edit[] { 111 | const allClasses = Array.from(origClasses) 112 | const gt = (a: Edit, b: Edit) => { // a.dc.className === b.dc.className ? 0 : a.dc.className < b.dc.className ? 1 : -1 113 | if (a.sortName === b.sortName) { 114 | return a.dc.className > b.dc.className ? -1 : a.dc.className < b.dc.className ? 1 : 0 115 | } 116 | return a.sortName > b.sortName ? -1 : 1 117 | } 118 | const isSorted = allClasses.every((v, i, a) => !i || gt(a[i - 1], v) === -1) 119 | if (isSorted) { return edits } 120 | 121 | allClasses.sort(gt) 122 | 123 | const result: Edit[] = [] 124 | for (let i in allClasses) { 125 | const cl = allClasses[i] 126 | const csp = this.editor.findClassAbsoluteStart(cl.dc) 127 | const rcsp = this.editor.findClassAbsoluteStart(origClasses[i].dc) 128 | 129 | result.push({ 130 | dc: cl.dc, 131 | sortName: cl.sortName, 132 | startPos: rcsp, 133 | endPos: origClasses[i].endPos, 134 | text: this.editor.fullBuf.substring(csp, cl.startPos) + cl.text, 135 | }) 136 | } 137 | 138 | return result 139 | } 140 | 141 | reorderClass(dc: Class): [string[], boolean] { 142 | const lines: string[] = [] 143 | 144 | lines.push(dc.lines[0].line) // Curly brace. 145 | 146 | // Add in LeaveUnmodified lines... 147 | let foundLeaveUnmodified = false 148 | for (let i = 1; i < dc.lines.length; i++) { 149 | const line = dc.lines[i] 150 | if (line.entityType === EntityType.LeaveUnmodified) { 151 | lines.push(line.line) 152 | foundLeaveUnmodified = true 153 | } 154 | } 155 | if (foundLeaveUnmodified) { 156 | lines.push('') 157 | } 158 | 159 | const addEntity = (entity: Entity | null, separateEntities: boolean) => { 160 | if (entity === null) { 161 | return 162 | } 163 | 164 | entity.lines.forEach((line) => lines.push(line.line)) 165 | 166 | if (separateEntities !== false || entity.lines.length > 1) { 167 | if (lines.length > 0 && lines[lines.length - 1] !== '') { 168 | lines.push('') 169 | this.logf(`reorderClass.addEntity(${entity.entityType}): adding blank line #${lines.length}`) 170 | } 171 | } 172 | } 173 | 174 | const addEntities = (entities: Entity[], separateEntities: boolean) => { 175 | if (entities.length === 0) { 176 | return 177 | } 178 | entities.forEach((e) => addEntity(e, separateEntities)) 179 | if (!separateEntities && lines.length > 0 && lines[lines.length - 1] !== '') { 180 | lines.push('') 181 | this.logf(`reorderClass.addEntities(${entities[0].entityType}): separateEntities=${separateEntities}, adding blank line #${lines.length}`) 182 | } 183 | } 184 | 185 | const addEntitiesByVarTypes = (entities: Entity[]) => { 186 | const finalVars: Entity[] = [] 187 | const normalVars: Entity[] = [] 188 | const optionalVars: Entity[] = [] 189 | entities.forEach((e) => { 190 | const stripped = e.lines[0].stripped 191 | if (stripped.includes('final ')) { 192 | finalVars.push(e) 193 | } else if (stripped.includes('?')) { 194 | optionalVars.push(e) 195 | } else { 196 | normalVars.push(e) 197 | } 198 | }) 199 | if (finalVars.length > 0) { addEntities(finalVars, false) } 200 | if (normalVars.length > 0) { addEntities(normalVars, false) } 201 | if (optionalVars.length > 0) { addEntities(optionalVars, false) } 202 | } 203 | 204 | // const sortFunc = (a: Entity, b: Entity) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }) 205 | const sortFunc = (a: Entity, b: Entity) => a.name === b.name ? 0 : a.name < b.name ? -1 : 1 206 | 207 | const ordering = this.opts.MemberOrdering || defaultMemberOrdering 208 | ordering.forEach((el, order) => { 209 | // if (!this.opts.Quiet) { 210 | this.logf(`Ordering step #${order + 1}: placing all '${el}'...`) 211 | // } 212 | 213 | // Strip trailing blank lines. 214 | while (lines.length > 2 && lines[lines.length - 1] === '' && lines[lines.length - 2] === '') { 215 | this.logf(`reorderClass(el='${el}'): removing blank line #${lines.length}`) 216 | lines.pop() 217 | } 218 | 219 | switch (el) { 220 | case 'public-constructor': 221 | addEntity(dc.theConstructor, true) 222 | break 223 | case 'named-constructors': 224 | dc.namedConstructors.sort(sortFunc) 225 | addEntities(dc.namedConstructors, true) 226 | break 227 | case 'public-static-variables': 228 | dc.staticVariables.sort(sortFunc) 229 | addEntities(dc.staticVariables, false) 230 | break 231 | case 'public-instance-variables': 232 | dc.instanceVariables.sort(sortFunc) 233 | if (this.opts.GroupAndSortVariableTypes) { 234 | addEntitiesByVarTypes(dc.instanceVariables) 235 | } else { 236 | addEntities(dc.instanceVariables, false) 237 | } 238 | break 239 | case 'public-override-variables': 240 | dc.overrideVariables.sort(sortFunc) 241 | addEntities(dc.overrideVariables, false) 242 | break 243 | case 'private-static-variables': 244 | dc.staticPrivateVariables.sort(sortFunc) 245 | addEntities(dc.staticPrivateVariables, false) 246 | break 247 | case 'private-instance-variables': 248 | dc.privateVariables.sort(sortFunc) 249 | if (this.opts.GroupAndSortVariableTypes) { 250 | addEntitiesByVarTypes(dc.privateVariables) 251 | } else { 252 | addEntities(dc.privateVariables, false) 253 | } 254 | break 255 | case 'public-override-methods': 256 | dc.overrideMethods.sort(sortFunc) 257 | addEntities(dc.overrideMethods, true) 258 | break 259 | case 'private-other-methods': 260 | if (this.opts.SeparatePrivateMethods) { 261 | if (this.opts.SortOtherMethods) { 262 | dc.otherPrivateMethods.sort(sortFunc) 263 | } 264 | addEntities(dc.otherPrivateMethods, true) 265 | } 266 | break 267 | case 'public-other-methods': 268 | if (this.opts.GroupAndSortGetterMethods) { 269 | dc.getterMethods.sort(sortFunc) 270 | addEntities(dc.getterMethods, false) 271 | } 272 | 273 | if (this.opts.SortOtherMethods) { 274 | dc.otherAllOrPublicMethods.sort(sortFunc) 275 | } 276 | addEntities(dc.otherAllOrPublicMethods, true) 277 | 278 | // Preserve random single-line and multi-line comments. 279 | for (let i = 1; i < dc.lines.length; i++) { 280 | let foundComment = false 281 | for (; i < dc.lines.length && isComment(dc.lines[i]); i++) { 282 | lines.push(dc.lines[i].line) 283 | foundComment = true 284 | } 285 | if (foundComment) { 286 | lines.push('') 287 | } 288 | } 289 | break 290 | case 'build-method': 291 | addEntity(dc.buildMethod, true) 292 | } 293 | }) 294 | 295 | this.logf(`Ordering done. Placed ${lines.length} lines.`) 296 | 297 | let changesMade = false 298 | if (dc.lines.length !== lines.length) { 299 | changesMade = true 300 | } else { 301 | for (let i = 0; i < dc.lines.length; i++) { 302 | if (dc.lines[i].line !== lines[i]) { 303 | changesMade = true 304 | break 305 | } 306 | } 307 | } 308 | 309 | return [lines, changesMade] 310 | } 311 | 312 | rewriteClasses(buf: string, edits: Edit[]): string { 313 | let newBuf = buf 314 | edits.forEach((edit) => { 315 | newBuf = `${newBuf.substring(0, edit.startPos)}${edit.text}${newBuf.substring(edit.endPos)}` 316 | }) 317 | return newBuf 318 | } 319 | 320 | // logf logs the line if verbose is true. 321 | logf(s: string) { 322 | if (this.opts.Verbose) { 323 | console.log(s) 324 | } 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /src/test/suite/dart/issue31.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 Glenn M. Lewis 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import * as vscode from 'vscode' 18 | 19 | import { EntityType } from '../../../dart/entity' 20 | import { runFullStylizer, runParsePhase } from './class.test' 21 | 22 | const fs = require('fs') 23 | const path = require('path') 24 | 25 | suite('Issue#31 Tests', function() { 26 | var myExtDir = (vscode.extensions.getExtension('gmlewis-vscode.flutter-stylizer') || {}).extensionPath || process.env.VSCODE_CWD 27 | const testfilesDir = path.join(myExtDir, 'src', 'test', 'suite', 'testfiles') 28 | 29 | test('Issue#31: case 1', () => { 30 | const groupAndSortGetterMethods = false 31 | const sortOtherMethods = false 32 | 33 | const source = fs.readFileSync(path.join(testfilesDir, 'issue31.dart.txt'), 'utf8') 34 | 35 | const want = [ 36 | [ 37 | EntityType.Unknown, // line #1: { 38 | EntityType.MainConstructor, // line #2: SnackbarService(); 39 | EntityType.BlankLine, // line #3: 40 | EntityType.InstanceVariable, // line #4: final varA1 = 'varA1'; 41 | EntityType.InstanceVariable, // line #5: String? varA2; 42 | EntityType.InstanceVariable, // line #6: String varA3 = 'varA3'; 43 | EntityType.InstanceVariable, // line #7: final varB1 = 'varB1'; 44 | EntityType.InstanceVariable, // line #8: String? varB2; 45 | EntityType.InstanceVariable, // line #9: String varB3 = 'varB3'; 46 | EntityType.InstanceVariable, // line #10: final varC1 = 'varC1'; 47 | EntityType.InstanceVariable, // line #11: String? varC2; 48 | EntityType.InstanceVariable, // line #12: String varC3 = 'varC3'; 49 | EntityType.BlankLine, // line #13: 50 | ], 51 | [ 52 | EntityType.Unknown, // line #15: { 53 | EntityType.MainConstructor, // line #16: User({ 54 | EntityType.MainConstructor, // line #17: required this.firstNameFinal, 55 | EntityType.MainConstructor, // line #18: required this.idFinal, 56 | EntityType.MainConstructor, // line #19: required this.lastNameFinal, 57 | EntityType.MainConstructor, // line #20: required this.middleNameFinal, 58 | EntityType.MainConstructor, // line #21: required this.phoneNumberFinal, 59 | EntityType.MainConstructor, // line #22: required this.usernameFinal, 60 | EntityType.MainConstructor, // line #23: required this.firstNameRegular, 61 | EntityType.MainConstructor, // line #24: required this.idRegular, 62 | EntityType.MainConstructor, // line #25: required this.lastNameRegular, 63 | EntityType.MainConstructor, // line #26: required this.usernameRegular, 64 | EntityType.MainConstructor, // line #27: }); 65 | EntityType.BlankLine, // line #28: 66 | EntityType.InstanceVariable, // line #29: final String firstNameFinal; 67 | EntityType.InstanceVariable, // line #30: final int idFinal; 68 | EntityType.InstanceVariable, // line #31: final String lastNameFinal; 69 | EntityType.InstanceVariable, // line #32: final String? middleNameFinal; 70 | EntityType.InstanceVariable, // line #33: final String? phoneNumberFinal; 71 | EntityType.InstanceVariable, // line #34: final String usernameFinal; 72 | EntityType.BlankLine, // line #35: 73 | EntityType.InstanceVariable, // line #36: String firstNameRegular; 74 | EntityType.InstanceVariable, // line #37: int idRegular; 75 | EntityType.InstanceVariable, // line #38: String lastNameRegular; 76 | EntityType.InstanceVariable, // line #39: String usernameRegular; 77 | EntityType.BlankLine, // line #40: 78 | EntityType.InstanceVariable, // line #41: int? ageOptional; 79 | EntityType.InstanceVariable, // line #42: String? birthdateOptional; 80 | EntityType.InstanceVariable, // line #43: String? emailOptional; 81 | EntityType.InstanceVariable, // line #44: String? middleNameRegular; 82 | EntityType.InstanceVariable, // line #45: String? phoneNumberRegular; 83 | EntityType.BlankLine, // line #46: 84 | EntityType.PrivateInstanceVariable, // line #47: int? _agePrivate; 85 | EntityType.PrivateInstanceVariable, // line #48: String? _birthdatePrivate; 86 | EntityType.PrivateInstanceVariable, // line #49: String? _emailPrivate; 87 | EntityType.PrivateInstanceVariable, // line #50: final String _firstNamePrivate = 'Secret'; 88 | EntityType.PrivateInstanceVariable, // line #51: final int _idPrivate = 0; 89 | EntityType.PrivateInstanceVariable, // line #52: final String _lastNamePrivate = 'Secret'; 90 | EntityType.PrivateInstanceVariable, // line #53: String? _middleNamePrivate; 91 | EntityType.PrivateInstanceVariable, // line #54: final String _phoneNumberPrivate = 'Secret'; 92 | EntityType.PrivateInstanceVariable, // line #55: final String _usernamePrivate = 'Secret'; 93 | EntityType.BlankLine, // line #56: 94 | ] 95 | ] 96 | 97 | const memberOrdering = [ 98 | 'public-constructor', 99 | 'named-constructors', 100 | 'public-static-variables', 101 | 'private-static-variables', 102 | 'public-instance-variables', 103 | 'public-override-variables', 104 | 'private-instance-variables', 105 | 'public-override-methods', 106 | 'public-other-methods', 107 | 'private-other-methods', 108 | 'build-method', 109 | ] 110 | 111 | const opts = { 112 | GroupAndSortGetterMethods: groupAndSortGetterMethods, 113 | MemberOrdering: memberOrdering, 114 | SortOtherMethods: sortOtherMethods, 115 | } 116 | 117 | runParsePhase(opts, source, want) 118 | }) 119 | 120 | test('Issue#31: case 2', () => { 121 | const groupAndSortVariableTypes = true 122 | const sortOtherMethods = true 123 | 124 | const source = fs.readFileSync(path.join(testfilesDir, 'issue31.dart.txt'), 'utf8') 125 | const wantSource = fs.readFileSync(path.join(testfilesDir, 'issue31.want.txt'), 'utf8') 126 | 127 | const want = [ 128 | [ 129 | EntityType.Unknown, // line #1: { 130 | EntityType.MainConstructor, // line #2: SnackbarService(); 131 | EntityType.BlankLine, // line #3: 132 | EntityType.InstanceVariable, // line #4: final varA1 = 'varA1'; 133 | EntityType.InstanceVariable, // line #5: String? varA2; 134 | EntityType.InstanceVariable, // line #6: String varA3 = 'varA3'; 135 | EntityType.InstanceVariable, // line #7: final varB1 = 'varB1'; 136 | EntityType.InstanceVariable, // line #8: String? varB2; 137 | EntityType.InstanceVariable, // line #9: String varB3 = 'varB3'; 138 | EntityType.InstanceVariable, // line #10: final varC1 = 'varC1'; 139 | EntityType.InstanceVariable, // line #11: String? varC2; 140 | EntityType.InstanceVariable, // line #12: String varC3 = 'varC3'; 141 | EntityType.BlankLine, // line #13: 142 | ], 143 | [ 144 | EntityType.Unknown, // line #15: { 145 | EntityType.MainConstructor, // line #16: User({ 146 | EntityType.MainConstructor, // line #17: required this.firstNameFinal, 147 | EntityType.MainConstructor, // line #18: required this.idFinal, 148 | EntityType.MainConstructor, // line #19: required this.lastNameFinal, 149 | EntityType.MainConstructor, // line #20: required this.middleNameFinal, 150 | EntityType.MainConstructor, // line #21: required this.phoneNumberFinal, 151 | EntityType.MainConstructor, // line #22: required this.usernameFinal, 152 | EntityType.MainConstructor, // line #23: required this.firstNameRegular, 153 | EntityType.MainConstructor, // line #24: required this.idRegular, 154 | EntityType.MainConstructor, // line #25: required this.lastNameRegular, 155 | EntityType.MainConstructor, // line #26: required this.usernameRegular, 156 | EntityType.MainConstructor, // line #27: }); 157 | EntityType.BlankLine, // line #28: 158 | EntityType.InstanceVariable, // line #29: final String firstNameFinal; 159 | EntityType.InstanceVariable, // line #30: final int idFinal; 160 | EntityType.InstanceVariable, // line #31: final String lastNameFinal; 161 | EntityType.InstanceVariable, // line #32: final String? middleNameFinal; 162 | EntityType.InstanceVariable, // line #33: final String? phoneNumberFinal; 163 | EntityType.InstanceVariable, // line #34: final String usernameFinal; 164 | EntityType.BlankLine, // line #35: 165 | EntityType.InstanceVariable, // line #36: String firstNameRegular; 166 | EntityType.InstanceVariable, // line #37: int idRegular; 167 | EntityType.InstanceVariable, // line #38: String lastNameRegular; 168 | EntityType.InstanceVariable, // line #39: String usernameRegular; 169 | EntityType.BlankLine, // line #40: 170 | EntityType.InstanceVariable, // line #41: int? ageOptional; 171 | EntityType.InstanceVariable, // line #42: String? birthdateOptional; 172 | EntityType.InstanceVariable, // line #43: String? emailOptional; 173 | EntityType.InstanceVariable, // line #44: String? middleNameRegular; 174 | EntityType.InstanceVariable, // line #45: String? phoneNumberRegular; 175 | EntityType.BlankLine, // line #46: 176 | EntityType.PrivateInstanceVariable, // line #47: int? _agePrivate; 177 | EntityType.PrivateInstanceVariable, // line #48: String? _birthdatePrivate; 178 | EntityType.PrivateInstanceVariable, // line #49: String? _emailPrivate; 179 | EntityType.PrivateInstanceVariable, // line #50: final String _firstNamePrivate = 'Secret'; 180 | EntityType.PrivateInstanceVariable, // line #51: final int _idPrivate = 0; 181 | EntityType.PrivateInstanceVariable, // line #52: final String _lastNamePrivate = 'Secret'; 182 | EntityType.PrivateInstanceVariable, // line #53: String? _middleNamePrivate; 183 | EntityType.PrivateInstanceVariable, // line #54: final String _phoneNumberPrivate = 'Secret'; 184 | EntityType.PrivateInstanceVariable, // line #55: final String _usernamePrivate = 'Secret'; 185 | EntityType.BlankLine, // line #56: 186 | ] 187 | ] 188 | 189 | const memberOrdering = [ 190 | 'public-constructor', 191 | 'named-constructors', 192 | 'public-static-variables', 193 | 'public-instance-variables', 194 | 'public-override-variables', 195 | 'private-static-variables', 196 | 'private-instance-variables', 197 | 'public-other-methods', 198 | 'public-override-methods', 199 | 'private-other-methods', 200 | 'build-method', 201 | ] 202 | 203 | const opts = { 204 | GroupAndSortVariableTypes: groupAndSortVariableTypes, 205 | MemberOrdering: memberOrdering, 206 | SortOtherMethods: sortOtherMethods, 207 | } 208 | 209 | runFullStylizer(opts, source, wantSource, want) 210 | }) 211 | }) 212 | -------------------------------------------------------------------------------- /src/test/suite/dart/editor.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 Glenn M. Lewis 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import * as assert from 'assert' 18 | import * as vscode from 'vscode' 19 | 20 | import { Editor } from '../../../dart/editor' 21 | import { EntityType } from '../../../dart/entity' 22 | import { Line } from '../../../dart/line' 23 | 24 | // import * as stylizer from '../../extension' 25 | const fs = require('fs') 26 | const path = require('path') 27 | 28 | export function setupEditor(searchFor: string, buf: string): [Editor, number, number, number] { 29 | const classOffset = buf.indexOf(searchFor) 30 | const openCurlyOffset = classOffset + searchFor.length - 1 31 | const closeCurlyOffset = buf[buf.length - 2] === '\r' ? buf.length - 3 : buf.length - 2 32 | 33 | assert.strictEqual( 34 | buf.substring(classOffset, openCurlyOffset + 1), 35 | searchFor, 36 | `open offset error: buf[${classOffset}:${openCurlyOffset + 1}]`, 37 | ) 38 | assert.strictEqual( 39 | buf.substring(closeCurlyOffset, closeCurlyOffset + 1), 40 | '}', 41 | `close offset error: buf[${closeCurlyOffset}:${closeCurlyOffset + 1}]`, 42 | ) 43 | 44 | const p = buf.substring(0, openCurlyOffset + 1).split('\n') 45 | const lineOffset = p.length - 1 46 | assert.strictEqual( 47 | p[lineOffset], 48 | searchFor, 49 | ) 50 | 51 | const editor = new Editor(buf, false, false) 52 | 53 | return [editor, lineOffset, openCurlyOffset, closeCurlyOffset] 54 | } 55 | 56 | // Defines a Mocha test suite to group tests of similar kind together 57 | suite('DartEditor Parsing Tests', function() { 58 | var myExtDir = (vscode.extensions.getExtension('gmlewis-vscode.flutter-stylizer') || {}).extensionPath || process.env.VSCODE_CWD 59 | const testfilesDir = path.join(myExtDir, 'src', 'test', 'suite', 'testfiles') 60 | const basicClasses = fs.readFileSync(path.join(testfilesDir, 'basic_classes.dart.txt'), 'utf8') 61 | const bcWindoze = fs.readFileSync(path.join(testfilesDir, 'basic_classes.dart.windz.txt'), 'utf8') 62 | const utf8Text = fs.readFileSync(path.join(testfilesDir, 'utf8_text.dart.txt'), 'utf8') 63 | 64 | test('FindLineIndexAtOffset', () => { 65 | const [bc, /* _unused1 */, bcOCO, /* _unused2 */] = setupEditor('class Class1 {', basicClasses) 66 | const [wz, /* _unused3 */, wzOCO, /* _unused4 */] = setupEditor('class Class1 {', bcWindoze) 67 | 68 | const tests: { 69 | name: string, 70 | editor: Editor, 71 | openOffset: number, 72 | wantLineIndex: number, 73 | wantRelOffset: number, 74 | }[] = [ 75 | // *nix file 76 | { 77 | name: 'top-level', 78 | editor: bc, 79 | openOffset: bcOCO, 80 | wantLineIndex: 6, 81 | wantRelOffset: 13, 82 | }, 83 | { 84 | name: 'build()', 85 | editor: bc, 86 | openOffset: basicClasses.indexOf('build()') + 5, 87 | wantLineIndex: 10, 88 | wantRelOffset: 5, 89 | }, 90 | { 91 | name: 'build() {}', 92 | editor: bc, 93 | openOffset: basicClasses.indexOf('build() {}') + 8, 94 | wantLineIndex: 10, 95 | wantRelOffset: 8, 96 | }, 97 | { 98 | name: 'Class1();', 99 | editor: bc, 100 | openOffset: basicClasses.indexOf('Class1();') + 6, 101 | wantLineIndex: 29, 102 | wantRelOffset: 6, 103 | }, 104 | { 105 | name: 'Class1.fromNum();', 106 | editor: bc, 107 | openOffset: basicClasses.indexOf('Class1.fromNum();') + 14, 108 | wantLineIndex: 30, 109 | wantRelOffset: 14, 110 | }, 111 | { 112 | name: 'var myfunc = (int n) => n;', 113 | editor: bc, 114 | openOffset: basicClasses.indexOf('var myfunc = (int n) => n;') + 13, 115 | wantLineIndex: 31, 116 | wantRelOffset: 13, 117 | }, 118 | { 119 | name: 'toString()', 120 | editor: bc, 121 | openOffset: basicClasses.indexOf('toString()') + 8, 122 | wantLineIndex: 34, 123 | wantRelOffset: 8, 124 | }, 125 | { 126 | name: 'toString() {', 127 | editor: bc, 128 | openOffset: basicClasses.indexOf('toString() {') + 11, 129 | wantLineIndex: 34, 130 | wantRelOffset: 11, 131 | }, 132 | { 133 | name: 'print(\'$_pvi, $_spv, $_spvni, $_pvini, ${sqrt(2)}\');', 134 | editor: bc, 135 | openOffset: basicClasses.indexOf('print(\'$_pvi, $_spv, $_spvni, $_pvini, ${sqrt(2)}\');') + 5, 136 | wantLineIndex: 35, 137 | wantRelOffset: 5, 138 | }, 139 | { 140 | name: '${sqrt(2)}', 141 | editor: bc, 142 | openOffset: basicClasses.indexOf('${sqrt(2)}') + 1, 143 | wantLineIndex: 35, 144 | wantRelOffset: 40, 145 | }, 146 | { 147 | name: 'sqrt(2)', 148 | editor: bc, 149 | openOffset: basicClasses.indexOf('sqrt(2)') + 4, 150 | wantLineIndex: 35, 151 | wantRelOffset: 45, 152 | }, 153 | 154 | // Windoze file 155 | { 156 | name: 'windoze top-level', 157 | editor: wz, 158 | openOffset: wzOCO, 159 | wantLineIndex: 6, 160 | wantRelOffset: 13, 161 | }, 162 | { 163 | name: 'windoze build()', 164 | editor: wz, 165 | openOffset: bcWindoze.indexOf('build()') + 5, 166 | wantLineIndex: 10, 167 | wantRelOffset: 5, 168 | }, 169 | { 170 | name: 'windoze build() {}', 171 | editor: wz, 172 | openOffset: bcWindoze.indexOf('build() {}') + 8, 173 | wantLineIndex: 10, 174 | wantRelOffset: 8, 175 | }, 176 | { 177 | name: 'windoze Class1();', 178 | editor: wz, 179 | openOffset: bcWindoze.indexOf('Class1();') + 6, 180 | wantLineIndex: 29, 181 | wantRelOffset: 6, 182 | }, 183 | { 184 | name: 'windoze Class1.fromNum();', 185 | editor: wz, 186 | openOffset: bcWindoze.indexOf('Class1.fromNum();') + 14, 187 | wantLineIndex: 30, 188 | wantRelOffset: 14, 189 | }, 190 | { 191 | name: 'windoze var myfunc = (int n) => n;', 192 | editor: wz, 193 | openOffset: bcWindoze.indexOf('var myfunc = (int n) => n;') + 13, 194 | wantLineIndex: 31, 195 | wantRelOffset: 13, 196 | }, 197 | { 198 | name: 'windoze toString()', 199 | editor: wz, 200 | openOffset: bcWindoze.indexOf('toString()') + 8, 201 | wantLineIndex: 34, 202 | wantRelOffset: 8, 203 | }, 204 | { 205 | name: 'windoze toString() {', 206 | editor: wz, 207 | openOffset: bcWindoze.indexOf('toString() {') + 11, 208 | wantLineIndex: 34, 209 | wantRelOffset: 11, 210 | }, 211 | { 212 | name: 'windoze print(\'$_pvi, $_spv, $_spvni, $_pvini, ${sqrt(2)}\');', 213 | editor: wz, 214 | openOffset: bcWindoze.indexOf('print(\'$_pvi, $_spv, $_spvni, $_pvini, ${sqrt(2)}\');') + 5, 215 | wantLineIndex: 35, 216 | wantRelOffset: 5, 217 | }, 218 | { 219 | name: 'windoze ${sqrt(2)}', 220 | editor: wz, 221 | openOffset: bcWindoze.indexOf('${sqrt(2)}') + 1, 222 | wantLineIndex: 35, 223 | wantRelOffset: 40, 224 | }, 225 | { 226 | name: 'windoze sqrt(2)', 227 | editor: wz, 228 | openOffset: bcWindoze.indexOf('sqrt(2)') + 4, 229 | wantLineIndex: 35, 230 | wantRelOffset: 45, 231 | }, 232 | ] 233 | 234 | for (let tt of tests) { 235 | const [gotLineIndex, gotRelOffset] = tt.editor.findLineIndexAtOffset(tt.openOffset) 236 | assert.strictEqual(gotLineIndex, tt.wantLineIndex, `name='${tt.name}': lineIndex: findLineIndexAtOffset(${tt.openOffset})`) 237 | assert.strictEqual(gotRelOffset, tt.wantRelOffset, `name='${tt.name}': relOffset: findLineIndexAtOffset(${tt.openOffset})`) 238 | } 239 | }) 240 | 241 | test('New Editor with utf8', () => { 242 | const tests: { 243 | name: string, 244 | buf: string, 245 | want: Line[], 246 | }[] = [ 247 | { 248 | name: 'utf8 string', 249 | buf: utf8Text, 250 | want: [ 251 | { 252 | line: 'abstract class ElementImpl implements Element {', 253 | stripped: 'abstract class ElementImpl implements Element {', 254 | strippedOffset: 0, 255 | originalIndex: 0, 256 | startOffset: 0, 257 | endOffset: 47, 258 | entityType: 0, 259 | classLevelText: '', 260 | classLevelTextOffsets: [], 261 | isCommentOrString: false, 262 | }, 263 | { 264 | line: ' /// An Unicode right arrow.', 265 | stripped: '', 266 | strippedOffset: 2, 267 | classLevelText: '', 268 | classLevelTextOffsets: [], 269 | originalIndex: 1, 270 | startOffset: 48, 271 | endOffset: 77, 272 | entityType: EntityType.SingleLineComment, 273 | isCommentOrString: false, 274 | }, 275 | { 276 | line: ' @deprecated', 277 | stripped: '@deprecated', 278 | strippedOffset: 2, 279 | classLevelText: '@deprecated', 280 | classLevelTextOffsets: [80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90], 281 | originalIndex: 2, 282 | startOffset: 78, 283 | endOffset: 91, 284 | entityType: 0, 285 | isCommentOrString: false, 286 | }, 287 | { 288 | line: ` static final String RIGHT_ARROW = " \\u2192 ";`, 289 | stripped: `static final String RIGHT_ARROW = " \\u2192 ";`, 290 | strippedOffset: 2, 291 | classLevelText: `static final String RIGHT_ARROW = "";`, 292 | classLevelTextOffsets: [94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 137, 138], 293 | originalIndex: 3, 294 | startOffset: 92, 295 | endOffset: 139, 296 | entityType: 0, 297 | isCommentOrString: false, 298 | }, 299 | { 300 | line: '}', 301 | stripped: '}', 302 | strippedOffset: 0, 303 | classLevelTextOffsets: [], 304 | originalIndex: 4, 305 | startOffset: 140, 306 | endOffset: 141, 307 | entityType: 0, 308 | classLevelText: '', 309 | isCommentOrString: false, 310 | }, 311 | ], 312 | }, 313 | ] 314 | 315 | for (let tt of tests) { 316 | const editor = new Editor(tt.buf, false, false) 317 | assert.strictEqual(editor.lines.length, tt.want.length) 318 | editor.lines.forEach((line, i) => { 319 | assert.strictEqual(line.line, tt.want[i].line, `name=${tt.name}, i=${i}, field='line'`) 320 | assert.strictEqual(line.stripped, tt.want[i].stripped, `name=${tt.name}, i=${i}, field='stripped'`) 321 | assert.strictEqual(line.strippedOffset, tt.want[i].strippedOffset, `name=${tt.name}, i=${i}, field='strippedOffset'`) 322 | assert.strictEqual(line.originalIndex, tt.want[i].originalIndex, `name=${tt.name}, i=${i}, field='originalIndex'`) 323 | assert.strictEqual(line.startOffset, tt.want[i].startOffset, `name=${tt.name}, i=${i}, field='startOffset'`) 324 | assert.strictEqual(line.endOffset, tt.want[i].endOffset, `name=${tt.name}, i=${i}, field='endOffset'`) 325 | assert.strictEqual(line.entityType, tt.want[i].entityType, `name=${tt.name}, i=${i}, field='entityType'`) 326 | assert.strictEqual(line.classLevelText, tt.want[i].classLevelText, `name=${tt.name}, i=${i}, field='classLevelText'`) 327 | assert.strictEqual(line.classLevelTextOffsets.length, tt.want[i].classLevelTextOffsets.length, `name=${tt.name}, i=${i}, field='classLevelTextOffsets.length'`) 328 | const want = tt.want[i].classLevelTextOffsets 329 | line.classLevelTextOffsets.forEach((offset, j) => { 330 | assert.strictEqual(offset, want[j], `name=${tt.name}, i=${i}, field='classLevelTextOffsets[${j}]'`) 331 | }) 332 | assert.strictEqual(line.isCommentOrString, tt.want[i].isCommentOrString, `name=${tt.name}, i=${i}, field='isCommentOrString'`) 333 | // assert.deepStrictEqual(line, tt.want[i]) // Doesn't give useful information in output. :-( 334 | // t.Errorf('line[%v] =\n%#v\nwant\n%#v', i, e.lines[i], tt.want[i]) 335 | }) 336 | } 337 | }) 338 | }) 339 | -------------------------------------------------------------------------------- /src/test/suite/dart/pubspec.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 Glenn M. Lewis 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import * as assert from 'assert' 18 | import * as vscode from 'vscode' 19 | 20 | import { Editor } from '../../../dart/editor' 21 | import { EntityType } from '../../../dart/entity' 22 | import { runFullStylizer } from './class.test' 23 | 24 | const fs = require('fs') 25 | const path = require('path') 26 | 27 | suite('Pubspec Tests', function() { 28 | var myExtDir = (vscode.extensions.getExtension('gmlewis-vscode.flutter-stylizer') || {}).extensionPath || process.env.VSCODE_CWD 29 | const testfilesDir = path.join(myExtDir, 'src', 'test', 'suite', 'testfiles') 30 | 31 | test('Pubspec get classes', () => { 32 | const source = fs.readFileSync(path.join(testfilesDir, 'pubspec.dart.txt'), 'utf8') 33 | 34 | const e = new Editor(source, false, false) 35 | 36 | const [got, err] = e.getClasses(false, false) 37 | if (err !== null) { 38 | throw Error(err.message) // Make the compiler happy. 39 | } 40 | 41 | assert.strictEqual(got.length, 1, 'classes') 42 | }) 43 | 44 | test('Pubspec class 1', () => { 45 | const source = fs.readFileSync(path.join(testfilesDir, 'pubspec.dart.txt'), 'utf8') 46 | const wantSource = fs.readFileSync(path.join(testfilesDir, 'pubspec_want.txt'), 'utf8') 47 | 48 | const want = [ 49 | EntityType.Unknown, // line #14: { 50 | EntityType.InstanceVariable, // line #15: // TODO: executables 51 | EntityType.InstanceVariable, // line #16: 52 | EntityType.InstanceVariable, // line #17: final String name; 53 | EntityType.BlankLine, // line #18: 54 | EntityType.InstanceVariable, // line #19: @JsonKey(fromJson: _versionFromString) 55 | EntityType.InstanceVariable, // line #20: final Version version; 56 | EntityType.BlankLine, // line #21: 57 | EntityType.InstanceVariable, // line #22: final String description; 58 | EntityType.BlankLine, // line #23: 59 | EntityType.InstanceVariable, // line #24: /// This should be a URL pointing to the website for the package. 60 | EntityType.InstanceVariable, // line #25: final String homepage; 61 | EntityType.BlankLine, // line #26: 62 | EntityType.InstanceVariable, // line #27: /// Specifies where to publish this package. 63 | EntityType.InstanceVariable, // line #28: /// 64 | EntityType.InstanceVariable, // line #29: /// Accepted values: `null`, `'none'` or an `http` or `https` URL. 65 | EntityType.InstanceVariable, // line #30: /// 66 | EntityType.InstanceVariable, // line #31: /// [More information](https://dart.dev/tools/pub/pubspec#publish_to). 67 | EntityType.InstanceVariable, // line #32: final String publishTo; 68 | EntityType.BlankLine, // line #33: 69 | EntityType.InstanceVariable, // line #34: /// Optional field to specify the source code repository of the package. 70 | EntityType.InstanceVariable, // line #35: /// Useful when a package has both a home page and a repository. 71 | EntityType.InstanceVariable, // line #36: final Uri repository; 72 | EntityType.BlankLine, // line #37: 73 | EntityType.InstanceVariable, // line #38: /// Optional field to a web page where developers can report new issues or 74 | EntityType.InstanceVariable, // line #39: /// view existing ones. 75 | EntityType.InstanceVariable, // line #40: final Uri issueTracker; 76 | EntityType.BlankLine, // line #41: 77 | EntityType.OtherMethod, // line #42: /// If there is exactly 1 value in [authors], returns it. 78 | EntityType.OtherMethod, // line #43: /// 79 | EntityType.OtherMethod, // line #44: /// If there are 0 or more than 1, returns `null`. 80 | EntityType.OtherMethod, // line #45: @Deprecated( 81 | EntityType.OtherMethod, // line #46: 'Here for completeness, but not recommended. Use `authors` instead.') 82 | EntityType.OtherMethod, // line #47: String get author { 83 | EntityType.OtherMethod, // line #48: if (authors.length == 1) { 84 | EntityType.OtherMethod, // line #49: return authors.single; 85 | EntityType.OtherMethod, // line #50: } 86 | EntityType.OtherMethod, // line #51: return null; 87 | EntityType.OtherMethod, // line #52: } 88 | EntityType.BlankLine, // line #53: 89 | EntityType.InstanceVariable, // line #54: final List authors; 90 | EntityType.InstanceVariable, // line #55: final String documentation; 91 | EntityType.BlankLine, // line #56: 92 | EntityType.InstanceVariable, // line #57: @JsonKey(fromJson: _environmentMap) 93 | EntityType.InstanceVariable, // line #58: final Map environment; 94 | EntityType.BlankLine, // line #59: 95 | EntityType.InstanceVariable, // line #60: @JsonKey(fromJson: parseDeps, nullable: false) 96 | EntityType.InstanceVariable, // line #61: final Map dependencies; 97 | EntityType.BlankLine, // line #62: 98 | EntityType.InstanceVariable, // line #63: @JsonKey(fromJson: parseDeps, nullable: false) 99 | EntityType.InstanceVariable, // line #64: final Map devDependencies; 100 | EntityType.BlankLine, // line #65: 101 | EntityType.InstanceVariable, // line #66: @JsonKey(fromJson: parseDeps, nullable: false) 102 | EntityType.InstanceVariable, // line #67: final Map dependencyOverrides; 103 | EntityType.BlankLine, // line #68: 104 | EntityType.InstanceVariable, // line #69: /// Optional configuration specific to [Flutter](https://flutter.io/) 105 | EntityType.InstanceVariable, // line #70: /// packages. 106 | EntityType.InstanceVariable, // line #71: /// 107 | EntityType.InstanceVariable, // line #72: /// May include 108 | EntityType.InstanceVariable, // line #73: /// [assets](https://flutter.io/docs/development/ui/assets-and-images) 109 | EntityType.InstanceVariable, // line #74: /// and other settings. 110 | EntityType.InstanceVariable, // line #75: final Map flutter; 111 | EntityType.BlankLine, // line #76: 112 | EntityType.MainConstructor, // line #77: /// If [author] and [authors] are both provided, their values are combined 113 | EntityType.MainConstructor, // line #78: /// with duplicates eliminated. 114 | EntityType.MainConstructor, // line #79: Pubspec( 115 | EntityType.MainConstructor, // line #80: this.name, { 116 | EntityType.MainConstructor, // line #81: this.version, 117 | EntityType.MainConstructor, // line #82: this.publishTo, 118 | EntityType.MainConstructor, // line #83: String author, 119 | EntityType.MainConstructor, // line #84: List authors, 120 | EntityType.MainConstructor, // line #85: Map environment, 121 | EntityType.MainConstructor, // line #86: this.homepage, 122 | EntityType.MainConstructor, // line #87: this.repository, 123 | EntityType.MainConstructor, // line #88: this.issueTracker, 124 | EntityType.MainConstructor, // line #89: this.documentation, 125 | EntityType.MainConstructor, // line #90: this.description, 126 | EntityType.MainConstructor, // line #91: Map dependencies, 127 | EntityType.MainConstructor, // line #92: Map devDependencies, 128 | EntityType.MainConstructor, // line #93: Map dependencyOverrides, 129 | EntityType.MainConstructor, // line #94: this.flutter, 130 | EntityType.MainConstructor, // line #95: }) : authors = _normalizeAuthors(author, authors), 131 | EntityType.MainConstructor, // line #96: environment = environment ?? const {}, 132 | EntityType.MainConstructor, // line #97: dependencies = dependencies ?? const {}, 133 | EntityType.MainConstructor, // line #98: devDependencies = devDependencies ?? const {}, 134 | EntityType.MainConstructor, // line #99: dependencyOverrides = dependencyOverrides ?? const {} { 135 | EntityType.MainConstructor, // line #100: if (name == null || name.isEmpty) { 136 | EntityType.MainConstructor, // line #101: throw ArgumentError.value(name, 'name', '"name" cannot be empty.'); 137 | EntityType.MainConstructor, // line #102: } 138 | EntityType.MainConstructor, // line #103: 139 | EntityType.MainConstructor, // line #104: if (publishTo != null && publishTo != 'none') { 140 | EntityType.MainConstructor, // line #105: try { 141 | EntityType.MainConstructor, // line #106: final targetUri = Uri.parse(publishTo); 142 | EntityType.MainConstructor, // line #107: if (!(targetUri.isScheme('http') || targetUri.isScheme('https'))) { 143 | EntityType.MainConstructor, // line #108: throw const FormatException('Must be an http or https URL.'); 144 | EntityType.MainConstructor, // line #109: } 145 | EntityType.MainConstructor, // line #110: } on FormatException catch (e) { 146 | EntityType.MainConstructor, // line #111: throw ArgumentError.value(publishTo, 'publishTo', e.message); 147 | EntityType.MainConstructor, // line #112: } 148 | EntityType.MainConstructor, // line #113: } 149 | EntityType.MainConstructor, // line #114: } 150 | EntityType.BlankLine, // line #115: 151 | EntityType.NamedConstructor, // line #116: factory Pubspec.fromJson(Map json, {bool lenient = false}) { 152 | EntityType.NamedConstructor, // line #117: lenient ??= false; 153 | EntityType.NamedConstructor, // line #118: 154 | EntityType.NamedConstructor, // line #119: if (lenient) { 155 | EntityType.NamedConstructor, // line #120: while (json.isNotEmpty) { 156 | EntityType.NamedConstructor, // line #121: // Attempting to remove top-level properties that cause parsing errors. 157 | EntityType.NamedConstructor, // line #122: try { 158 | EntityType.NamedConstructor, // line #123: return _$PubspecFromJson(json); 159 | EntityType.NamedConstructor, // line #124: } on CheckedFromJsonException catch (e) { 160 | EntityType.NamedConstructor, // line #125: if (e.map == json && json.containsKey(e.key)) { 161 | EntityType.NamedConstructor, // line #126: json = Map.from(json)..remove(e.key); 162 | EntityType.NamedConstructor, // line #127: continue; 163 | EntityType.NamedConstructor, // line #128: } 164 | EntityType.NamedConstructor, // line #129: rethrow; 165 | EntityType.NamedConstructor, // line #130: } 166 | EntityType.NamedConstructor, // line #131: } 167 | EntityType.NamedConstructor, // line #132: } 168 | EntityType.NamedConstructor, // line #133: 169 | EntityType.NamedConstructor, // line #134: return _$PubspecFromJson(json); 170 | EntityType.NamedConstructor, // line #135: } 171 | EntityType.BlankLine, // line #136: 172 | EntityType.NamedConstructor, // line #137: /// Parses source [yaml] into [Pubspec]. 173 | EntityType.NamedConstructor, // line #138: /// 174 | EntityType.NamedConstructor, // line #139: /// When [lenient] is set, top-level property-parsing or type cast errors are 175 | EntityType.NamedConstructor, // line #140: /// ignored and `null` values are returned. 176 | EntityType.NamedConstructor, // line #141: factory Pubspec.parse(String yaml, {sourceUrl, bool lenient = false}) { 177 | EntityType.NamedConstructor, // line #142: lenient ??= false; 178 | EntityType.NamedConstructor, // line #143: 179 | EntityType.NamedConstructor, // line #144: return checkedYamlDecode( 180 | EntityType.NamedConstructor, // line #145: yaml, (map) => Pubspec.fromJson(map, lenient: lenient), 181 | EntityType.NamedConstructor, // line #146: sourceUrl: sourceUrl); 182 | EntityType.NamedConstructor, // line #147: } 183 | EntityType.BlankLine, // line #148: 184 | EntityType.OtherMethod, // line #149: static List _normalizeAuthors(String author, List authors) { 185 | EntityType.OtherMethod, // line #150: final value = {}; 186 | EntityType.OtherMethod, // line #151: if (author != null) { 187 | EntityType.OtherMethod, // line #152: value.add(author); 188 | EntityType.OtherMethod, // line #153: } 189 | EntityType.OtherMethod, // line #154: if (authors != null) { 190 | EntityType.OtherMethod, // line #155: value.addAll(authors); 191 | EntityType.OtherMethod, // line #156: } 192 | EntityType.OtherMethod, // line #157: return value.toList(); 193 | EntityType.OtherMethod, // line #158: } 194 | EntityType.BlankLine, // line #159: 195 | ] 196 | 197 | runFullStylizer(null, source, wantSource, [want]) 198 | runFullStylizer(null, wantSource, wantSource, null) 199 | }) 200 | }) 201 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flutter-stylizer - Mothballed/Archived Plugin 2 | 3 | [![CodeQL](https://github.com/gmlewis/flutter-stylizer/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/gmlewis/flutter-stylizer/actions/workflows/codeql-analysis.yml) 4 | 5 | Flutter Stylizer is a VSCode extension that organizes your Flutter classes 6 | in an opinionated and consistent manner. 7 | 8 | Note that as of release `0.1.1`, there is now a standalone version of 9 | `flutter-stylizer` (with identical output to this plugin) that is available here: 10 | - https://github.com/gmlewis/go-flutter-stylizer 11 | 12 | ## Status 13 | 14 | After 5 years, 119K installs, 4.9/5 stars, and zero sponsors, this project 15 | is now mothballed and archived. Feel free to fork the project to create your 16 | own version of the plugin and add your own customizations to your liking. 17 | 18 | ## Features 19 | 20 | Flutter Stylizer organizes the class(es) within a `*.dart` file 21 | in the following manner (with a blank line separating these parts): 22 | 23 | * The main (possibly factory) constructor is listed first, if it exists. 24 | - (`public-constructor` in configuration) 25 | * Any named constructors are listed next, in sorted order. 26 | - (`named-constructors` in configuration) 27 | * Any static (class) variables are listed next, in sorted order. 28 | - (`public-static-variables` in configuration) 29 | * Any instance variables are listed next, in sorted order. 30 | (As of version `v0.1.8`, a new option flag affects this section; see below.) 31 | - (`public-instance-variables` in configuration) 32 | * Any `@override` variables are listed next, in sorted order. 33 | - (`public-override-variables` in configuration) 34 | * Any private static (class) variables are listed next, in sorted order. 35 | - (`private-static-variables` in configuration) 36 | * Any private instance variables are listed next, in sorted order. 37 | - (`private-instance-variables` in configuration) 38 | * Any `@override` methods are listed next, in sorted order. 39 | - (`public-override-methods` in configuration) 40 | * Any other methods are listed next in their original (unchanged) order. 41 | (As of version `v0.0.19`, two new option flags affect this section; see below.) 42 | - (`public-other-methods` in configuration) 43 | * If `private-other-methods` is (optionally) specified, these will be sorted 44 | separately from `public-other-methods`. 45 | * The `build` method is listed last. 46 | - (`build-method` in configuration) 47 | 48 | I have found that developer productivity increases when all code in 49 | large projects follows a consistent and opinionated style. 50 | 51 | Additionally, bringing new developers into a team with a large code base 52 | is easier when the code is consistently written and therefore easier 53 | to navigate and understand. 54 | 55 | Without tooling to enforce a consistent style, developing code is less fun. 56 | Having an automated tool to do this ugly work for you, however, makes 57 | coding a lot more enjoyable, as you don't have to worry about the rules, 58 | but can just run the plugin on file save, and the rules are automatically 59 | enforced. Note that this plugin doesn't natively support format-on-save, 60 | but you could use another extension... possibly something like this: 61 | https://marketplace.visualstudio.com/items?itemName=emeraldwalk.RunOnSave 62 | or you could use the stand-alone [Go flutter-stylizer] 63 | in your GitHub Actions or CI pipeline to perform this action for you. 64 | 65 | ## Configuration 66 | 67 | To override the default order of the stylizer, add a section to your 68 | VSCode User Preferences (`Control/Cmd-,`) like this: 69 | 70 | ``` 71 | "flutterStylizer": { 72 | "groupAndSortGetterMethods": false, 73 | "groupAndSortVariableTypes": false, 74 | "memberOrdering": [ 75 | "public-constructor", 76 | "named-constructors", 77 | "public-static-variables", 78 | "public-instance-variables", 79 | "public-override-variables", 80 | "private-static-variables", 81 | "private-instance-variables", 82 | "public-override-methods", 83 | "public-other-methods", 84 | "private-other-methods", 85 | "build-method", 86 | ], 87 | "processEnumsLikeClasses": false, 88 | "sortClassesWithinFile": false, 89 | "sortOtherMethods": false, 90 | } 91 | ``` 92 | 93 | And then rearrange member names as desired. 94 | 95 | ### Other option flags 96 | 97 | Note that as of `v0.0.19`, two new option flags were added to modify the 98 | behavior of the "public-other-methods" as requested in #18: 99 | 100 | - `groupAndSortGetterMethods` (default: `false`) 101 | - Whether to group getters separately (before 'public-other-methods') 102 | and sort them within their group. 103 | 104 | - `sortOtherMethods` (default: `false`) 105 | - Whether to sort the 'public-other-methods' within their group. 106 | 107 | As of `v0.1.5`, a new `private-other-methods` field was added. 108 | If not specified, private methods will continue to be grouped within 109 | the `public-other-methods` section. 110 | 111 | As of `v0.1.8`, a new option flag was added: 112 | 113 | - `groupAndSortVariableTypes` (default: `false`) 114 | - Whether to group public variables separately by type and sort 115 | them within their groups. Types are: "final", "optional" (`?`), and "normal". 116 | 117 | As of `v0.1.12`, a new option flag was added: 118 | 119 | - `sortClassesWithinFile` (default: `false`) 120 | - Whether to sort multiple classes within each file. 121 | 122 | As of `v0.1.15`, a new option flag was added: 123 | - `processEnumsLikeClasses` (default: `false`) 124 | - Whether to process enums identically to how classes are processed. 125 | 126 | These features are experimental and should be used with caution. 127 | Please file any bugs you find on the [GitHub issue tracker]. 128 | 129 | ## Run Flutter Stylizer on all files - Option 1 130 | 131 | If you want a super-fast way to process all files in a very large project, 132 | and aren't averse to the command-line terminal (:smile:) please remember 133 | to check out the stand-alone command-line companion tool: 134 | https://github.com/gmlewis/go-flutter-stylizer 135 | 136 | ## Run Flutter Stylizer on all files - Option 2 137 | 138 | If you install the [Command on All Files](https://marketplace.visualstudio.com/items?itemName=rioj7.commandOnAllFiles) 139 | VSCode extension, you can then run the Flutter Stylizer on all files 140 | within your project. 141 | 142 | To do so, edit your VSCode `settings.json` file and add this section: 143 | 144 | ```json 145 | "commandOnAllFiles.commands": { 146 | "Format File": { 147 | "command": "editor.action.formatDocument", 148 | "includeFileExtensions": [ 149 | ".dart" 150 | ] 151 | }, 152 | "Flutter Stylizer": { 153 | "command": "extension.flutterStylizer", 154 | "includeFileExtensions": [ 155 | ".dart" 156 | ] 157 | } 158 | } 159 | ``` 160 | 161 | Then run the command "Flutter Stylizer" and all `.dart` files in your project 162 | will be stylized. 163 | 164 | Note that this command can take upwards of 20 seconds just to get going due 165 | to the time it takes to scan your project for `.dart` files. 166 | 167 | Many thanks to `@longtimedeveloper` for this 168 | [recommendation](https://github.com/gmlewis/flutter-stylizer/issues/23#issuecomment-1014781798)! 169 | 170 | ## Limitations 171 | 172 | This plugin does not have a full-featured Dart syntax tree parser. 173 | As a result, it may come across Dart code that it doesn't handle properly. 174 | See the [Known Issues](#known-issues) section below for more details. 175 | 176 | ## Reporting Problems 177 | 178 | It is my goal to be able to use this plugin on large group projects, so 179 | every attempt has been made to make this robust. If, however, problems 180 | are found, please raise issues on the [GitHub issue tracker] for this repo 181 | along with a (short) example demonstrating the "before" and "after" results 182 | of running this plugin on the example code. 183 | 184 | Even better, please submit a PR with your new "before"/"after" example coded-up 185 | as a unit test along with the code to fix the problem, and I'll try to 186 | incorporate the fix into the plugin. 187 | 188 | ***Please remember to state which version of the plugin you are using and include your configuration settings!*** 189 | 190 | [GitHub issue tracker]: https://github.com/gmlewis/flutter-stylizer/issues 191 | [Go flutter-stylizer]: https://github.com/gmlewis/go-flutter-stylizer 192 | 193 | ## Known Issues 194 | 195 | * Flutter Stylizer is line-oriented. It is meant to be run on code that 196 | is nicely separated by lines. The `dartfmt` tool typically makes 197 | sane-looking code, and this is the type of code that is being targeted 198 | by this extension. 199 | * Code that follows the end of a multiline comment on the same 200 | line is not supported. Unusual code like this will most likely not ever be 201 | supported even though the Dart compiler can handle it. 202 | 203 | ## Release Notes 204 | 205 | ### v0.1.16 206 | 207 | - Fix [issue #31](https://github.com/gmlewis/flutter-stylizer/issues/31) for private vars. 208 | 209 | ### v0.1.15 210 | 211 | - Add new option: 212 | - `processEnumsLikeClasses` (default: `false`) 213 | 214 | ### v0.1.14 215 | 216 | - Fix sort order of class names when `sortClassesWithinFile: true`. 217 | 218 | ### v0.1.13 219 | 220 | - Fix [issue #8](https://github.com/gmlewis/go-flutter-stylizer/issues/8). 221 | 222 | ### v0.1.12 223 | 224 | - Add new option [issue #8](https://github.com/gmlewis/go-flutter-stylizer/issues/8): 225 | - `sortClassesWithinFile` (default: `false`) 226 | 227 | ### v0.1.11 228 | 229 | - Fix [issue #6](https://github.com/gmlewis/go-flutter-stylizer/issues/6). 230 | 231 | ### v0.1.10 232 | 233 | - Update vsce version. 234 | 235 | ### v0.1.9 236 | 237 | - Add sponsorship ability. 238 | 239 | ### v0.1.8 240 | 241 | - Add new option [issue #31](https://github.com/gmlewis/flutter-stylizer/issues/31): 242 | - `groupAndSortVariableTypes` (default: `false`) 243 | 244 | ### v0.1.7 245 | 246 | - Fix [issue #26](https://github.com/gmlewis/flutter-stylizer/issues/26) caused by `Function()`. 247 | 248 | ### 0.1.6 249 | 250 | - Process all `mixin` blocks in addition to all `class` blocks. 251 | 252 | ### 0.1.5 253 | 254 | - `private-other-methods` can optionally be added to the member ordering. 255 | 256 | ### 0.1.3 257 | 258 | - Add plugin icon image. 259 | 260 | ### 0.1.2 261 | 262 | - Update dependencies. 263 | 264 | ### 0.1.1 265 | 266 | - Complete rewrite of Dart parser to identically match output from 267 | standalone [Go flutter-stylizer](https://github.com/gmlewis/go-flutter-stylizer). 268 | This VSCode plugin can now be used in the same CI/CD projects with 269 | the standalone `flutter-stylizer`. 270 | 271 | ### 0.0.21 272 | 273 | - Fix incorrectly identified NamedConstructor bug reported in #20. 274 | 275 | ### 0.0.20 276 | 277 | - Fix plugin broken on Windows bug reported in #19. 278 | 279 | ### 0.0.19 280 | 281 | - Add two new configuration booleans for experimental features, 282 | requested in #18. Please use these features with caution and 283 | file any bugs you find on GitHub. 284 | - `groupAndSortGetterMethods` (default: `false`) 285 | - `sortOtherMethods` (default: `false`) 286 | 287 | ### 0.0.18 288 | 289 | - Fix incorrectly-identified Function-type variable reported in #17. 290 | 291 | ### 0.0.17 292 | 293 | - Breaking change: 294 | Add `"public-override-variables"` configuration property to allow 295 | customization of `@override` variables ordering, requested in #16. 296 | You will need to add this new property to your `flutterStylizer.memberOrdering`, 297 | otherwise it will use the default built-in ordering. 298 | 299 | ### 0.0.16 300 | 301 | - Add `flutterStylizer.memberOrdering` configuration property to allow 302 | customization of member ordering, requested in #11. 303 | 304 | ### 0.0.15 305 | 306 | - Fix incorrectly-identified named constructor reported in #9. 307 | 308 | ### 0.0.14 309 | 310 | - Upgrade lodash to fix security vulnerability in #8. 311 | 312 | ### 0.0.13 313 | 314 | - Adds a statusbar button (on the lower left) to run the stylizer command on the current file. 315 | The button appears whenever an editor with the language type `dart` is the active editor. 316 | This is accomplished with a language-based "activation event" for "flutter-stylizer". 317 | - Adds an extension dependency to the Dart extension (this adds `dart` as an editor language). 318 | - This feature was generously added by @sketchbuch in #7. 319 | 320 | ### 0.0.12 321 | 322 | - Incorporate vulnerability fixes from #3, #4, and #5. 323 | 324 | ### 0.0.11 325 | 326 | - Fix bugs running on flutter package. 327 | 328 | ### 0.0.10 329 | 330 | - Fix await bug. 331 | 332 | ### 0.0.9 333 | 334 | - npm update vscode. 335 | 336 | ### 0.0.8 337 | 338 | - Improve spacing for single-line variables. 339 | 340 | ### 0.0.7 341 | 342 | - Fix instance variable bug. 343 | 344 | ### 0.0.6 345 | 346 | - Keep groups of comments associated with following entity. 347 | 348 | ### 0.0.5 349 | 350 | - Fix bugs found with abstract classes, getters, and @overrides. 351 | 352 | ### 0.0.4 353 | 354 | - Add support for missing getters. 355 | 356 | ### 0.0.3 357 | 358 | - Fix placement of getters with other methods. 359 | 360 | ### 0.0.2 361 | 362 | - Preserve solitary single- and multi-line comments. 363 | 364 | ### 0.0.1 365 | 366 | - Initial release, "Flutter Stylizer" is the provided command. 367 | 368 | ----------------------------------------------------------------------------------------------------------- 369 | 370 | **Enjoy!** 371 | 372 | ---------------------------------------------------------------------- 373 | 374 | # License 375 | 376 | Copyright 2018 Glenn M. Lewis. All Rights Reserved. 377 | 378 | Licensed under the Apache License, Version 2.0 (the "License"); 379 | you may not use this file except in compliance with the License. 380 | You may obtain a copy of the License at 381 | 382 | http://www.apache.org/licenses/LICENSE-2.0 383 | 384 | Unless required by applicable law or agreed to in writing, software 385 | distributed under the License is distributed on an "AS IS" BASIS, 386 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 387 | See the License for the specific language governing permissions and 388 | limitations under the License. 389 | -------------------------------------------------------------------------------- /src/test/suite/testfiles/issue20.dart.txt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:ffi'; 6 | 7 | import 'package:ffi/ffi.dart'; 8 | import 'package:win32/win32.dart'; 9 | 10 | import 'base.dart'; 11 | import 'classlayout.dart'; 12 | import 'com/IMetaDataImport2.dart'; 13 | import 'constants.dart'; 14 | import 'event.dart'; 15 | import 'field.dart'; 16 | import 'metadatastore.dart'; 17 | import 'method.dart'; 18 | import 'mixins/customattributes_mixin.dart'; 19 | import 'mixins/genericparams_mixin.dart'; 20 | import 'property.dart'; 21 | import 'systemtokens.dart'; 22 | import 'type_aliases.dart'; 23 | import 'typeidentifier.dart'; 24 | import 'utils.dart'; 25 | 26 | enum TypeVisibility { 27 | notPublic, 28 | public, 29 | nestedPublic, 30 | nestedPrivate, 31 | nestedFamily, 32 | nestedAssembly, 33 | nestedFamilyAndAssembly, 34 | nestedFamilyOrAssembly 35 | } 36 | 37 | enum TypeLayout { auto, sequential, explicit } 38 | 39 | enum StringFormat { ansi, unicode, auto, custom } 40 | 41 | /// Represents a TypeDef in the Windows Metadata file 42 | class TypeDef extends TokenObject 43 | with CustomAttributesMixin, GenericParamsMixin { 44 | final String typeName; 45 | final int _attributes; 46 | final int baseTypeToken; 47 | final TypeIdentifier? typeSpec; 48 | 49 | final _interfaces = []; 50 | final _fields = []; 51 | final _methods = []; 52 | final _properties = []; 53 | final _events = []; 54 | 55 | TypeVisibility get typeVisibility => 56 | TypeVisibility.values[_attributes & CorTypeAttr.tdVisibilityMask]; 57 | 58 | TypeLayout get typeLayout { 59 | switch (_attributes & CorTypeAttr.tdLayoutMask) { 60 | case CorTypeAttr.tdAutoLayout: 61 | return TypeLayout.auto; 62 | case CorTypeAttr.tdSequentialLayout: 63 | return TypeLayout.sequential; 64 | case CorTypeAttr.tdExplicitLayout: 65 | return TypeLayout.explicit; 66 | default: 67 | throw WinmdException('Attribute missing type layout information'); 68 | } 69 | } 70 | 71 | /// Is the type a class? 72 | bool get isClass => 73 | _attributes & CorTypeAttr.tdClassSemanticsMask == CorTypeAttr.tdClass; 74 | 75 | /// Is the type an interface? 76 | bool get isInterface => 77 | _attributes & CorTypeAttr.tdClassSemanticsMask == CorTypeAttr.tdInterface; 78 | 79 | bool get isAbstract => 80 | _attributes & CorTypeAttr.tdAbstract == CorTypeAttr.tdAbstract; 81 | 82 | bool get isSealed => 83 | _attributes & CorTypeAttr.tdSealed == CorTypeAttr.tdSealed; 84 | 85 | bool get isSpecialName => 86 | _attributes & CorTypeAttr.tdSpecialName == CorTypeAttr.tdSpecialName; 87 | 88 | bool get isImported => 89 | _attributes & CorTypeAttr.tdImport == CorTypeAttr.tdImport; 90 | 91 | bool get isSerializable => 92 | _attributes & CorTypeAttr.tdSerializable == CorTypeAttr.tdSerializable; 93 | 94 | bool get isWindowsRuntime => 95 | _attributes & CorTypeAttr.tdWindowsRuntime == 96 | CorTypeAttr.tdWindowsRuntime; 97 | 98 | bool get isRTSpecialName => 99 | _attributes & CorTypeAttr.tdRTSpecialName == CorTypeAttr.tdRTSpecialName; 100 | 101 | StringFormat get stringFormat { 102 | switch (_attributes & CorTypeAttr.tdStringFormatMask) { 103 | case CorTypeAttr.tdAnsiClass: 104 | return StringFormat.ansi; 105 | case CorTypeAttr.tdUnicodeClass: 106 | return StringFormat.unicode; 107 | case CorTypeAttr.tdAutoClass: 108 | return StringFormat.auto; 109 | case CorTypeAttr.tdCustomFormatClass: 110 | return StringFormat.custom; 111 | default: 112 | throw WinmdException('Attribute missing string format information'); 113 | } 114 | } 115 | 116 | bool get isBeforeFieldInit => 117 | _attributes & CorTypeAttr.tdBeforeFieldInit == 118 | CorTypeAttr.tdBeforeFieldInit; 119 | 120 | bool get isForwarder => 121 | _attributes & CorTypeAttr.tdForwarder == CorTypeAttr.tdForwarder; 122 | 123 | /// Is the type a delegate? 124 | bool get isDelegate => parent?.typeName == 'System.MulticastDelegate'; 125 | 126 | /// Retrieve class layout information. 127 | /// 128 | /// This includes the packing alignment, the minimum class size, and the field 129 | /// layout (e.g. for sparsely or overlapping structs). 130 | ClassLayout get classLayout => ClassLayout(reader, token); 131 | 132 | /// Is the type a non-Windows Runtime type, such as System.Object or 133 | /// IInspectable? 134 | /// 135 | /// More information at: 136 | /// https://docs.microsoft.com/en-us/uwp/winrt-cref/winmd-files#type-system-encoding 137 | bool get isSystemType => systemTokens.containsValue(typeName); 138 | 139 | /// Create a typedef. 140 | /// 141 | /// Typically, typedefs should be obtained from a [WinmdScope] object rather 142 | /// than being created directly. 143 | TypeDef(IMetaDataImport2 reader, 144 | [int token = 0, 145 | this.typeName = '', 146 | this._attributes = 0, 147 | this.baseTypeToken = 0, 148 | this.typeSpec]) 149 | : super(reader, token); 150 | 151 | /// Creates a typedef object from its given token. 152 | factory TypeDef.fromToken(IMetaDataImport2 reader, int token) { 153 | switch (token & 0xFF000000) { 154 | case CorTokenType.mdtTypeRef: 155 | return TypeDef.fromTypeRefToken(reader, token); 156 | case CorTokenType.mdtTypeDef: 157 | return TypeDef.fromTypeDefToken(reader, token); 158 | case CorTokenType.mdtTypeSpec: 159 | return TypeDef.fromTypeSpecToken(reader, token); 160 | default: 161 | throw WinmdException('Unrecognized token ${token.toHexString(32)}'); 162 | } 163 | } 164 | 165 | /// Instantiate a typedef from a TypeDef token. 166 | factory TypeDef.fromTypeDefToken(IMetaDataImport2 reader, int typeDefToken) { 167 | final szTypeDef = stralloc(MAX_STRING_SIZE); 168 | final pchTypeDef = calloc(); 169 | final pdwTypeDefFlags = calloc(); 170 | final ptkExtends = calloc(); 171 | 172 | try { 173 | final hr = reader.GetTypeDefProps(typeDefToken, szTypeDef, 174 | MAX_STRING_SIZE, pchTypeDef, pdwTypeDefFlags, ptkExtends); 175 | 176 | if (SUCCEEDED(hr)) { 177 | return TypeDef(reader, typeDefToken, szTypeDef.toDartString(), 178 | pdwTypeDefFlags.value, ptkExtends.value); 179 | } else { 180 | throw WindowsException(hr); 181 | } 182 | } finally { 183 | free(pchTypeDef); 184 | free(pdwTypeDefFlags); 185 | free(ptkExtends); 186 | free(szTypeDef); 187 | } 188 | } 189 | 190 | /// Instantiate a typedef from a TypeRef token. 191 | /// 192 | /// Unless the TypeRef token is `IInspectable`, the COM parent interface for 193 | /// Windows Runtime classes, the TypeRef is used to obtain the host scope 194 | /// metadata file, from which the TypeDef can be found and returned. 195 | factory TypeDef.fromTypeRefToken(IMetaDataImport2 reader, int typeRefToken) { 196 | final ptkResolutionScope = calloc(); 197 | final szName = stralloc(MAX_STRING_SIZE); 198 | final pchName = calloc(); 199 | 200 | try { 201 | final hr = reader.GetTypeRefProps( 202 | typeRefToken, ptkResolutionScope, szName, MAX_STRING_SIZE, pchName); 203 | 204 | if (SUCCEEDED(hr)) { 205 | final typeName = szName.toDartString(); 206 | try { 207 | final newScope = MetadataStore.getScopeForType(typeName); 208 | return newScope.findTypeDef(typeName)!; 209 | } catch (exception) { 210 | // a token like IInspectable is out of reach of GetTypeRefProps, since it is 211 | // a plain COM object. These objects are returned as system types. 212 | if (systemTokens.containsKey(typeRefToken)) { 213 | return TypeDef(reader, 0, systemTokens[typeRefToken]!); 214 | } 215 | if (systemTokens.containsValue(typeName)) { 216 | return TypeDef(reader, 0, typeName); 217 | } 218 | // Perhaps we can find it in the current scope after all (for example, 219 | // it's a nested class) 220 | try { 221 | final typedef = TypeDef.fromTypeDefToken(reader, typeRefToken); 222 | return typedef; 223 | } catch (exception) { 224 | throw WinmdException( 225 | 'Unable to find scope for $typeName [${typeRefToken.toHexString(32)}]...'); 226 | } 227 | } 228 | } else { 229 | throw WindowsException(hr); 230 | } 231 | } finally { 232 | free(ptkResolutionScope); 233 | free(szName); 234 | free(pchName); 235 | } 236 | } 237 | 238 | /// Instantiate a typedef from a TypeSpec token. 239 | factory TypeDef.fromTypeSpecToken( 240 | IMetaDataImport2 reader, int typeSpecToken) { 241 | final ppvSig = calloc(); 242 | final pcbSig = calloc(); 243 | 244 | try { 245 | final hr = 246 | reader.GetTypeSpecFromToken(typeSpecToken, ppvSig.cast(), pcbSig); 247 | final signature = ppvSig.value.asTypedList(pcbSig.value); 248 | final typeTuple = parseTypeFromSignature(signature, reader); 249 | 250 | if (SUCCEEDED(hr)) { 251 | return TypeDef( 252 | reader, typeSpecToken, '', 0, 0, typeTuple.typeIdentifier); 253 | } else { 254 | throw WindowsException(hr); 255 | } 256 | } finally { 257 | free(ppvSig); 258 | free(pcbSig); 259 | } 260 | } 261 | 262 | /// Converts an individual interface into a type. 263 | TypeDef processInterfaceToken(int token) { 264 | final ptkClass = calloc(); 265 | final ptkIface = calloc(); 266 | 267 | try { 268 | final hr = reader.GetInterfaceImplProps(token, ptkClass, ptkIface); 269 | if (SUCCEEDED(hr)) { 270 | final interfaceToken = ptkIface.value; 271 | return TypeDef.fromToken(reader, interfaceToken); 272 | } else { 273 | throw WindowsException(hr); 274 | } 275 | } finally { 276 | free(ptkClass); 277 | free(ptkIface); 278 | } 279 | } 280 | 281 | /// Enumerate all interfaces that this type implements. 282 | List get interfaces { 283 | if (_interfaces.isEmpty) { 284 | final phEnum = calloc(); 285 | final rImpls = calloc(); 286 | final pcImpls = calloc(); 287 | 288 | try { 289 | var hr = reader.EnumInterfaceImpls(phEnum, token, rImpls, 1, pcImpls); 290 | while (hr == S_OK) { 291 | final interfaceToken = rImpls.value; 292 | 293 | _interfaces.add(processInterfaceToken(interfaceToken)); 294 | hr = reader.EnumInterfaceImpls(phEnum, token, rImpls, 1, pcImpls); 295 | } 296 | } finally { 297 | reader.CloseEnum(phEnum.value); 298 | free(phEnum); 299 | free(rImpls); 300 | free(pcImpls); 301 | } 302 | } 303 | return _interfaces; 304 | } 305 | 306 | /// Enumerate all fields contained within this type. 307 | List get fields { 308 | if (_fields.isEmpty) { 309 | final phEnum = calloc(); 310 | final rgFields = calloc(); 311 | final pcTokens = calloc(); 312 | 313 | try { 314 | var hr = reader.EnumFields(phEnum, token, rgFields, 1, pcTokens); 315 | while (hr == S_OK) { 316 | final fieldToken = rgFields.value; 317 | 318 | _fields.add(Field.fromToken(reader, fieldToken)); 319 | hr = reader.EnumFields(phEnum, token, rgFields, 1, pcTokens); 320 | } 321 | } finally { 322 | reader.CloseEnum(phEnum.value); 323 | free(phEnum); 324 | free(rgFields); 325 | free(pcTokens); 326 | } 327 | } 328 | return _fields; 329 | } 330 | 331 | /// Enumerate all methods contained within this type. 332 | List get methods { 333 | if (_methods.isEmpty) { 334 | final phEnum = calloc(); 335 | final rgMethods = calloc(); 336 | final pcTokens = calloc(); 337 | 338 | try { 339 | var hr = reader.EnumMethods(phEnum, token, rgMethods, 1, pcTokens); 340 | while (hr == S_OK) { 341 | final methodToken = rgMethods.value; 342 | 343 | _methods.add(Method.fromToken(reader, methodToken)); 344 | hr = reader.EnumMethods(phEnum, token, rgMethods, 1, pcTokens); 345 | } 346 | } finally { 347 | reader.CloseEnum(phEnum.value); 348 | free(phEnum); 349 | free(rgMethods); 350 | free(pcTokens); 351 | } 352 | } 353 | return _methods; 354 | } 355 | 356 | /// Enumerate all properties contained within this type. 357 | List get properties { 358 | if (_properties.isEmpty) { 359 | final phEnum = calloc(); 360 | final rgProperties = calloc(); 361 | final pcProperties = calloc(); 362 | 363 | try { 364 | var hr = 365 | reader.EnumProperties(phEnum, token, rgProperties, 1, pcProperties); 366 | while (hr == S_OK) { 367 | final propertyToken = rgProperties.value; 368 | 369 | _properties.add(Property.fromToken(reader, propertyToken)); 370 | hr = reader.EnumMethods(phEnum, token, rgProperties, 1, pcProperties); 371 | } 372 | } finally { 373 | reader.CloseEnum(phEnum.value); 374 | free(phEnum); 375 | free(rgProperties); 376 | free(pcProperties); 377 | } 378 | } 379 | return _properties; 380 | } 381 | 382 | /// Enumerate all events contained within this type. 383 | List get events { 384 | if (_events.isEmpty) { 385 | final phEnum = calloc(); 386 | final rgEvents = calloc(); 387 | final pcEvents = calloc(); 388 | 389 | try { 390 | var hr = reader.EnumEvents(phEnum, token, rgEvents, 1, pcEvents); 391 | while (hr == S_OK) { 392 | final eventToken = rgEvents.value; 393 | 394 | _events.add(Event.fromToken(reader, eventToken)); 395 | hr = reader.EnumEvents(phEnum, token, rgEvents, 1, pcEvents); 396 | } 397 | } finally { 398 | reader.CloseEnum(phEnum.value); 399 | free(phEnum); 400 | free(rgEvents); 401 | free(pcEvents); 402 | } 403 | } 404 | return _events; 405 | } 406 | 407 | /// Get a field matching the name, if one exists. 408 | /// 409 | /// Returns null if the field is not found. 410 | Field? findField(String fieldName) { 411 | try { 412 | return fields.firstWhere((field) => field.name == fieldName); 413 | } on StateError { 414 | return null; 415 | } 416 | } 417 | 418 | /// Get a method matching the name, if one exists. 419 | /// 420 | /// Returns null if the method is not found. 421 | Method? findMethod(String methodName) { 422 | final szName = methodName.toNativeUtf16(); 423 | final pmb = calloc(); 424 | 425 | try { 426 | final hr = reader.FindMethod(token, szName, nullptr, 0, pmb); 427 | if (SUCCEEDED(hr)) { 428 | return Method.fromToken(reader, pmb.value); 429 | } else if (hr == CLDB_E_RECORD_NOTFOUND) { 430 | return null; 431 | } else { 432 | throw COMException(hr); 433 | } 434 | } finally { 435 | free(szName); 436 | free(pmb); 437 | } 438 | } 439 | 440 | /// Gets the type referencing this type's superclass. 441 | TypeDef? get parent => 442 | token == 0 ? null : TypeDef.fromToken(reader, baseTypeToken); 443 | 444 | String? getCustomGUIDAttribute(String guidAttributeName) { 445 | final ptrAttributeName = guidAttributeName.toNativeUtf16(); 446 | final ppData = calloc>(); 447 | final pcbData = calloc(); 448 | 449 | try { 450 | final hr = reader.GetCustomAttributeByName( 451 | token, ptrAttributeName, ppData.cast(), pcbData); 452 | if (SUCCEEDED(hr)) { 453 | final blob = ppData.value; 454 | if (pcbData.value > 0) { 455 | final returnValue = blob.elementAt(2).cast(); 456 | return returnValue.ref.toString(); 457 | } 458 | } 459 | } finally { 460 | free(ptrAttributeName); 461 | free(ppData); 462 | free(pcbData); 463 | } 464 | } 465 | 466 | /// Get the GUID for this type. 467 | /// 468 | /// Returns null if a GUID couldn't be found. 469 | String? get guid { 470 | var guid = 471 | getCustomGUIDAttribute('Windows.Foundation.Metadata.GuidAttribute'); 472 | guid ??= getCustomGUIDAttribute('Windows.Win32.Interop.GuidAttribute'); 473 | 474 | return guid; 475 | } 476 | 477 | @override 478 | String toString() => 'TypeDef: $typeName'; 479 | } 480 | --------------------------------------------------------------------------------